teamix-evo 0.8.0 → 0.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +35 -28
- package/dist/core/index.d.ts +234 -11
- package/dist/core/index.js +2424 -861
- package/dist/core/index.js.map +1 -1
- package/dist/index.js +6159 -2153
- package/dist/index.js.map +1 -1
- package/package.json +7 -7
package/dist/core/index.js
CHANGED
|
@@ -113,7 +113,8 @@ var TEAMIX_DIR = ".teamix-evo";
|
|
|
113
113
|
var CONFIG_FILE = "config.json";
|
|
114
114
|
var MANIFEST_FILE = "manifest.json";
|
|
115
115
|
var TOKENS_LOCK_FILE = "tokens-lock.json";
|
|
116
|
-
var SKILLS_DIR = "skills";
|
|
116
|
+
var SKILLS_DIR = "skills-source";
|
|
117
|
+
var LEGACY_SKILLS_DIR = "skills";
|
|
117
118
|
var SKILLS_LOCK_FILE = "manifest.lock.json";
|
|
118
119
|
function getTeamixDir(projectRoot) {
|
|
119
120
|
return path2.join(projectRoot, TEAMIX_DIR);
|
|
@@ -197,6 +198,9 @@ function getSkillsSourceDir(projectRoot, skillName) {
|
|
|
197
198
|
const base = path2.join(projectRoot, TEAMIX_DIR, SKILLS_DIR);
|
|
198
199
|
return skillName ? path2.join(base, skillName) : base;
|
|
199
200
|
}
|
|
201
|
+
function getLegacySkillsSourceDir(projectRoot) {
|
|
202
|
+
return path2.join(projectRoot, TEAMIX_DIR, LEGACY_SKILLS_DIR);
|
|
203
|
+
}
|
|
200
204
|
async function readSkillsLock(projectRoot) {
|
|
201
205
|
const lockPath = path2.join(
|
|
202
206
|
projectRoot,
|
|
@@ -365,13 +369,20 @@ function resolveSourcePath(source, variantDir, packageRoot) {
|
|
|
365
369
|
}
|
|
366
370
|
return path6.join(variantDir, source);
|
|
367
371
|
}
|
|
368
|
-
|
|
372
|
+
var DEFAULT_SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
373
|
+
"node_modules",
|
|
374
|
+
"dist",
|
|
375
|
+
"build",
|
|
376
|
+
".teamix-evo"
|
|
377
|
+
]);
|
|
378
|
+
async function walkDir(dir, skipDirs) {
|
|
369
379
|
const files = [];
|
|
370
380
|
const entries = await fs4.readdir(dir, { withFileTypes: true });
|
|
371
381
|
for (const entry of entries) {
|
|
372
382
|
const fullPath = path6.join(dir, entry.name);
|
|
373
383
|
if (entry.isDirectory()) {
|
|
374
|
-
|
|
384
|
+
if (skipDirs && skipDirs.has(entry.name)) continue;
|
|
385
|
+
files.push(...await walkDir(fullPath, skipDirs));
|
|
375
386
|
} else if (entry.isFile()) {
|
|
376
387
|
files.push(fullPath);
|
|
377
388
|
}
|
|
@@ -385,6 +396,7 @@ function resolveTokensPackageRoot(packageName) {
|
|
|
385
396
|
|
|
386
397
|
// src/core/skills-installer.ts
|
|
387
398
|
async function installSkills(options) {
|
|
399
|
+
await migrateLegacySkillsSourceDir(options.projectRoot);
|
|
388
400
|
const { manifest, ides, scope, onlyIds } = options;
|
|
389
401
|
const installed = [];
|
|
390
402
|
const targets = manifest.skills.filter(
|
|
@@ -677,6 +689,7 @@ function escapeRegExp(str) {
|
|
|
677
689
|
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
678
690
|
}
|
|
679
691
|
async function syncSkillsToIdes(options) {
|
|
692
|
+
await migrateLegacySkillsSourceDir(options.projectRoot);
|
|
680
693
|
const { projectRoot, skills, ides, scope, onlyIds } = options;
|
|
681
694
|
const out = [];
|
|
682
695
|
const targets = skills.filter((s) => !onlyIds || onlyIds.includes(s.id));
|
|
@@ -720,6 +733,118 @@ async function syncSkillsToIdes(options) {
|
|
|
720
733
|
}
|
|
721
734
|
return { resources: out, count: out.length };
|
|
722
735
|
}
|
|
736
|
+
async function migrateLegacySkillsSourceDir(projectRoot) {
|
|
737
|
+
const legacyDir = getLegacySkillsSourceDir(projectRoot);
|
|
738
|
+
const newDir = getSkillsSourceDir(projectRoot);
|
|
739
|
+
let legacyExists = false;
|
|
740
|
+
let newExists = false;
|
|
741
|
+
try {
|
|
742
|
+
legacyExists = (await fs5.stat(legacyDir)).isDirectory();
|
|
743
|
+
} catch {
|
|
744
|
+
legacyExists = false;
|
|
745
|
+
}
|
|
746
|
+
try {
|
|
747
|
+
newExists = (await fs5.stat(newDir)).isDirectory();
|
|
748
|
+
} catch {
|
|
749
|
+
newExists = false;
|
|
750
|
+
}
|
|
751
|
+
if (!legacyExists) return;
|
|
752
|
+
if (newExists) {
|
|
753
|
+
logger.warn(
|
|
754
|
+
`Detected stale legacy skills source dir at ${legacyDir} alongside ${newDir}; the new layout takes precedence \u2014 you can safely delete the legacy dir.`
|
|
755
|
+
);
|
|
756
|
+
return;
|
|
757
|
+
}
|
|
758
|
+
try {
|
|
759
|
+
await fs5.rename(legacyDir, newDir);
|
|
760
|
+
logger.info(
|
|
761
|
+
`Migrated skills source dir: \`.teamix-evo/${LEGACY_SKILLS_DIR}/\` \u2192 \`.teamix-evo/skills-source/\``
|
|
762
|
+
);
|
|
763
|
+
} catch (err) {
|
|
764
|
+
logger.warn(
|
|
765
|
+
`Failed to rename legacy skills source dir (${getErrorMessage(
|
|
766
|
+
err
|
|
767
|
+
)}); leaving as-is. New skills will install under the new layout.`
|
|
768
|
+
);
|
|
769
|
+
return;
|
|
770
|
+
}
|
|
771
|
+
try {
|
|
772
|
+
const manifest = await readInstalledManifest(projectRoot);
|
|
773
|
+
if (!manifest) return;
|
|
774
|
+
const legacyFragmentPosix = `/.teamix-evo/${LEGACY_SKILLS_DIR}/`;
|
|
775
|
+
const newFragmentPosix = `/.teamix-evo/skills-source/`;
|
|
776
|
+
const legacyFragmentNative = `${path7.sep}.teamix-evo${path7.sep}${LEGACY_SKILLS_DIR}${path7.sep}`;
|
|
777
|
+
const newFragmentNative = `${path7.sep}.teamix-evo${path7.sep}skills-source${path7.sep}`;
|
|
778
|
+
let touched = 0;
|
|
779
|
+
for (const pkg of manifest.installed) {
|
|
780
|
+
for (const r of pkg.resources) {
|
|
781
|
+
if (typeof r.target !== "string") continue;
|
|
782
|
+
const before = r.target;
|
|
783
|
+
let after = before.replace(legacyFragmentPosix, newFragmentPosix);
|
|
784
|
+
after = after.replace(legacyFragmentNative, newFragmentNative);
|
|
785
|
+
if (after !== before) {
|
|
786
|
+
r.target = after;
|
|
787
|
+
touched += 1;
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
if (touched > 0) {
|
|
792
|
+
await writeInstalledManifest(projectRoot, manifest);
|
|
793
|
+
logger.debug(
|
|
794
|
+
`Rewrote ${touched} manifest target(s) to the new skills-source path.`
|
|
795
|
+
);
|
|
796
|
+
}
|
|
797
|
+
} catch (err) {
|
|
798
|
+
logger.warn(
|
|
799
|
+
`Migrated skills source dir but failed to update manifest paths (${getErrorMessage(
|
|
800
|
+
err
|
|
801
|
+
)}); manifest may still reference legacy paths.`
|
|
802
|
+
);
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
async function pruneEmptyIdeSkillDirs(args) {
|
|
806
|
+
const removed = [];
|
|
807
|
+
for (const ide of args.ides) {
|
|
808
|
+
const adapter = getAdapter(ide);
|
|
809
|
+
const placeholderDir = adapter.getSkillTargetDir(
|
|
810
|
+
"__placeholder__",
|
|
811
|
+
args.scope,
|
|
812
|
+
args.projectRoot
|
|
813
|
+
);
|
|
814
|
+
const skillsRoot = path7.dirname(placeholderDir);
|
|
815
|
+
let entries;
|
|
816
|
+
try {
|
|
817
|
+
entries = await fs5.readdir(skillsRoot);
|
|
818
|
+
} catch {
|
|
819
|
+
continue;
|
|
820
|
+
}
|
|
821
|
+
for (const name of entries) {
|
|
822
|
+
const dir = path7.join(skillsRoot, name);
|
|
823
|
+
let stat5;
|
|
824
|
+
try {
|
|
825
|
+
stat5 = await fs5.stat(dir);
|
|
826
|
+
} catch {
|
|
827
|
+
continue;
|
|
828
|
+
}
|
|
829
|
+
if (!stat5.isDirectory()) continue;
|
|
830
|
+
let children;
|
|
831
|
+
try {
|
|
832
|
+
children = await fs5.readdir(dir);
|
|
833
|
+
} catch {
|
|
834
|
+
continue;
|
|
835
|
+
}
|
|
836
|
+
if (children.some((c) => c === "SKILL.md")) continue;
|
|
837
|
+
if (children.length !== 0) continue;
|
|
838
|
+
try {
|
|
839
|
+
await fs5.rmdir(dir);
|
|
840
|
+
removed.push(dir);
|
|
841
|
+
logger.debug(`Pruned empty IDE skill dir: ${dir}`);
|
|
842
|
+
} catch {
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
return removed;
|
|
847
|
+
}
|
|
723
848
|
async function removeSkillFiles(records) {
|
|
724
849
|
const removed = [];
|
|
725
850
|
for (const r of records) {
|
|
@@ -815,13 +940,29 @@ async function runSkillsInit(options) {
|
|
|
815
940
|
}
|
|
816
941
|
return true;
|
|
817
942
|
}).map((s) => s.id);
|
|
818
|
-
const skippedSkillIds =
|
|
819
|
-
|
|
943
|
+
const { onlyIds, skippedSkillIds, outdatedSkills } = partitionByVersion(
|
|
944
|
+
candidateIds,
|
|
945
|
+
manifest,
|
|
946
|
+
existing
|
|
820
947
|
);
|
|
821
|
-
|
|
822
|
-
if (existingSkillsCfg && onlyIds.length === 0) {
|
|
948
|
+
if (existingSkillsCfg && onlyIds.length === 0 && outdatedSkills.length === 0) {
|
|
823
949
|
return { status: "already-initialized" };
|
|
824
950
|
}
|
|
951
|
+
if (onlyIds.length === 0) {
|
|
952
|
+
return {
|
|
953
|
+
status: "installed",
|
|
954
|
+
packageName,
|
|
955
|
+
version: existingSkillsCfg?.version ?? manifest.version,
|
|
956
|
+
ides,
|
|
957
|
+
scope,
|
|
958
|
+
skillCount: 0,
|
|
959
|
+
fileCount: 0,
|
|
960
|
+
resources: [],
|
|
961
|
+
addedSkillIds: [],
|
|
962
|
+
skippedSkillIds,
|
|
963
|
+
outdatedSkills
|
|
964
|
+
};
|
|
965
|
+
}
|
|
825
966
|
return finalizeSkillsInstall({
|
|
826
967
|
projectRoot,
|
|
827
968
|
packageName,
|
|
@@ -833,6 +974,7 @@ async function runSkillsInit(options) {
|
|
|
833
974
|
scope,
|
|
834
975
|
onlyIds,
|
|
835
976
|
skippedSkillIds,
|
|
977
|
+
outdatedSkills,
|
|
836
978
|
existing,
|
|
837
979
|
existingConfig
|
|
838
980
|
});
|
|
@@ -873,10 +1015,11 @@ async function runSkillsAdd(options) {
|
|
|
873
1015
|
}
|
|
874
1016
|
}
|
|
875
1017
|
const existing = await readExistingState(projectRoot, packageName);
|
|
876
|
-
const skippedSkillIds =
|
|
877
|
-
|
|
1018
|
+
const { onlyIds, skippedSkillIds, outdatedSkills } = partitionByVersion(
|
|
1019
|
+
requestedNames,
|
|
1020
|
+
manifest,
|
|
1021
|
+
existing
|
|
878
1022
|
);
|
|
879
|
-
const onlyIds = requestedNames.filter((n) => !existing.skillIds.has(n));
|
|
880
1023
|
if (onlyIds.length === 0) {
|
|
881
1024
|
return {
|
|
882
1025
|
status: "installed",
|
|
@@ -888,7 +1031,8 @@ async function runSkillsAdd(options) {
|
|
|
888
1031
|
fileCount: 0,
|
|
889
1032
|
resources: [],
|
|
890
1033
|
addedSkillIds: [],
|
|
891
|
-
skippedSkillIds
|
|
1034
|
+
skippedSkillIds,
|
|
1035
|
+
outdatedSkills
|
|
892
1036
|
};
|
|
893
1037
|
}
|
|
894
1038
|
return finalizeSkillsInstall({
|
|
@@ -902,10 +1046,52 @@ async function runSkillsAdd(options) {
|
|
|
902
1046
|
scope,
|
|
903
1047
|
onlyIds,
|
|
904
1048
|
skippedSkillIds,
|
|
1049
|
+
outdatedSkills,
|
|
905
1050
|
existing,
|
|
906
1051
|
existingConfig
|
|
907
1052
|
});
|
|
908
1053
|
}
|
|
1054
|
+
function partitionByVersion(ids, manifest, existing) {
|
|
1055
|
+
const manifestById = new Map(manifest.skills.map((s) => [s.id, s]));
|
|
1056
|
+
const onlyIds = [];
|
|
1057
|
+
const skippedSkillIds = [];
|
|
1058
|
+
const outdatedSkills = [];
|
|
1059
|
+
for (const name of ids) {
|
|
1060
|
+
if (!existing.skillIds.has(name)) {
|
|
1061
|
+
onlyIds.push(name);
|
|
1062
|
+
continue;
|
|
1063
|
+
}
|
|
1064
|
+
const installedVer = existing.lock?.skills?.[name]?.version;
|
|
1065
|
+
const latestVer = manifestById.get(name)?.version;
|
|
1066
|
+
if (installedVer && latestVer && compareSemver(installedVer, latestVer) < 0) {
|
|
1067
|
+
outdatedSkills.push({
|
|
1068
|
+
id: name,
|
|
1069
|
+
installed: installedVer,
|
|
1070
|
+
latest: latestVer
|
|
1071
|
+
});
|
|
1072
|
+
} else {
|
|
1073
|
+
skippedSkillIds.push(name);
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
return { onlyIds, skippedSkillIds, outdatedSkills };
|
|
1077
|
+
}
|
|
1078
|
+
function parseSemverTriple(v) {
|
|
1079
|
+
const m = /^(\d+)\.(\d+)\.(\d+)/.exec(v);
|
|
1080
|
+
if (!m) return null;
|
|
1081
|
+
return [Number(m[1]), Number(m[2]), Number(m[3])];
|
|
1082
|
+
}
|
|
1083
|
+
function compareSemver(a, b) {
|
|
1084
|
+
const pa = parseSemverTriple(a);
|
|
1085
|
+
const pb = parseSemverTriple(b);
|
|
1086
|
+
if (!pa || !pb) {
|
|
1087
|
+
if (a === b) return 0;
|
|
1088
|
+
return a < b ? -1 : 1;
|
|
1089
|
+
}
|
|
1090
|
+
for (let i = 0; i < 3; i++) {
|
|
1091
|
+
if (pa[i] !== pb[i]) return pa[i] < pb[i] ? -1 : 1;
|
|
1092
|
+
}
|
|
1093
|
+
return 0;
|
|
1094
|
+
}
|
|
909
1095
|
async function readExistingState(projectRoot, packageName) {
|
|
910
1096
|
const installed = await readInstalledManifest(projectRoot);
|
|
911
1097
|
const pkg = installed?.installed.find((p) => p.package === packageName);
|
|
@@ -930,6 +1116,7 @@ async function finalizeSkillsInstall(args) {
|
|
|
930
1116
|
scope,
|
|
931
1117
|
onlyIds,
|
|
932
1118
|
skippedSkillIds,
|
|
1119
|
+
outdatedSkills,
|
|
933
1120
|
existing,
|
|
934
1121
|
existingConfig
|
|
935
1122
|
} = args;
|
|
@@ -995,6 +1182,10 @@ async function finalizeSkillsInstall(args) {
|
|
|
995
1182
|
}
|
|
996
1183
|
await writeSkillsLock(projectRoot, lock);
|
|
997
1184
|
await ensureMcpJson(projectRoot);
|
|
1185
|
+
try {
|
|
1186
|
+
await pruneEmptyIdeSkillDirs({ projectRoot, ides, scope });
|
|
1187
|
+
} catch {
|
|
1188
|
+
}
|
|
998
1189
|
return {
|
|
999
1190
|
status: "installed",
|
|
1000
1191
|
packageName,
|
|
@@ -1005,7 +1196,8 @@ async function finalizeSkillsInstall(args) {
|
|
|
1005
1196
|
fileCount: result.count,
|
|
1006
1197
|
resources: result.resources,
|
|
1007
1198
|
addedSkillIds: onlyIds,
|
|
1008
|
-
skippedSkillIds
|
|
1199
|
+
skippedSkillIds,
|
|
1200
|
+
outdatedSkills: outdatedSkills ?? []
|
|
1009
1201
|
};
|
|
1010
1202
|
}
|
|
1011
1203
|
function mergeInstalledResources(existing, next) {
|
|
@@ -1066,13 +1258,16 @@ Run \`npx teamix-evo@latest tokens list-variants\` to see all options.`
|
|
|
1066
1258
|
if (!await fileExists(overridesAbs)) {
|
|
1067
1259
|
await writeFileSafe(overridesAbs, EMPTY_OVERRIDES_TEMPLATE);
|
|
1068
1260
|
}
|
|
1069
|
-
const
|
|
1070
|
-
installed.
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1261
|
+
const overridesId = `tokens:${CONSUMER_OVERRIDES_FILE}`;
|
|
1262
|
+
if (!installed.some((r) => r.id === overridesId)) {
|
|
1263
|
+
const overridesContent = await fs6.readFile(overridesAbs, "utf-8");
|
|
1264
|
+
installed.push({
|
|
1265
|
+
id: overridesId,
|
|
1266
|
+
target: path9.posix.join(CONSUMER_TOKENS_DIR, CONSUMER_OVERRIDES_FILE),
|
|
1267
|
+
hash: computeHash(overridesContent),
|
|
1268
|
+
strategy: "frozen"
|
|
1269
|
+
});
|
|
1270
|
+
}
|
|
1076
1271
|
const lock = {
|
|
1077
1272
|
schemaVersion: 1,
|
|
1078
1273
|
variant: {
|
|
@@ -1213,6 +1408,9 @@ async function installVariantFile(fileRelToPackage, packageRoot, projectRoot) {
|
|
|
1213
1408
|
const targetRel = path9.posix.join(CONSUMER_TOKENS_DIR, CONSUMER_THEME_FILE);
|
|
1214
1409
|
const targetAbs = path9.join(projectRoot, targetRel);
|
|
1215
1410
|
const content = await fs6.readFile(sourceAbs, "utf-8");
|
|
1411
|
+
if (await fileExists(targetAbs)) {
|
|
1412
|
+
await backupFile(targetAbs, projectRoot);
|
|
1413
|
+
}
|
|
1216
1414
|
await writeFileSafe(targetAbs, content);
|
|
1217
1415
|
return {
|
|
1218
1416
|
id: `tokens:${CONSUMER_THEME_FILE}`,
|
|
@@ -1593,6 +1791,9 @@ async function installUiEntries(options) {
|
|
|
1593
1791
|
const sourceAbs = path11.resolve(rootForEntry, file.source);
|
|
1594
1792
|
const raw = await fs8.readFile(sourceAbs, "utf-8");
|
|
1595
1793
|
const transformed = rewriteImports(raw, aliases);
|
|
1794
|
+
if (exists) {
|
|
1795
|
+
await backupFile(targetAbs, projectRoot);
|
|
1796
|
+
}
|
|
1596
1797
|
await writeFileSafe(targetAbs, transformed);
|
|
1597
1798
|
written++;
|
|
1598
1799
|
logger.info(` write: ${rel(projectRoot, targetAbs)}`);
|
|
@@ -1963,18 +2164,29 @@ var ESLINT_DEPS = [
|
|
|
1963
2164
|
];
|
|
1964
2165
|
var STYLELINT_DEPS = ["@teamix-evo/stylelint-config", "stylelint"];
|
|
1965
2166
|
async function runLintInit(options) {
|
|
1966
|
-
const {
|
|
2167
|
+
const {
|
|
2168
|
+
projectRoot,
|
|
2169
|
+
skipInstall,
|
|
2170
|
+
eslintStrategy = "overwrite",
|
|
2171
|
+
stylelintStrategy = "overwrite",
|
|
2172
|
+
eslintExistingPaths = [],
|
|
2173
|
+
stylelintExistingPaths = []
|
|
2174
|
+
} = options;
|
|
1967
2175
|
const eslintConfigPath = path13.join(projectRoot, "eslint.config.js");
|
|
1968
2176
|
const stylelintConfigPath = path13.join(projectRoot, "stylelint.config.cjs");
|
|
1969
|
-
const
|
|
1970
|
-
const
|
|
1971
|
-
|
|
2177
|
+
const eslintTemplateExists = await fileExists(eslintConfigPath);
|
|
2178
|
+
const stylelintTemplateExists = await fileExists(stylelintConfigPath);
|
|
2179
|
+
const eslintSkipRequested = eslintStrategy === "skip" && eslintExistingPaths.length > 0;
|
|
2180
|
+
const stylelintSkipRequested = stylelintStrategy === "skip" && stylelintExistingPaths.length > 0;
|
|
2181
|
+
const eslintNeedsWrite = !eslintTemplateExists && !eslintSkipRequested;
|
|
2182
|
+
const stylelintNeedsWrite = !stylelintTemplateExists && !stylelintSkipRequested;
|
|
2183
|
+
if (!eslintNeedsWrite && !stylelintNeedsWrite) {
|
|
1972
2184
|
return { status: "already-initialized" };
|
|
1973
2185
|
}
|
|
1974
2186
|
if (!skipInstall) {
|
|
1975
2187
|
const depsToInstall = [
|
|
1976
|
-
...
|
|
1977
|
-
...
|
|
2188
|
+
...eslintNeedsWrite ? ESLINT_DEPS : [],
|
|
2189
|
+
...stylelintNeedsWrite ? STYLELINT_DEPS : []
|
|
1978
2190
|
];
|
|
1979
2191
|
if (depsToInstall.length > 0) {
|
|
1980
2192
|
const pm = detectPm(projectRoot);
|
|
@@ -1983,23 +2195,66 @@ async function runLintInit(options) {
|
|
|
1983
2195
|
await execa(pm, args, { cwd: projectRoot, stdio: "inherit" });
|
|
1984
2196
|
}
|
|
1985
2197
|
}
|
|
2198
|
+
if (eslintNeedsWrite && eslintExistingPaths.length > 0) {
|
|
2199
|
+
for (const rel2 of eslintExistingPaths) {
|
|
2200
|
+
await backupFile(path13.join(projectRoot, rel2), projectRoot);
|
|
2201
|
+
}
|
|
2202
|
+
}
|
|
2203
|
+
if (stylelintNeedsWrite && stylelintExistingPaths.length > 0) {
|
|
2204
|
+
for (const rel2 of stylelintExistingPaths) {
|
|
2205
|
+
await backupFile(path13.join(projectRoot, rel2), projectRoot);
|
|
2206
|
+
}
|
|
2207
|
+
}
|
|
1986
2208
|
let wroteEslint = false;
|
|
1987
2209
|
let wroteStylelint = false;
|
|
1988
|
-
if (
|
|
2210
|
+
if (eslintNeedsWrite) {
|
|
1989
2211
|
await writeFileSafe(eslintConfigPath, ESLINT_CONFIG_CONTENT);
|
|
1990
2212
|
logger.debug(`Wrote eslint.config.js \u2192 ${eslintConfigPath}`);
|
|
1991
2213
|
wroteEslint = true;
|
|
1992
2214
|
}
|
|
1993
|
-
if (
|
|
2215
|
+
if (stylelintNeedsWrite) {
|
|
1994
2216
|
await writeFileSafe(stylelintConfigPath, STYLELINT_CONFIG_CONTENT);
|
|
1995
2217
|
logger.debug(`Wrote stylelint.config.cjs \u2192 ${stylelintConfigPath}`);
|
|
1996
2218
|
wroteStylelint = true;
|
|
1997
2219
|
}
|
|
1998
|
-
await patchPackageJsonScripts(projectRoot);
|
|
2220
|
+
const packageJsonPatched = await patchPackageJsonScripts(projectRoot);
|
|
2221
|
+
let stylelintIgnoreFilesWarning = false;
|
|
2222
|
+
if (!stylelintNeedsWrite && stylelintTemplateExists) {
|
|
2223
|
+
try {
|
|
2224
|
+
const existingContent = fs9.readFileSync(stylelintConfigPath, "utf-8");
|
|
2225
|
+
const usesTeamixPreset = existingContent.includes("@teamix-evo/stylelint-config/presets/") || existingContent.includes("@teamix-evo/stylelint-config/preset/");
|
|
2226
|
+
const hasTokenIgnore = existingContent.includes("tokens.theme.css") && existingContent.includes("tokens.overrides.css");
|
|
2227
|
+
if (!usesTeamixPreset && !hasTokenIgnore) {
|
|
2228
|
+
stylelintIgnoreFilesWarning = true;
|
|
2229
|
+
logger.warn(
|
|
2230
|
+
[
|
|
2231
|
+
"\u68C0\u6D4B\u5230\u73B0\u6709 stylelint \u914D\u7F6E\u672A\u6392\u9664 token \u5B9A\u4E49\u6587\u4EF6\u3002",
|
|
2232
|
+
"\u5EFA\u8BAE\u5728 stylelint.config.cjs \u4E2D\u6DFB\u52A0 ignoreFiles:",
|
|
2233
|
+
"",
|
|
2234
|
+
" ignoreFiles: [",
|
|
2235
|
+
" '**/tokens.theme.css',",
|
|
2236
|
+
" '**/tokens.overrides.css',",
|
|
2237
|
+
" ]",
|
|
2238
|
+
"",
|
|
2239
|
+
"\u6216\u5207\u6362\u5230 teamix-evo \u9884\u8BBE\u4EE5\u81EA\u52A8\u83B7\u5F97\u6392\u9664\u89C4\u5219:",
|
|
2240
|
+
"",
|
|
2241
|
+
" extends: ['@teamix-evo/stylelint-config/presets/consumer']"
|
|
2242
|
+
].join("\n")
|
|
2243
|
+
);
|
|
2244
|
+
}
|
|
2245
|
+
} catch {
|
|
2246
|
+
}
|
|
2247
|
+
}
|
|
1999
2248
|
return {
|
|
2000
2249
|
status: "installed",
|
|
2001
2250
|
eslint: wroteEslint,
|
|
2002
|
-
stylelint: wroteStylelint
|
|
2251
|
+
stylelint: wroteStylelint,
|
|
2252
|
+
eslintMergeRequested: wroteEslint && eslintStrategy === "merge" && eslintExistingPaths.length > 0,
|
|
2253
|
+
stylelintMergeRequested: wroteStylelint && stylelintStrategy === "merge" && stylelintExistingPaths.length > 0,
|
|
2254
|
+
eslintSkipped: eslintSkipRequested,
|
|
2255
|
+
stylelintSkipped: stylelintSkipRequested,
|
|
2256
|
+
packageJsonPatched,
|
|
2257
|
+
stylelintIgnoreFilesWarning
|
|
2003
2258
|
};
|
|
2004
2259
|
}
|
|
2005
2260
|
function detectPm(projectRoot) {
|
|
@@ -2010,12 +2265,12 @@ function detectPm(projectRoot) {
|
|
|
2010
2265
|
async function patchPackageJsonScripts(projectRoot) {
|
|
2011
2266
|
const pkgPath = path13.join(projectRoot, "package.json");
|
|
2012
2267
|
const raw = await readFileOrNull(pkgPath);
|
|
2013
|
-
if (!raw) return;
|
|
2268
|
+
if (!raw) return false;
|
|
2014
2269
|
let pkg;
|
|
2015
2270
|
try {
|
|
2016
2271
|
pkg = JSON.parse(raw);
|
|
2017
2272
|
} catch {
|
|
2018
|
-
return;
|
|
2273
|
+
return false;
|
|
2019
2274
|
}
|
|
2020
2275
|
const scripts = pkg.scripts ?? {};
|
|
2021
2276
|
let changed = false;
|
|
@@ -2028,17 +2283,22 @@ async function patchPackageJsonScripts(projectRoot) {
|
|
|
2028
2283
|
changed = true;
|
|
2029
2284
|
}
|
|
2030
2285
|
if (changed) {
|
|
2286
|
+
await backupFile(pkgPath, projectRoot);
|
|
2031
2287
|
pkg.scripts = scripts;
|
|
2032
2288
|
await writeFileSafe(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
|
|
2033
2289
|
logger.debug("Patched package.json scripts with lint / lint:css");
|
|
2034
2290
|
}
|
|
2291
|
+
return changed;
|
|
2035
2292
|
}
|
|
2036
2293
|
|
|
2037
2294
|
// src/core/agents-md.ts
|
|
2038
2295
|
import * as fs10 from "fs/promises";
|
|
2039
2296
|
import * as path14 from "path";
|
|
2297
|
+
import { hasManagedRegion as hasManagedRegion2, replaceManagedRegion as replaceManagedRegion2 } from "@teamix-evo/registry";
|
|
2298
|
+
var AGENTS_MD_MANAGED_ID = "teamix-evo-skills";
|
|
2040
2299
|
async function runGenerateAgentsMd(options) {
|
|
2041
2300
|
const { projectRoot, variant, skillIds } = options;
|
|
2301
|
+
const mode = options.mode ?? "overwrite";
|
|
2042
2302
|
const ordered = [...skillIds].sort(
|
|
2043
2303
|
(a, b) => bucketRank(a) - bucketRank(b) || a.localeCompare(b)
|
|
2044
2304
|
);
|
|
@@ -2049,13 +2309,47 @@ async function runGenerateAgentsMd(options) {
|
|
|
2049
2309
|
sections.push(section);
|
|
2050
2310
|
if (missing) missingSkillIds.push(id);
|
|
2051
2311
|
}
|
|
2052
|
-
const body = renderAgentsMd({ variant, sections });
|
|
2053
2312
|
const target = path14.join(projectRoot, "AGENTS.md");
|
|
2054
|
-
await
|
|
2313
|
+
const targetExists = await fileExists(target);
|
|
2314
|
+
const fullTemplate = renderAgentsMd({ variant, sections });
|
|
2315
|
+
const managedBody = renderManagedBlockBody({ variant, sections });
|
|
2316
|
+
let outputContent;
|
|
2317
|
+
let merge;
|
|
2318
|
+
if (!targetExists) {
|
|
2319
|
+
outputContent = fullTemplate;
|
|
2320
|
+
merge = "created";
|
|
2321
|
+
} else {
|
|
2322
|
+
await backupFile(target, projectRoot);
|
|
2323
|
+
if (mode === "merge-managed") {
|
|
2324
|
+
const existing = await readFileOrNull(target) ?? "";
|
|
2325
|
+
if (hasManagedRegion2(existing, AGENTS_MD_MANAGED_ID)) {
|
|
2326
|
+
outputContent = replaceManagedRegion2(
|
|
2327
|
+
existing,
|
|
2328
|
+
AGENTS_MD_MANAGED_ID,
|
|
2329
|
+
managedBody
|
|
2330
|
+
);
|
|
2331
|
+
merge = "managed-replaced";
|
|
2332
|
+
} else {
|
|
2333
|
+
const wrapped = wrapManagedBlock(managedBody);
|
|
2334
|
+
outputContent = `${wrapped}
|
|
2335
|
+
|
|
2336
|
+
${PRECEDENCE_NOTICE}
|
|
2337
|
+
|
|
2338
|
+
${existing.trimStart()}`;
|
|
2339
|
+
merge = "managed-prepended";
|
|
2340
|
+
}
|
|
2341
|
+
} else {
|
|
2342
|
+
outputContent = fullTemplate;
|
|
2343
|
+
merge = "overwritten";
|
|
2344
|
+
}
|
|
2345
|
+
}
|
|
2346
|
+
await fs10.writeFile(target, outputContent, "utf8");
|
|
2055
2347
|
return {
|
|
2056
2348
|
path: target,
|
|
2057
2349
|
skillCount: ordered.length,
|
|
2058
|
-
missingSkillIds
|
|
2350
|
+
missingSkillIds,
|
|
2351
|
+
backedUp: targetExists,
|
|
2352
|
+
merge
|
|
2059
2353
|
};
|
|
2060
2354
|
}
|
|
2061
2355
|
function bucketRank(id) {
|
|
@@ -2065,10 +2359,7 @@ function bucketRank(id) {
|
|
|
2065
2359
|
}
|
|
2066
2360
|
async function renderSkillSection(projectRoot, skillId) {
|
|
2067
2361
|
const skillPath = path14.join(
|
|
2068
|
-
projectRoot,
|
|
2069
|
-
".teamix-evo",
|
|
2070
|
-
"skills",
|
|
2071
|
-
skillId,
|
|
2362
|
+
getSkillsSourceDir(projectRoot, skillId),
|
|
2072
2363
|
"SKILL.md"
|
|
2073
2364
|
);
|
|
2074
2365
|
const lines = [];
|
|
@@ -2093,16 +2384,25 @@ async function renderSkillSection(projectRoot, skillId) {
|
|
|
2093
2384
|
if (parts?.coordinates) {
|
|
2094
2385
|
lines.push(`- **Coordinates with**: ${parts.coordinates}`);
|
|
2095
2386
|
}
|
|
2096
|
-
lines.push(`- **\u4F4D\u7F6E**: \`.teamix-evo/skills/${skillId}/SKILL.md\``);
|
|
2387
|
+
lines.push(`- **\u4F4D\u7F6E**: \`.teamix-evo/skills-source/${skillId}/SKILL.md\``);
|
|
2097
2388
|
return { section: lines.join("\n"), missing };
|
|
2098
2389
|
}
|
|
2099
2390
|
function renderAgentsMd(args) {
|
|
2391
|
+
const { variant, sections } = args;
|
|
2392
|
+
const managedBody = renderManagedBlockBody({ variant, sections });
|
|
2393
|
+
const wrapped = wrapManagedBlock(managedBody);
|
|
2394
|
+
return `${wrapped}
|
|
2395
|
+
|
|
2396
|
+
${PRECEDENCE_NOTICE}
|
|
2397
|
+
`;
|
|
2398
|
+
}
|
|
2399
|
+
function renderManagedBlockBody(args) {
|
|
2100
2400
|
const { variant, sections } = args;
|
|
2101
2401
|
const skillBlock = sections.length > 0 ? sections.join("\n\n") : "_\uFF08\u672C\u5DE5\u7A0B\u672A\u88C5\u914D\u5DE5\u7A0B\u7EA7 skill\u3002\uFF09_";
|
|
2102
2402
|
return `# AGENTS.md
|
|
2103
2403
|
|
|
2104
2404
|
> \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
|
|
2405
|
+
> \u672C\u6587\u4EF6\u7531 \`teamix-evo init\` / \`create-teamix-evo\` \u81EA\u52A8\u751F\u6210\uFF08regenerable\uFF0C\u9075\u5FAA ADR 0038\uFF09\uFF0C\u5237\u65B0\u65B9\u5F0F\u89C1\u5E95\u90E8\u3002
|
|
2106
2406
|
|
|
2107
2407
|
## \u5DF2\u88C5 Skills\uFF08variant: ${variant}\uFF09
|
|
2108
2408
|
|
|
@@ -2115,9 +2415,22 @@ ${skillBlock}
|
|
|
2115
2415
|
- \u6A21\u7CCA\u573A\u666F\uFF1A\u5148\u6309 SKIP \u53CD\u5411\u6392\u9664\uFF0C\u5269\u4F59\u552F\u4E00 skill \u5373\u4E3A\u5165\u53E3
|
|
2116
2416
|
- \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
2417
|
|
|
2118
|
-
|
|
2119
|
-
|
|
2418
|
+
## UI \u7EC4\u4EF6\u9886\u5730\uFF08init \u540E\u7EA6\u675F\uFF09
|
|
2419
|
+
|
|
2420
|
+
- \`src/components/ui/\` \u7531 teamix-evo \u63A5\u7BA1\uFF0C\u7981\u6B62\u624B\u5DE5\u6216\u901A\u8FC7 shadcn CLI \u6DFB\u52A0\u65B0\u7EC4\u4EF6
|
|
2421
|
+
- \u5982\u9700\u65B0\u589E UI \u7EC4\u4EF6\uFF0C\u4F7F\u7528 \`npx teamix-evo ui add <id>\`
|
|
2422
|
+
- \`src/components/shadcn-ui/\` \u662F init \u524D legacy \u7EC4\u4EF6\u5F52\u6863\uFF0C\u53EA\u8BFB\uFF1B\u65B0\u4EE3\u7801\u4E0D\u5E94\u518D import
|
|
2423
|
+
- \u5347\u7EA7 legacy \u7EC4\u4EF6\uFF1A\u89E6\u53D1 teamix-evo-upgrade skill
|
|
2424
|
+
|
|
2425
|
+
> \u5237\u65B0\u672C\u6587\u4EF6\uFF1A\`npx teamix-evo skills add\` \u6216\u91CD\u8DD1 \`npm create teamix-evo\` / \`teamix-evo init\`\u3002`;
|
|
2426
|
+
}
|
|
2427
|
+
function wrapManagedBlock(body) {
|
|
2428
|
+
return `<!-- teamix-evo:managed:start id="${AGENTS_MD_MANAGED_ID}" -->
|
|
2429
|
+
${body}
|
|
2430
|
+
<!-- teamix-evo:managed:end id="${AGENTS_MD_MANAGED_ID}" -->`;
|
|
2120
2431
|
}
|
|
2432
|
+
var PRECEDENCE_NOTICE = `<!-- teamix-evo:precedence -->
|
|
2433
|
+
> \u51B2\u7A81\u4EE5\u4E0A\u65B9\u7684 **Skills** \u7D22\u5F15\u4E3A\u51C6\uFF08\u4E0A\u6E38\u8DEF\u5F84\u4E0E TRIGGER/SKIP \u5951\u7EA6\uFF09\uFF1B\u9879\u76EE\u7279\u6709\u7684\u4EBA\u5DE5\u7EC6\u5219\u8BF7\u5199\u5728\u672C\u5904\u4EE5\u4E0B\u3001\u4E0D\u8981\u8986\u76D6\u4E0A\u65B9 managed \u533A\u57DF\u3002`;
|
|
2121
2434
|
function extractDescriptionParts(fileContent) {
|
|
2122
2435
|
const description = extractDescriptionBlock(fileContent);
|
|
2123
2436
|
if (description == null) return null;
|
|
@@ -2311,6 +2624,26 @@ var INDEX_CSS_CANDIDATES = [
|
|
|
2311
2624
|
];
|
|
2312
2625
|
var SHADCN_FILE_CANDIDATES = ["src/lib/utils.ts"];
|
|
2313
2626
|
var SHADCN_DIR_CANDIDATES = ["src/components/ui"];
|
|
2627
|
+
var ESLINT_CONFIG_CANDIDATES = [
|
|
2628
|
+
".eslintrc.cjs",
|
|
2629
|
+
".eslintrc.js",
|
|
2630
|
+
".eslintrc.json",
|
|
2631
|
+
".eslintrc.yml",
|
|
2632
|
+
"eslint.config.js",
|
|
2633
|
+
"eslint.config.cjs",
|
|
2634
|
+
"eslint.config.mjs",
|
|
2635
|
+
"eslint.config.ts"
|
|
2636
|
+
];
|
|
2637
|
+
var STYLELINT_CONFIG_CANDIDATES = [
|
|
2638
|
+
".stylelintrc.cjs",
|
|
2639
|
+
".stylelintrc.js",
|
|
2640
|
+
".stylelintrc.json",
|
|
2641
|
+
".stylelintrc.yml",
|
|
2642
|
+
"stylelint.config.cjs",
|
|
2643
|
+
"stylelint.config.js",
|
|
2644
|
+
"stylelint.config.mjs",
|
|
2645
|
+
"stylelint.config.ts"
|
|
2646
|
+
];
|
|
2314
2647
|
async function isDir(target) {
|
|
2315
2648
|
try {
|
|
2316
2649
|
const stat5 = await fs12.stat(target);
|
|
@@ -2487,7 +2820,9 @@ async function detectConflicts(cwd) {
|
|
|
2487
2820
|
detectTailwindConfig(absCwd),
|
|
2488
2821
|
detectTokens(absCwd),
|
|
2489
2822
|
detectIndexCss(absCwd),
|
|
2490
|
-
detectShadcnSource(absCwd)
|
|
2823
|
+
detectShadcnSource(absCwd),
|
|
2824
|
+
detectEslintConfig(absCwd),
|
|
2825
|
+
detectStylelintConfig(absCwd)
|
|
2491
2826
|
]);
|
|
2492
2827
|
return {
|
|
2493
2828
|
cwd: absCwd,
|
|
@@ -2495,6 +2830,46 @@ async function detectConflicts(cwd) {
|
|
|
2495
2830
|
hasAnyConflict: items.some((i) => i.exists)
|
|
2496
2831
|
};
|
|
2497
2832
|
}
|
|
2833
|
+
async function detectEslintConfig(cwd) {
|
|
2834
|
+
const matched = [];
|
|
2835
|
+
const contents = [];
|
|
2836
|
+
for (const rel2 of ESLINT_CONFIG_CANDIDATES) {
|
|
2837
|
+
const c = await readFileOrNull(path16.join(cwd, rel2));
|
|
2838
|
+
if (c !== null) {
|
|
2839
|
+
matched.push(rel2);
|
|
2840
|
+
contents.push(c);
|
|
2841
|
+
}
|
|
2842
|
+
}
|
|
2843
|
+
const exists = matched.length > 0;
|
|
2844
|
+
return {
|
|
2845
|
+
key: "eslint-config",
|
|
2846
|
+
exists,
|
|
2847
|
+
paths: matched,
|
|
2848
|
+
fingerprint: exists ? fingerprint(contents) : void 0,
|
|
2849
|
+
recommendedStrategy: exists ? "merge" : "overwrite",
|
|
2850
|
+
availableStrategies: ["merge", "backup-overwrite", "overwrite", "skip"]
|
|
2851
|
+
};
|
|
2852
|
+
}
|
|
2853
|
+
async function detectStylelintConfig(cwd) {
|
|
2854
|
+
const matched = [];
|
|
2855
|
+
const contents = [];
|
|
2856
|
+
for (const rel2 of STYLELINT_CONFIG_CANDIDATES) {
|
|
2857
|
+
const c = await readFileOrNull(path16.join(cwd, rel2));
|
|
2858
|
+
if (c !== null) {
|
|
2859
|
+
matched.push(rel2);
|
|
2860
|
+
contents.push(c);
|
|
2861
|
+
}
|
|
2862
|
+
}
|
|
2863
|
+
const exists = matched.length > 0;
|
|
2864
|
+
return {
|
|
2865
|
+
key: "stylelint-config",
|
|
2866
|
+
exists,
|
|
2867
|
+
paths: matched,
|
|
2868
|
+
fingerprint: exists ? fingerprint(contents) : void 0,
|
|
2869
|
+
recommendedStrategy: exists ? "merge" : "overwrite",
|
|
2870
|
+
availableStrategies: ["merge", "backup-overwrite", "overwrite", "skip"]
|
|
2871
|
+
};
|
|
2872
|
+
}
|
|
2498
2873
|
|
|
2499
2874
|
// src/core/legacy-tokens-migrate.ts
|
|
2500
2875
|
import * as path17 from "path";
|
|
@@ -2603,92 +2978,1301 @@ async function migrateLegacyTokens(options) {
|
|
|
2603
2978
|
};
|
|
2604
2979
|
}
|
|
2605
2980
|
|
|
2606
|
-
// src/core/
|
|
2981
|
+
// src/core/ui-conflict-detector.ts
|
|
2607
2982
|
import * as fs14 from "fs/promises";
|
|
2608
2983
|
import * as path18 from "path";
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
}
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
await fs14.cp(src, dst, { recursive: true });
|
|
2984
|
+
function looksLikeShadcnOriginal(content) {
|
|
2985
|
+
if (content.includes("data-slot=")) return false;
|
|
2986
|
+
if (content.includes("@teamix-evo")) return false;
|
|
2987
|
+
if (content.includes("teamix-evo:managed")) return false;
|
|
2988
|
+
return true;
|
|
2989
|
+
}
|
|
2990
|
+
async function detectUiConflicts(options) {
|
|
2991
|
+
const { projectRoot, aliases, manifest } = options;
|
|
2992
|
+
const conflicts = [];
|
|
2993
|
+
const conflictDirSet = /* @__PURE__ */ new Set();
|
|
2994
|
+
let totalChecked = 0;
|
|
2995
|
+
for (const entry of manifest.entries) {
|
|
2996
|
+
for (const file of entry.files) {
|
|
2997
|
+
totalChecked++;
|
|
2998
|
+
const aliasDir = aliases[file.targetAlias];
|
|
2999
|
+
if (!aliasDir) continue;
|
|
3000
|
+
const targetAbs = path18.join(projectRoot, aliasDir, file.targetName);
|
|
3001
|
+
const exists = await fileExists(targetAbs);
|
|
3002
|
+
if (!exists) continue;
|
|
3003
|
+
let isShadcnOriginal = true;
|
|
3004
|
+
try {
|
|
3005
|
+
const content = await fs14.readFile(targetAbs, "utf-8");
|
|
3006
|
+
isShadcnOriginal = looksLikeShadcnOriginal(content);
|
|
3007
|
+
} catch {
|
|
3008
|
+
}
|
|
3009
|
+
const relativePath = path18.relative(projectRoot, targetAbs).replace(/\\/g, "/");
|
|
3010
|
+
conflicts.push({
|
|
3011
|
+
id: entry.id,
|
|
3012
|
+
targetPath: targetAbs,
|
|
3013
|
+
relativePath,
|
|
3014
|
+
isShadcnOriginal
|
|
3015
|
+
});
|
|
3016
|
+
conflictDirSet.add(aliasDir);
|
|
3017
|
+
}
|
|
2644
3018
|
}
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
3019
|
+
return {
|
|
3020
|
+
conflictEntries: conflicts,
|
|
3021
|
+
unconflictedTargets: totalChecked - conflicts.length,
|
|
3022
|
+
totalEntries: totalChecked,
|
|
3023
|
+
shouldBlock: conflicts.length > 0,
|
|
3024
|
+
conflictDirs: [...conflictDirSet]
|
|
2648
3025
|
};
|
|
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
3026
|
}
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
3027
|
+
|
|
3028
|
+
// src/core/ui-isolate.ts
|
|
3029
|
+
import * as fs15 from "fs/promises";
|
|
3030
|
+
import * as path19 from "path";
|
|
3031
|
+
var IGNORE_DIR_NAMES = DEFAULT_SKIP_DIRS;
|
|
3032
|
+
async function runUiIsolate(options) {
|
|
3033
|
+
const { projectRoot, aliases } = options;
|
|
3034
|
+
const componentsDir = path19.join(projectRoot, aliases.components);
|
|
3035
|
+
const legacyDir = path19.join(
|
|
3036
|
+
projectRoot,
|
|
3037
|
+
aliases.components.replace(/\/ui\/?$/, "/shadcn-ui")
|
|
3038
|
+
);
|
|
3039
|
+
const movedFiles = [];
|
|
3040
|
+
const backedUpFiles = [];
|
|
3041
|
+
let componentsJsonRemoved = false;
|
|
3042
|
+
if (await fileExists(componentsDir)) {
|
|
3043
|
+
await ensureDir(legacyDir);
|
|
3044
|
+
const UI_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
3045
|
+
".ts",
|
|
3046
|
+
".tsx",
|
|
3047
|
+
".js",
|
|
3048
|
+
".jsx",
|
|
3049
|
+
".css",
|
|
3050
|
+
".json",
|
|
3051
|
+
".md",
|
|
3052
|
+
".mdx"
|
|
3053
|
+
]);
|
|
3054
|
+
const allUiFiles = await walkDir(componentsDir);
|
|
3055
|
+
const uiFiles = allUiFiles.filter(
|
|
3056
|
+
(f) => UI_EXTENSIONS.has(path19.extname(f))
|
|
3057
|
+
);
|
|
3058
|
+
for (const srcFile of uiFiles) {
|
|
3059
|
+
const relFromUi = path19.relative(componentsDir, srcFile);
|
|
3060
|
+
const destFile = path19.join(legacyDir, relFromUi);
|
|
3061
|
+
await ensureDir(path19.dirname(destFile));
|
|
3062
|
+
const content = await fs15.readFile(srcFile, "utf-8");
|
|
3063
|
+
await fs15.writeFile(destFile, content, "utf-8");
|
|
3064
|
+
try {
|
|
3065
|
+
await fs15.unlink(srcFile);
|
|
3066
|
+
} catch {
|
|
3067
|
+
logger.warn(`Could not remove: ${srcFile}`);
|
|
3068
|
+
}
|
|
3069
|
+
const fromRel = path19.relative(projectRoot, srcFile).replace(/\\/g, "/");
|
|
3070
|
+
const toRel = path19.relative(projectRoot, destFile).replace(/\\/g, "/");
|
|
3071
|
+
movedFiles.push({ from: fromRel, to: toRel });
|
|
3072
|
+
}
|
|
3073
|
+
await pruneEmptyDirs(componentsDir);
|
|
3074
|
+
logger.info(
|
|
3075
|
+
` moved ${movedFiles.length} files \u2192 ${path19.relative(
|
|
3076
|
+
projectRoot,
|
|
3077
|
+
legacyDir
|
|
3078
|
+
)}/`
|
|
3079
|
+
);
|
|
2669
3080
|
}
|
|
2670
|
-
const
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
const
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
const
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
3081
|
+
const importRewrites = /* @__PURE__ */ new Map();
|
|
3082
|
+
const srcDir = path19.join(projectRoot, "src");
|
|
3083
|
+
if (await fileExists(srcDir)) {
|
|
3084
|
+
const SCAN_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mdx"]);
|
|
3085
|
+
const allScanFiles = await walkDir(srcDir, DEFAULT_SKIP_DIRS);
|
|
3086
|
+
const scanFiles = allScanFiles.filter((f) => {
|
|
3087
|
+
const rel2 = path19.relative(projectRoot, f);
|
|
3088
|
+
const segments = rel2.split(path19.sep);
|
|
3089
|
+
if (segments.some((s) => IGNORE_DIR_NAMES.has(s))) return false;
|
|
3090
|
+
return SCAN_EXTENSIONS.has(path19.extname(f));
|
|
3091
|
+
});
|
|
3092
|
+
const oldAlias = aliases.components;
|
|
3093
|
+
const newAlias = oldAlias.replace(/\/ui\/?$/, "/shadcn-ui");
|
|
3094
|
+
const oldWithoutSrc = oldAlias.replace(/^src\//, "");
|
|
3095
|
+
const newWithoutSrc = newAlias.replace(/^src\//, "");
|
|
3096
|
+
const dirName = path19.basename(oldAlias);
|
|
3097
|
+
const newDirName = path19.basename(newAlias);
|
|
3098
|
+
for (const file of scanFiles) {
|
|
3099
|
+
const content = await fs15.readFile(file, "utf-8");
|
|
3100
|
+
let modified = content;
|
|
3101
|
+
let count = 0;
|
|
3102
|
+
modified = modified.replace(
|
|
3103
|
+
new RegExp(`(['"'\`])@/${escapeRegExp2(oldWithoutSrc)}/`, "g"),
|
|
3104
|
+
(match, quote) => {
|
|
3105
|
+
count++;
|
|
3106
|
+
return `${quote}@/${newWithoutSrc}/`;
|
|
3107
|
+
}
|
|
3108
|
+
);
|
|
3109
|
+
modified = modified.replace(
|
|
3110
|
+
new RegExp(`(['"'\`])~/${escapeRegExp2(oldWithoutSrc)}/`, "g"),
|
|
3111
|
+
(match, quote) => {
|
|
3112
|
+
count++;
|
|
3113
|
+
return `${quote}~/${newWithoutSrc}/`;
|
|
3114
|
+
}
|
|
3115
|
+
);
|
|
3116
|
+
modified = modified.replace(
|
|
3117
|
+
new RegExp(
|
|
3118
|
+
`(from\\s+['"'\`](?:\\.\\.?\\/)+(?:[\\w.-]+\\/)*)${escapeRegExp2(
|
|
3119
|
+
dirName
|
|
3120
|
+
)}/`,
|
|
3121
|
+
"g"
|
|
3122
|
+
),
|
|
3123
|
+
(match) => {
|
|
3124
|
+
count++;
|
|
3125
|
+
return match.slice(0, -dirName.length - 1) + `${newDirName}/`;
|
|
3126
|
+
}
|
|
3127
|
+
);
|
|
3128
|
+
if (count > 0) {
|
|
3129
|
+
await fs15.writeFile(file, modified, "utf-8");
|
|
3130
|
+
const relPath = path19.relative(projectRoot, file).replace(/\\/g, "/");
|
|
3131
|
+
importRewrites.set(relPath, count);
|
|
2684
3132
|
}
|
|
3133
|
+
}
|
|
3134
|
+
logger.info(` rewrote imports in ${importRewrites.size} files`);
|
|
3135
|
+
}
|
|
3136
|
+
const componentsJsonPath = path19.join(projectRoot, "components.json");
|
|
3137
|
+
if (await fileExists(componentsJsonPath)) {
|
|
3138
|
+
await backupFile(componentsJsonPath, projectRoot);
|
|
3139
|
+
backedUpFiles.push("components.json");
|
|
3140
|
+
try {
|
|
3141
|
+
await fs15.unlink(componentsJsonPath);
|
|
3142
|
+
componentsJsonRemoved = true;
|
|
3143
|
+
logger.info(" backed up and removed components.json");
|
|
2685
3144
|
} catch {
|
|
2686
|
-
|
|
3145
|
+
logger.warn(" could not remove components.json");
|
|
2687
3146
|
}
|
|
2688
|
-
result.push({ ts: entry.name, isoTs, reason, path: dir });
|
|
2689
3147
|
}
|
|
2690
|
-
|
|
2691
|
-
|
|
3148
|
+
const libBackupTargets = [
|
|
3149
|
+
path19.join(projectRoot, aliases.utils + ".ts"),
|
|
3150
|
+
path19.join(projectRoot, aliases.lib, "color.ts")
|
|
3151
|
+
];
|
|
3152
|
+
const hooksDir = path19.join(projectRoot, aliases.hooks);
|
|
3153
|
+
if (await fileExists(hooksDir)) {
|
|
3154
|
+
const allHookFiles = await walkDir(hooksDir, DEFAULT_SKIP_DIRS);
|
|
3155
|
+
const hookFiles = allHookFiles.filter(
|
|
3156
|
+
(f) => path19.extname(f) === ".ts" || path19.extname(f) === ".tsx"
|
|
3157
|
+
);
|
|
3158
|
+
libBackupTargets.push(...hookFiles);
|
|
3159
|
+
}
|
|
3160
|
+
for (const target of libBackupTargets) {
|
|
3161
|
+
if (await fileExists(target)) {
|
|
3162
|
+
await backupFile(target, projectRoot);
|
|
3163
|
+
backedUpFiles.push(
|
|
3164
|
+
path19.relative(projectRoot, target).replace(/\\/g, "/")
|
|
3165
|
+
);
|
|
3166
|
+
}
|
|
3167
|
+
}
|
|
3168
|
+
return {
|
|
3169
|
+
movedFiles,
|
|
3170
|
+
importRewrites,
|
|
3171
|
+
componentsJsonRemoved,
|
|
3172
|
+
backedUpFiles
|
|
3173
|
+
};
|
|
3174
|
+
}
|
|
3175
|
+
function escapeRegExp2(s) {
|
|
3176
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3177
|
+
}
|
|
3178
|
+
async function pruneEmptyDirs(dir) {
|
|
3179
|
+
try {
|
|
3180
|
+
const entries = await fs15.readdir(dir, { withFileTypes: true });
|
|
3181
|
+
for (const entry of entries) {
|
|
3182
|
+
if (entry.isDirectory()) {
|
|
3183
|
+
await pruneEmptyDirs(path19.join(dir, entry.name));
|
|
3184
|
+
}
|
|
3185
|
+
}
|
|
3186
|
+
const remaining = await fs15.readdir(dir);
|
|
3187
|
+
if (remaining.length === 0) {
|
|
3188
|
+
await fs15.rmdir(dir);
|
|
3189
|
+
}
|
|
3190
|
+
} catch {
|
|
3191
|
+
}
|
|
3192
|
+
}
|
|
3193
|
+
|
|
3194
|
+
// src/core/ui-upgrade.ts
|
|
3195
|
+
import * as path22 from "path";
|
|
3196
|
+
import { createRequire as createRequire5 } from "module";
|
|
3197
|
+
import {
|
|
3198
|
+
loadUiPackageManifest as loadUiPackageManifest3,
|
|
3199
|
+
loadVariantUiPackageManifest as loadVariantUiPackageManifest2
|
|
3200
|
+
} from "@teamix-evo/registry";
|
|
3201
|
+
|
|
3202
|
+
// src/core/ui-upgrade-detector.ts
|
|
3203
|
+
import * as fs16 from "fs/promises";
|
|
3204
|
+
import * as path20 from "path";
|
|
3205
|
+
var PACKAGE_NAME = {
|
|
3206
|
+
ui: "@teamix-evo/ui",
|
|
3207
|
+
"biz-ui": "@teamix-evo/biz-ui"
|
|
3208
|
+
};
|
|
3209
|
+
var ALIAS_KEY = {
|
|
3210
|
+
ui: "components",
|
|
3211
|
+
"biz-ui": "business"
|
|
3212
|
+
};
|
|
3213
|
+
var COMPONENT_FILE_RE = /\.(tsx|ts)$/;
|
|
3214
|
+
var SKIP_FILENAMES = /* @__PURE__ */ new Set(["index.ts", "index.tsx"]);
|
|
3215
|
+
async function detectComponentLineage(options) {
|
|
3216
|
+
const { projectRoot, category } = options;
|
|
3217
|
+
const config = options.config ?? await readProjectConfig(projectRoot);
|
|
3218
|
+
const installed = options.installed ?? await readInstalledManifest(projectRoot);
|
|
3219
|
+
const installDir = resolveInstallDir(category, config);
|
|
3220
|
+
const installDirAbs = path20.join(projectRoot, installDir);
|
|
3221
|
+
const installDirExists = await directoryExists(installDirAbs);
|
|
3222
|
+
const hasComponentsJson = await fileExists(
|
|
3223
|
+
path20.join(projectRoot, "components.json")
|
|
3224
|
+
);
|
|
3225
|
+
const installedPkg = findInstalledPackage(installed, PACKAGE_NAME[category]);
|
|
3226
|
+
const registeredIds = installedPkg ? extractIds(installedPkg).sort() : [];
|
|
3227
|
+
const onDiskIds = installDirExists ? await listComponentIds(installDirAbs) : [];
|
|
3228
|
+
const registeredSet = new Set(registeredIds);
|
|
3229
|
+
const unregisteredIds = onDiskIds.filter((id) => !registeredSet.has(id)).sort();
|
|
3230
|
+
const lineage = classifyLineage({
|
|
3231
|
+
hasInstalled: installedPkg !== null,
|
|
3232
|
+
hasComponentsJson,
|
|
3233
|
+
onDiskIds,
|
|
3234
|
+
unregisteredIds
|
|
3235
|
+
});
|
|
3236
|
+
return {
|
|
3237
|
+
category,
|
|
3238
|
+
lineage,
|
|
3239
|
+
installDir,
|
|
3240
|
+
installDirExists,
|
|
3241
|
+
hasComponentsJson,
|
|
3242
|
+
registeredIds,
|
|
3243
|
+
unregisteredIds,
|
|
3244
|
+
installedVersion: installedPkg?.version ?? null,
|
|
3245
|
+
installedVariant: installedPkg?.variant ?? null
|
|
3246
|
+
};
|
|
3247
|
+
}
|
|
3248
|
+
function resolveInstallDir(category, config) {
|
|
3249
|
+
const aliasMap = config?.packages?.ui?.aliases ?? config?.packages?.["biz-ui"]?.aliases ?? DEFAULT_UI_ALIASES;
|
|
3250
|
+
const key = ALIAS_KEY[category];
|
|
3251
|
+
return aliasMap[key] ?? DEFAULT_UI_ALIASES[key];
|
|
3252
|
+
}
|
|
3253
|
+
function extractIds(pkg) {
|
|
3254
|
+
const ids = /* @__PURE__ */ new Set();
|
|
3255
|
+
for (const r of pkg.resources) {
|
|
3256
|
+
const colon = r.id.indexOf(":");
|
|
3257
|
+
ids.add(colon >= 0 ? r.id.slice(0, colon) : r.id);
|
|
3258
|
+
}
|
|
3259
|
+
return [...ids];
|
|
3260
|
+
}
|
|
3261
|
+
async function directoryExists(p) {
|
|
3262
|
+
try {
|
|
3263
|
+
const stat5 = await fs16.stat(p);
|
|
3264
|
+
return stat5.isDirectory();
|
|
3265
|
+
} catch {
|
|
3266
|
+
return false;
|
|
3267
|
+
}
|
|
3268
|
+
}
|
|
3269
|
+
async function listComponentIds(installDirAbs) {
|
|
3270
|
+
const entries = await fs16.readdir(installDirAbs, { withFileTypes: true });
|
|
3271
|
+
const ids = [];
|
|
3272
|
+
for (const e of entries) {
|
|
3273
|
+
if (!e.isFile()) continue;
|
|
3274
|
+
if (SKIP_FILENAMES.has(e.name)) continue;
|
|
3275
|
+
if (!COMPONENT_FILE_RE.test(e.name)) continue;
|
|
3276
|
+
ids.push(e.name.replace(COMPONENT_FILE_RE, ""));
|
|
3277
|
+
}
|
|
3278
|
+
return ids.sort();
|
|
3279
|
+
}
|
|
3280
|
+
function classifyLineage(args) {
|
|
3281
|
+
const { hasInstalled, hasComponentsJson, onDiskIds, unregisteredIds } = args;
|
|
3282
|
+
if (hasInstalled) {
|
|
3283
|
+
return unregisteredIds.length === 0 ? "teamix-evo" : "mixed";
|
|
3284
|
+
}
|
|
3285
|
+
if (onDiskIds.length === 0) return "absent";
|
|
3286
|
+
return hasComponentsJson ? "shadcn-native" : "custom-only";
|
|
3287
|
+
}
|
|
3288
|
+
|
|
3289
|
+
// src/core/ui-upgrade-staging.ts
|
|
3290
|
+
import * as path21 from "path";
|
|
3291
|
+
var TEAMIX_DIR2 = ".teamix-evo";
|
|
3292
|
+
var STAGING_DIR = ".upgrade-staging";
|
|
3293
|
+
var PACKAGE_NAME2 = {
|
|
3294
|
+
ui: "@teamix-evo/ui",
|
|
3295
|
+
"biz-ui": "@teamix-evo/biz-ui"
|
|
3296
|
+
};
|
|
3297
|
+
function isoToFsSafe(iso) {
|
|
3298
|
+
return iso.replace(/[:.]/g, "-");
|
|
3299
|
+
}
|
|
3300
|
+
async function buildUiUpgradeStaging(options) {
|
|
3301
|
+
const { lineageReport, category } = options;
|
|
3302
|
+
if (lineageReport.lineage !== "teamix-evo" && lineageReport.lineage !== "mixed") {
|
|
3303
|
+
return null;
|
|
3304
|
+
}
|
|
3305
|
+
const installed = options.installed ?? await readInstalledManifest(options.projectRoot);
|
|
3306
|
+
const installedPkg = findInstalledPackage(installed, PACKAGE_NAME2[category]);
|
|
3307
|
+
if (!installedPkg) return null;
|
|
3308
|
+
const isoTs = options.isoTs ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
3309
|
+
const fsTs = isoToFsSafe(isoTs);
|
|
3310
|
+
const stagingDir = path21.join(
|
|
3311
|
+
options.projectRoot,
|
|
3312
|
+
TEAMIX_DIR2,
|
|
3313
|
+
STAGING_DIR,
|
|
3314
|
+
`${category}-${fsTs}`
|
|
3315
|
+
);
|
|
3316
|
+
const entryMap = new Map(
|
|
3317
|
+
options.manifest.entries.map((e) => [e.id, e])
|
|
3318
|
+
);
|
|
3319
|
+
const resByEntryId = collectResourcesByEntry(installedPkg.resources);
|
|
3320
|
+
const onlyIds = options.onlyIds && options.onlyIds.length > 0 ? new Set(options.onlyIds) : null;
|
|
3321
|
+
const entries = [];
|
|
3322
|
+
for (const id of lineageReport.registeredIds) {
|
|
3323
|
+
if (onlyIds && !onlyIds.has(id)) continue;
|
|
3324
|
+
const built = await processRegistered({
|
|
3325
|
+
id,
|
|
3326
|
+
entry: entryMap.get(id),
|
|
3327
|
+
resource: resByEntryId.get(id),
|
|
3328
|
+
packageRoot: options.packageRoot,
|
|
3329
|
+
entryPackageRoot: options.entryPackageRoot,
|
|
3330
|
+
aliases: options.aliases,
|
|
3331
|
+
stagingDir,
|
|
3332
|
+
projectRoot: options.projectRoot,
|
|
3333
|
+
category,
|
|
3334
|
+
sourceVersion: options.manifest.version
|
|
3335
|
+
});
|
|
3336
|
+
if (built) entries.push(built);
|
|
3337
|
+
}
|
|
3338
|
+
for (const id of lineageReport.unregisteredIds) {
|
|
3339
|
+
if (onlyIds && !onlyIds.has(id)) continue;
|
|
3340
|
+
const built = await processForeign({
|
|
3341
|
+
id,
|
|
3342
|
+
installDirAbs: path21.join(options.projectRoot, lineageReport.installDir),
|
|
3343
|
+
stagingDir,
|
|
3344
|
+
projectRoot: options.projectRoot,
|
|
3345
|
+
category
|
|
3346
|
+
});
|
|
3347
|
+
if (built) entries.push(built);
|
|
3348
|
+
}
|
|
3349
|
+
if (entries.length === 0) return null;
|
|
3350
|
+
const byRisk = aggregateByRisk(entries);
|
|
3351
|
+
const manifestOut = {
|
|
3352
|
+
schemaVersion: 1,
|
|
3353
|
+
ts: isoTs,
|
|
3354
|
+
package: category,
|
|
3355
|
+
trigger: options.trigger,
|
|
3356
|
+
variant: lineageReport.installedVariant ?? "_flat",
|
|
3357
|
+
fromVersion: lineageReport.installedVersion ?? "",
|
|
3358
|
+
toVersion: options.manifest.version,
|
|
3359
|
+
lineage: lineageReport.lineage,
|
|
3360
|
+
summary: { total: entries.length, byRisk },
|
|
3361
|
+
entries
|
|
3362
|
+
};
|
|
3363
|
+
await ensureDir(stagingDir);
|
|
3364
|
+
await writeFileSafe(
|
|
3365
|
+
path21.join(stagingDir, "meta.json"),
|
|
3366
|
+
JSON.stringify(manifestOut, null, 2) + "\n"
|
|
3367
|
+
);
|
|
3368
|
+
return { stagingDir, manifest: manifestOut };
|
|
3369
|
+
}
|
|
3370
|
+
async function processRegistered(args) {
|
|
3371
|
+
const { id, entry, resource, stagingDir, projectRoot, category } = args;
|
|
3372
|
+
if (!resource) return null;
|
|
3373
|
+
const currentSource = await readFileOrNull(resource.target);
|
|
3374
|
+
if (currentSource === null) {
|
|
3375
|
+
return buildBreakingEntry({
|
|
3376
|
+
id,
|
|
3377
|
+
category,
|
|
3378
|
+
resource,
|
|
3379
|
+
projectRoot,
|
|
3380
|
+
stagingDir,
|
|
3381
|
+
currentSource: "",
|
|
3382
|
+
hint: "installed file missing on disk"
|
|
3383
|
+
});
|
|
3384
|
+
}
|
|
3385
|
+
if (!entry) {
|
|
3386
|
+
return buildBreakingEntry({
|
|
3387
|
+
id,
|
|
3388
|
+
category,
|
|
3389
|
+
resource,
|
|
3390
|
+
projectRoot,
|
|
3391
|
+
stagingDir,
|
|
3392
|
+
currentSource,
|
|
3393
|
+
hint: "entry removed in upstream package"
|
|
3394
|
+
});
|
|
3395
|
+
}
|
|
3396
|
+
const file = entry.files[0];
|
|
3397
|
+
if (!file) return null;
|
|
3398
|
+
const rootForEntry = args.entryPackageRoot?.get(id) ?? args.packageRoot;
|
|
3399
|
+
const sourceAbs = path21.resolve(rootForEntry, file.source);
|
|
3400
|
+
const raw = await readFileOrNull(sourceAbs);
|
|
3401
|
+
if (raw === null) {
|
|
3402
|
+
return null;
|
|
3403
|
+
}
|
|
3404
|
+
const incomingTransformed = rewriteImports(raw, args.aliases);
|
|
3405
|
+
const incomingHash = computeHash(incomingTransformed);
|
|
3406
|
+
const currentExt = path21.extname(resource.target) || ".tsx";
|
|
3407
|
+
const incomingExt = path21.extname(file.targetName) || currentExt;
|
|
3408
|
+
const currentRel = `${id}/current${currentExt}`;
|
|
3409
|
+
const incomingRel = `${id}/incoming${incomingExt}`;
|
|
3410
|
+
await writeFileSafe(path21.join(stagingDir, currentRel), currentSource);
|
|
3411
|
+
await writeFileSafe(path21.join(stagingDir, incomingRel), incomingTransformed);
|
|
3412
|
+
const diff = classifyRisk({
|
|
3413
|
+
currentHash: resource.hash,
|
|
3414
|
+
incomingHash,
|
|
3415
|
+
currentSource,
|
|
3416
|
+
incomingSource: incomingTransformed,
|
|
3417
|
+
multiFile: entry.files.length > 1
|
|
3418
|
+
});
|
|
3419
|
+
const promotion = derivePromotion({
|
|
3420
|
+
currentSource,
|
|
3421
|
+
incomingSource: incomingTransformed,
|
|
3422
|
+
targetName: entry.files[0]?.targetName ?? `${id}.tsx`
|
|
3423
|
+
});
|
|
3424
|
+
return {
|
|
3425
|
+
id,
|
|
3426
|
+
category,
|
|
3427
|
+
current: {
|
|
3428
|
+
target: path21.relative(projectRoot, resource.target),
|
|
3429
|
+
hash: resource.hash,
|
|
3430
|
+
sourceLineage: "teamix-evo"
|
|
3431
|
+
},
|
|
3432
|
+
incoming: {
|
|
3433
|
+
sourceVersion: args.sourceVersion,
|
|
3434
|
+
hash: incomingHash,
|
|
3435
|
+
relPath: incomingRel
|
|
3436
|
+
},
|
|
3437
|
+
diff,
|
|
3438
|
+
promotion
|
|
3439
|
+
};
|
|
3440
|
+
}
|
|
3441
|
+
async function processForeign(args) {
|
|
3442
|
+
const { id, installDirAbs, stagingDir, projectRoot, category } = args;
|
|
3443
|
+
const tsx = path21.join(installDirAbs, `${id}.tsx`);
|
|
3444
|
+
const ts = path21.join(installDirAbs, `${id}.ts`);
|
|
3445
|
+
const target = await fileExists(tsx) ? tsx : await fileExists(ts) ? ts : null;
|
|
3446
|
+
if (!target) return null;
|
|
3447
|
+
const raw = await readFileOrNull(target);
|
|
3448
|
+
if (raw === null) return null;
|
|
3449
|
+
const ext = path21.extname(target);
|
|
3450
|
+
const currentRel = `${id}/current${ext}`;
|
|
3451
|
+
await writeFileSafe(path21.join(stagingDir, currentRel), raw);
|
|
3452
|
+
return {
|
|
3453
|
+
id,
|
|
3454
|
+
category,
|
|
3455
|
+
current: {
|
|
3456
|
+
target: path21.relative(projectRoot, target),
|
|
3457
|
+
hash: computeHash(raw),
|
|
3458
|
+
sourceLineage: "custom"
|
|
3459
|
+
},
|
|
3460
|
+
diff: {
|
|
3461
|
+
riskLevel: "foreign",
|
|
3462
|
+
hints: [
|
|
3463
|
+
"component is on disk but not registered in .teamix-evo/manifest.json",
|
|
3464
|
+
"AI should propose: (a) ignore, (b) re-register via teamix-evo ui add, or (c) remove"
|
|
3465
|
+
],
|
|
3466
|
+
filesChangedCount: 0
|
|
3467
|
+
}
|
|
3468
|
+
};
|
|
3469
|
+
}
|
|
3470
|
+
async function buildBreakingEntry(args) {
|
|
3471
|
+
const ext = path21.extname(args.resource.target) || ".tsx";
|
|
3472
|
+
const currentRel = `${args.id}/current${ext}`;
|
|
3473
|
+
await writeFileSafe(
|
|
3474
|
+
path21.join(args.stagingDir, currentRel),
|
|
3475
|
+
args.currentSource
|
|
3476
|
+
);
|
|
3477
|
+
return {
|
|
3478
|
+
id: args.id,
|
|
3479
|
+
category: args.category,
|
|
3480
|
+
current: {
|
|
3481
|
+
target: path21.relative(args.projectRoot, args.resource.target),
|
|
3482
|
+
hash: args.resource.hash,
|
|
3483
|
+
sourceLineage: "teamix-evo"
|
|
3484
|
+
},
|
|
3485
|
+
diff: {
|
|
3486
|
+
riskLevel: "breaking",
|
|
3487
|
+
hints: [args.hint],
|
|
3488
|
+
filesChangedCount: 0
|
|
3489
|
+
}
|
|
3490
|
+
};
|
|
3491
|
+
}
|
|
3492
|
+
function classifyRisk(args) {
|
|
3493
|
+
if (args.currentHash === args.incomingHash) {
|
|
3494
|
+
return { riskLevel: "unchanged", hints: [], filesChangedCount: 0 };
|
|
3495
|
+
}
|
|
3496
|
+
const curExports = extractExportNames(args.currentSource);
|
|
3497
|
+
const newExports = extractExportNames(args.incomingSource);
|
|
3498
|
+
const removedExports = setDiff(curExports, newExports);
|
|
3499
|
+
const addedExports = setDiff(newExports, curExports);
|
|
3500
|
+
const curVariants = extractCvaVariantValues(args.currentSource);
|
|
3501
|
+
const newVariants = extractCvaVariantValues(args.incomingSource);
|
|
3502
|
+
const removedVariants = setDiff(curVariants, newVariants);
|
|
3503
|
+
const addedVariants = setDiff(newVariants, curVariants);
|
|
3504
|
+
const hints = [];
|
|
3505
|
+
for (const e of removedExports) hints.push(`removed export: ${e}`);
|
|
3506
|
+
for (const e of addedExports) hints.push(`new export: ${e}`);
|
|
3507
|
+
for (const v of removedVariants) hints.push(`removed cva variant: ${v}`);
|
|
3508
|
+
for (const v of addedVariants) hints.push(`new cva variant: ${v}`);
|
|
3509
|
+
if (args.multiFile) hints.push("multi-file entry; only first file staged");
|
|
3510
|
+
let riskLevel;
|
|
3511
|
+
if (removedExports.length > 0 || removedVariants.length > 0) {
|
|
3512
|
+
riskLevel = "risky";
|
|
3513
|
+
} else if (addedExports.length > 0 || addedVariants.length > 0 || args.multiFile) {
|
|
3514
|
+
riskLevel = "upgradable-medium";
|
|
3515
|
+
} else {
|
|
3516
|
+
riskLevel = "upgradable-low";
|
|
3517
|
+
}
|
|
3518
|
+
return { riskLevel, hints, filesChangedCount: 1 };
|
|
3519
|
+
}
|
|
3520
|
+
function extractExportNames(src) {
|
|
3521
|
+
const names = /* @__PURE__ */ new Set();
|
|
3522
|
+
const re = /^\s*export\s+(?:default\s+)?(?:async\s+)?(?:const|let|var|function|class|interface|type|enum)\s+(\w+)/gm;
|
|
3523
|
+
let m;
|
|
3524
|
+
while ((m = re.exec(src)) !== null) {
|
|
3525
|
+
if (m[1]) names.add(m[1]);
|
|
3526
|
+
}
|
|
3527
|
+
for (const dm of src.matchAll(/^\s*export\s+default\s+(\w+)\s*;/gm)) {
|
|
3528
|
+
if (dm[1]) names.add(dm[1]);
|
|
3529
|
+
}
|
|
3530
|
+
return [...names];
|
|
3531
|
+
}
|
|
3532
|
+
function extractCvaVariantValues(src) {
|
|
3533
|
+
const block = extractVariantsBlock(src);
|
|
3534
|
+
if (block === null) return [];
|
|
3535
|
+
const names = /* @__PURE__ */ new Set();
|
|
3536
|
+
for (const groupBody of extractGroupBodies(block)) {
|
|
3537
|
+
for (const km of groupBody.matchAll(/^\s*(?:['"]?)(\w+)(?:['"]?)\s*:/gm)) {
|
|
3538
|
+
if (km[1]) names.add(km[1]);
|
|
3539
|
+
}
|
|
3540
|
+
}
|
|
3541
|
+
return [...names];
|
|
3542
|
+
}
|
|
3543
|
+
function extractVariantsBlock(src) {
|
|
3544
|
+
const idx = src.search(/\bvariants\s*:\s*\{/);
|
|
3545
|
+
if (idx < 0) return null;
|
|
3546
|
+
const open = src.indexOf("{", idx);
|
|
3547
|
+
if (open < 0) return null;
|
|
3548
|
+
let depth = 0;
|
|
3549
|
+
for (let i = open; i < src.length; i++) {
|
|
3550
|
+
const c = src[i];
|
|
3551
|
+
if (c === "{") depth++;
|
|
3552
|
+
else if (c === "}") {
|
|
3553
|
+
depth--;
|
|
3554
|
+
if (depth === 0) return src.slice(open + 1, i);
|
|
3555
|
+
}
|
|
3556
|
+
}
|
|
3557
|
+
return null;
|
|
3558
|
+
}
|
|
3559
|
+
function* extractGroupBodies(block) {
|
|
3560
|
+
const re = /(\w+)\s*:\s*\{/g;
|
|
3561
|
+
let m;
|
|
3562
|
+
while ((m = re.exec(block)) !== null) {
|
|
3563
|
+
const open = block.indexOf("{", m.index);
|
|
3564
|
+
if (open < 0) continue;
|
|
3565
|
+
let depth = 0;
|
|
3566
|
+
for (let i = open; i < block.length; i++) {
|
|
3567
|
+
const c = block[i];
|
|
3568
|
+
if (c === "{") depth++;
|
|
3569
|
+
else if (c === "}") {
|
|
3570
|
+
depth--;
|
|
3571
|
+
if (depth === 0) {
|
|
3572
|
+
yield block.slice(open + 1, i);
|
|
3573
|
+
re.lastIndex = i + 1;
|
|
3574
|
+
break;
|
|
3575
|
+
}
|
|
3576
|
+
}
|
|
3577
|
+
}
|
|
3578
|
+
}
|
|
3579
|
+
}
|
|
3580
|
+
function setDiff(a, b) {
|
|
3581
|
+
const bset = new Set(b);
|
|
3582
|
+
return a.filter((x) => !bset.has(x)).sort();
|
|
3583
|
+
}
|
|
3584
|
+
function collectResourcesByEntry(resources) {
|
|
3585
|
+
const out = /* @__PURE__ */ new Map();
|
|
3586
|
+
for (const r of resources) {
|
|
3587
|
+
const colon = r.id.indexOf(":");
|
|
3588
|
+
const eid = colon >= 0 ? r.id.slice(0, colon) : r.id;
|
|
3589
|
+
if (!out.has(eid)) out.set(eid, r);
|
|
3590
|
+
}
|
|
3591
|
+
return out;
|
|
3592
|
+
}
|
|
3593
|
+
function aggregateByRisk(entries) {
|
|
3594
|
+
const out = {};
|
|
3595
|
+
for (const e of entries) {
|
|
3596
|
+
const k = e.diff.riskLevel;
|
|
3597
|
+
out[k] = (out[k] ?? 0) + 1;
|
|
3598
|
+
}
|
|
3599
|
+
return out;
|
|
3600
|
+
}
|
|
3601
|
+
function derivePromotion(args) {
|
|
3602
|
+
const fileType = classifyPromoteFileType(args.targetName, args.currentSource);
|
|
3603
|
+
const featureVector = buildFeatureVector(
|
|
3604
|
+
args.currentSource,
|
|
3605
|
+
args.incomingSource
|
|
3606
|
+
);
|
|
3607
|
+
const { recommendedModes, confidence, reasons } = scorePromotionModes(
|
|
3608
|
+
fileType,
|
|
3609
|
+
featureVector
|
|
3610
|
+
);
|
|
3611
|
+
return { fileType, featureVector, recommendedModes, confidence, reasons };
|
|
3612
|
+
}
|
|
3613
|
+
function classifyPromoteFileType(targetName, src) {
|
|
3614
|
+
if (targetName.endsWith(".d.ts")) return "type";
|
|
3615
|
+
if (/^use-[a-z0-9-]+\.tsx?$/i.test(targetName)) return "hook";
|
|
3616
|
+
const hasJsx = /<[A-Za-z][^>]*?>/.test(src);
|
|
3617
|
+
const hasReactImport = /from ['"]react['"]/.test(src);
|
|
3618
|
+
const hasProvider = /\.Provider\b/.test(src) || /createContext\s*[<(]/.test(src);
|
|
3619
|
+
if (hasProvider && (hasJsx || hasReactImport)) return "provider";
|
|
3620
|
+
if (hasJsx || /forwardRef\s*[<(]/.test(src)) return "component";
|
|
3621
|
+
if (!hasJsx && !hasReactImport) return "util";
|
|
3622
|
+
return "component";
|
|
3623
|
+
}
|
|
3624
|
+
function buildFeatureVector(current, incoming) {
|
|
3625
|
+
const curExports = extractExportNames(current);
|
|
3626
|
+
const newExports = extractExportNames(incoming);
|
|
3627
|
+
const apiAdded = setDiff(curExports, newExports);
|
|
3628
|
+
const apiRemoved = setDiff(newExports, curExports);
|
|
3629
|
+
const curVariants = extractCvaVariantValues(current);
|
|
3630
|
+
const newVariants = extractCvaVariantValues(incoming);
|
|
3631
|
+
const cvaAdded = setDiff(curVariants, newVariants);
|
|
3632
|
+
const cvaModified = [];
|
|
3633
|
+
const sharedVariants = curVariants.filter((v) => newVariants.includes(v));
|
|
3634
|
+
for (const v of sharedVariants) {
|
|
3635
|
+
if (extractVariantBody(current, v) !== extractVariantBody(incoming, v)) {
|
|
3636
|
+
cvaModified.push(v);
|
|
3637
|
+
}
|
|
3638
|
+
}
|
|
3639
|
+
const curClass = extractClassNameLiterals(current);
|
|
3640
|
+
const newClass = extractClassNameLiterals(incoming);
|
|
3641
|
+
const classNameDiff = curClass !== newClass;
|
|
3642
|
+
const curTokens = extractTokenRefs(current);
|
|
3643
|
+
const newTokens = extractTokenRefs(incoming);
|
|
3644
|
+
const tokenUsageDiff = curTokens.size !== newTokens.size || [...curTokens].some((t) => !newTokens.has(t));
|
|
3645
|
+
const hasState = /\buseState\s*[<(]/.test(current);
|
|
3646
|
+
const hasEffect = /\b(useEffect|useLayoutEffect|useMemo|useCallback)\s*[<(]/.test(current);
|
|
3647
|
+
const curImports = extractImportSources(current);
|
|
3648
|
+
const newImports = extractImportSources(incoming);
|
|
3649
|
+
const hasExtraImports = [...curImports].some((src) => !newImports.has(src));
|
|
3650
|
+
const tagSet = /* @__PURE__ */ new Set();
|
|
3651
|
+
for (const m of current.matchAll(/<([A-Z]\w+)[\s/>]/g)) {
|
|
3652
|
+
if (m[1]) tagSet.add(m[1]);
|
|
3653
|
+
}
|
|
3654
|
+
const atomicChildren = [...tagSet];
|
|
3655
|
+
const isComposition = atomicChildren.length > 2;
|
|
3656
|
+
const signatureChanged = extractDefaultParamList(current) !== extractDefaultParamList(incoming);
|
|
3657
|
+
return {
|
|
3658
|
+
apiDelta: { added: apiAdded, removed: apiRemoved, signatureChanged },
|
|
3659
|
+
styleDelta: { classNameDiff, tokenUsageDiff },
|
|
3660
|
+
logicDelta: { hasState, hasEffect, hasExtraImports },
|
|
3661
|
+
cvaDelta: { addedVariants: cvaAdded, modifiedVariants: cvaModified },
|
|
3662
|
+
structureDelta: { isComposition, atomicChildren }
|
|
3663
|
+
};
|
|
3664
|
+
}
|
|
3665
|
+
function scorePromotionModes(fileType, fv) {
|
|
3666
|
+
const reasons = [];
|
|
3667
|
+
if (fileType === "hook" || fileType === "util" || fileType === "type") {
|
|
3668
|
+
reasons.push(
|
|
3669
|
+
`fileType=${fileType} \u2014 not a component, deferred to ManualReview`
|
|
3670
|
+
);
|
|
3671
|
+
return { recommendedModes: ["ManualReview"], confidence: 0.5, reasons };
|
|
3672
|
+
}
|
|
3673
|
+
const apiNoChange = fv.apiDelta.added.length === 0 && fv.apiDelta.removed.length === 0 && !fv.apiDelta.signatureChanged;
|
|
3674
|
+
const logicMinimal = !fv.logicDelta.hasState && !fv.logicDelta.hasEffect && !fv.logicDelta.hasExtraImports;
|
|
3675
|
+
if (fv.apiDelta.removed.length > 0 || fv.apiDelta.signatureChanged) {
|
|
3676
|
+
reasons.push(
|
|
3677
|
+
"signature changed or props removed \u2014 Coexist preserves user version"
|
|
3678
|
+
);
|
|
3679
|
+
return { recommendedModes: ["Coexist"], confidence: 0.85, reasons };
|
|
3680
|
+
}
|
|
3681
|
+
if (apiNoChange && logicMinimal && (fv.styleDelta.classNameDiff || fv.styleDelta.tokenUsageDiff) && fv.cvaDelta.addedVariants.length === 0 && fv.cvaDelta.modifiedVariants.length === 0) {
|
|
3682
|
+
reasons.push(
|
|
3683
|
+
"only style / token differences \u2014 push to tokens.overrides.css"
|
|
3684
|
+
);
|
|
3685
|
+
return { recommendedModes: ["TokenOnly"], confidence: 0.8, reasons };
|
|
3686
|
+
}
|
|
3687
|
+
const modes = [];
|
|
3688
|
+
let score = 0;
|
|
3689
|
+
if (fv.cvaDelta.addedVariants.length > 0 || fv.cvaDelta.modifiedVariants.length > 0) {
|
|
3690
|
+
modes.push("Variant");
|
|
3691
|
+
reasons.push(
|
|
3692
|
+
`cva variants delta: +${fv.cvaDelta.addedVariants.length} ~${fv.cvaDelta.modifiedVariants.length}`
|
|
3693
|
+
);
|
|
3694
|
+
score = Math.max(score, 0.7);
|
|
3695
|
+
}
|
|
3696
|
+
if (fv.logicDelta.hasState || fv.logicDelta.hasEffect || fv.logicDelta.hasExtraImports || fv.apiDelta.added.length > 0) {
|
|
3697
|
+
modes.push("Wrapper");
|
|
3698
|
+
reasons.push("user added state / effect / imports / props");
|
|
3699
|
+
score = Math.max(score, 0.75);
|
|
3700
|
+
}
|
|
3701
|
+
if (apiNoChange && logicMinimal && !fv.styleDelta.classNameDiff && !fv.styleDelta.tokenUsageDiff && fv.cvaDelta.addedVariants.length === 0 && fv.cvaDelta.modifiedVariants.length === 0) {
|
|
3702
|
+
modes.push("Preset");
|
|
3703
|
+
reasons.push("no API/logic delta \u2014 Preset captures default-prop tweaks");
|
|
3704
|
+
score = Math.max(score, 0.6);
|
|
3705
|
+
}
|
|
3706
|
+
if (fv.structureDelta.isComposition) {
|
|
3707
|
+
modes.push("Compose");
|
|
3708
|
+
reasons.push(
|
|
3709
|
+
`composition of ${fv.structureDelta.atomicChildren.length} atomic children`
|
|
3710
|
+
);
|
|
3711
|
+
score = Math.max(score, 0.65);
|
|
3712
|
+
}
|
|
3713
|
+
if (modes.length === 0) {
|
|
3714
|
+
reasons.push("no axis crossed the 0.6 threshold");
|
|
3715
|
+
return { recommendedModes: ["ManualReview"], confidence: 0.4, reasons };
|
|
3716
|
+
}
|
|
3717
|
+
return { recommendedModes: modes, confidence: score, reasons };
|
|
3718
|
+
}
|
|
3719
|
+
function extractVariantBody(src, key) {
|
|
3720
|
+
const block = extractVariantsBlock(src);
|
|
3721
|
+
if (block === null) return "";
|
|
3722
|
+
const re = new RegExp(`(?:["']?${key}["']?)\\s*:\\s*\\{`);
|
|
3723
|
+
const idx = block.search(re);
|
|
3724
|
+
if (idx < 0) return "";
|
|
3725
|
+
const open = block.indexOf("{", idx);
|
|
3726
|
+
if (open < 0) return "";
|
|
3727
|
+
let depth = 0;
|
|
3728
|
+
for (let i = open; i < block.length; i++) {
|
|
3729
|
+
const c = block[i];
|
|
3730
|
+
if (c === "{") depth++;
|
|
3731
|
+
else if (c === "}") {
|
|
3732
|
+
depth--;
|
|
3733
|
+
if (depth === 0) return block.slice(open + 1, i);
|
|
3734
|
+
}
|
|
3735
|
+
}
|
|
3736
|
+
return "";
|
|
3737
|
+
}
|
|
3738
|
+
function extractClassNameLiterals(src) {
|
|
3739
|
+
const out = [];
|
|
3740
|
+
for (const m of src.matchAll(/className\s*=\s*["'`]([^"'`]*)["'`]/g)) {
|
|
3741
|
+
if (m[1]) out.push(m[1]);
|
|
3742
|
+
}
|
|
3743
|
+
for (const m of src.matchAll(/\b(?:cn|clsx|cva)\s*\(/g)) {
|
|
3744
|
+
const open = (m.index ?? 0) + m[0].length - 1;
|
|
3745
|
+
let depth = 1;
|
|
3746
|
+
let i = open + 1;
|
|
3747
|
+
for (; i < src.length && depth > 0; i++) {
|
|
3748
|
+
const c = src[i];
|
|
3749
|
+
if (c === "(") depth++;
|
|
3750
|
+
else if (c === ")") depth--;
|
|
3751
|
+
}
|
|
3752
|
+
const body = src.slice(open + 1, i - 1);
|
|
3753
|
+
for (const lit of body.matchAll(/["'`]([^"'`]*)["'`]/g)) {
|
|
3754
|
+
if (lit[1]) out.push(lit[1]);
|
|
3755
|
+
}
|
|
3756
|
+
}
|
|
3757
|
+
return out.sort().join("|");
|
|
3758
|
+
}
|
|
3759
|
+
function extractTokenRefs(src) {
|
|
3760
|
+
const out = /* @__PURE__ */ new Set();
|
|
3761
|
+
for (const m of src.matchAll(/var\(--([a-z0-9-]+)\)/g)) {
|
|
3762
|
+
if (m[1]) out.add(m[1]);
|
|
3763
|
+
}
|
|
3764
|
+
for (const m of src.matchAll(/--([a-z][a-z0-9-]*)\s*:/g)) {
|
|
3765
|
+
if (m[1]) out.add(m[1]);
|
|
3766
|
+
}
|
|
3767
|
+
return out;
|
|
3768
|
+
}
|
|
3769
|
+
function extractImportSources(src) {
|
|
3770
|
+
const out = /* @__PURE__ */ new Set();
|
|
3771
|
+
for (const m of src.matchAll(/^\s*import\b[^'"]*['"]([^'"]+)['"]/gm)) {
|
|
3772
|
+
if (m[1]) out.add(m[1]);
|
|
3773
|
+
}
|
|
3774
|
+
return out;
|
|
3775
|
+
}
|
|
3776
|
+
function extractDefaultParamList(src) {
|
|
3777
|
+
const m = /export\s+default\s+(?:async\s+)?function\s+\w*\s*\(([^)]*)\)/.exec(src) ?? /export\s+default\s+(?:\([^)]*\)|\w+)\s*=>/.exec(src) ?? /(?:const|function)\s+\w+\s*=?\s*(?:\(([^)]*)\)|\w+)\s*(?:=>|\{)/.exec(src);
|
|
3778
|
+
return m?.[1]?.replace(/\s+/g, " ").trim() ?? "";
|
|
3779
|
+
}
|
|
3780
|
+
|
|
3781
|
+
// src/core/ui-upgrade.ts
|
|
3782
|
+
var nodeRequire = createRequire5(import.meta.url);
|
|
3783
|
+
function resolvePackageRoot4(packageName) {
|
|
3784
|
+
const pkgJsonPath = nodeRequire.resolve(`${packageName}/package.json`);
|
|
3785
|
+
return path22.dirname(pkgJsonPath);
|
|
3786
|
+
}
|
|
3787
|
+
async function runUiUpgrade(options) {
|
|
3788
|
+
const { projectRoot, category, ids = [], trigger } = options;
|
|
3789
|
+
const config = await readProjectConfig(projectRoot);
|
|
3790
|
+
if (!config) return { status: "not-initialized" };
|
|
3791
|
+
const cfgKey = category === "ui" ? "ui" : "biz-ui";
|
|
3792
|
+
if (!config.packages?.[cfgKey]) {
|
|
3793
|
+
return { status: "not-installed", detail: `${category} not installed` };
|
|
3794
|
+
}
|
|
3795
|
+
const aliases = config.packages.ui?.aliases ?? config.packages["biz-ui"]?.aliases;
|
|
3796
|
+
if (!aliases) {
|
|
3797
|
+
return {
|
|
3798
|
+
status: "not-installed",
|
|
3799
|
+
detail: `${category} aliases not configured (run \`teamix-evo ui init\` first)`
|
|
3800
|
+
};
|
|
3801
|
+
}
|
|
3802
|
+
const installed = await readInstalledManifest(projectRoot);
|
|
3803
|
+
const lineageReport = await detectComponentLineage({
|
|
3804
|
+
projectRoot,
|
|
3805
|
+
category,
|
|
3806
|
+
config,
|
|
3807
|
+
installed
|
|
3808
|
+
});
|
|
3809
|
+
if (lineageReport.lineage !== "teamix-evo" && lineageReport.lineage !== "mixed") {
|
|
3810
|
+
return {
|
|
3811
|
+
status: "skipped",
|
|
3812
|
+
detail: `lineage=${lineageReport.lineage}; nothing to stage`,
|
|
3813
|
+
lineage: lineageReport.lineage
|
|
3814
|
+
};
|
|
3815
|
+
}
|
|
3816
|
+
if (ids.length > 0) {
|
|
3817
|
+
const knownIds = /* @__PURE__ */ new Set([
|
|
3818
|
+
...lineageReport.registeredIds,
|
|
3819
|
+
...lineageReport.unregisteredIds
|
|
3820
|
+
]);
|
|
3821
|
+
const unknown = ids.filter((id) => !knownIds.has(id));
|
|
3822
|
+
if (unknown.length > 0) {
|
|
3823
|
+
throw new Error(
|
|
3824
|
+
`Unknown ${category} component id(s): ${unknown.map((s) => `"${s}"`).join(
|
|
3825
|
+
", "
|
|
3826
|
+
)}. Hint: \`teamix-evo ${category} list\` shows available ids.`
|
|
3827
|
+
);
|
|
3828
|
+
}
|
|
3829
|
+
}
|
|
3830
|
+
const built = await buildStaging({
|
|
3831
|
+
category,
|
|
3832
|
+
projectRoot,
|
|
3833
|
+
aliases,
|
|
3834
|
+
lineageReport,
|
|
3835
|
+
trigger,
|
|
3836
|
+
onlyIds: ids,
|
|
3837
|
+
uiPackageRoot: options.uiPackageRoot,
|
|
3838
|
+
bizUiPackageRoot: options.bizUiPackageRoot
|
|
3839
|
+
});
|
|
3840
|
+
if (built === null) {
|
|
3841
|
+
return {
|
|
3842
|
+
status: "skipped",
|
|
3843
|
+
detail: "no entries to stage",
|
|
3844
|
+
lineage: lineageReport.lineage
|
|
3845
|
+
};
|
|
3846
|
+
}
|
|
3847
|
+
return {
|
|
3848
|
+
status: "staged",
|
|
3849
|
+
stagingDir: built.stagingDir,
|
|
3850
|
+
manifest: built.manifest
|
|
3851
|
+
};
|
|
3852
|
+
}
|
|
3853
|
+
async function buildStaging(args) {
|
|
3854
|
+
const { category, projectRoot, aliases, lineageReport, trigger, onlyIds } = args;
|
|
3855
|
+
if (category === "ui") {
|
|
3856
|
+
const root = args.uiPackageRoot ?? resolvePackageRoot4("@teamix-evo/ui");
|
|
3857
|
+
const manifest = await loadUiPackageManifest3(root);
|
|
3858
|
+
return buildUiUpgradeStaging({
|
|
3859
|
+
projectRoot,
|
|
3860
|
+
category,
|
|
3861
|
+
manifest,
|
|
3862
|
+
packageRoot: root,
|
|
3863
|
+
aliases,
|
|
3864
|
+
lineageReport,
|
|
3865
|
+
trigger,
|
|
3866
|
+
onlyIds
|
|
3867
|
+
});
|
|
3868
|
+
}
|
|
3869
|
+
const bizRoot = args.bizUiPackageRoot ?? resolvePackageRoot4("@teamix-evo/biz-ui");
|
|
3870
|
+
const variant = lineageReport.installedVariant ?? "_flat";
|
|
3871
|
+
const variantDir = path22.join(bizRoot, "variants", variant);
|
|
3872
|
+
const variantManifest = await loadVariantUiPackageManifest2(variantDir);
|
|
3873
|
+
const uiRoot = args.uiPackageRoot ?? resolvePackageRoot4("@teamix-evo/ui");
|
|
3874
|
+
const uiManifest = await loadUiPackageManifest3(uiRoot);
|
|
3875
|
+
const entryPackageRoot = /* @__PURE__ */ new Map();
|
|
3876
|
+
const merged = [];
|
|
3877
|
+
for (const e of variantManifest.entries) {
|
|
3878
|
+
entryPackageRoot.set(e.id, variantDir);
|
|
3879
|
+
merged.push(e);
|
|
3880
|
+
}
|
|
3881
|
+
for (const e of uiManifest.entries) {
|
|
3882
|
+
if (entryPackageRoot.has(e.id)) continue;
|
|
3883
|
+
entryPackageRoot.set(e.id, uiRoot);
|
|
3884
|
+
merged.push(e);
|
|
3885
|
+
}
|
|
3886
|
+
const synthetic = {
|
|
3887
|
+
schemaVersion: 1,
|
|
3888
|
+
package: "ui",
|
|
3889
|
+
version: variantManifest.version,
|
|
3890
|
+
engines: variantManifest.engines,
|
|
3891
|
+
entries: merged
|
|
3892
|
+
};
|
|
3893
|
+
return buildUiUpgradeStaging({
|
|
3894
|
+
projectRoot,
|
|
3895
|
+
category,
|
|
3896
|
+
manifest: synthetic,
|
|
3897
|
+
packageRoot: variantDir,
|
|
3898
|
+
entryPackageRoot,
|
|
3899
|
+
aliases,
|
|
3900
|
+
lineageReport,
|
|
3901
|
+
trigger,
|
|
3902
|
+
onlyIds
|
|
3903
|
+
});
|
|
3904
|
+
}
|
|
3905
|
+
|
|
3906
|
+
// src/core/deps-install.ts
|
|
3907
|
+
import * as fs17 from "fs/promises";
|
|
3908
|
+
import * as path23 from "path";
|
|
3909
|
+
import { exec } from "child_process";
|
|
3910
|
+
import { promisify } from "util";
|
|
3911
|
+
var execAsync = promisify(exec);
|
|
3912
|
+
async function detectPackageManager(projectRoot) {
|
|
3913
|
+
if (await fileExists(path23.join(projectRoot, "pnpm-lock.yaml"))) return "pnpm";
|
|
3914
|
+
if (await fileExists(path23.join(projectRoot, "pnpm-workspace.yaml")))
|
|
3915
|
+
return "pnpm";
|
|
3916
|
+
if (await fileExists(path23.join(projectRoot, "bun.lockb"))) return "bun";
|
|
3917
|
+
if (await fileExists(path23.join(projectRoot, "bun.lock"))) return "bun";
|
|
3918
|
+
if (await fileExists(path23.join(projectRoot, "yarn.lock"))) return "yarn";
|
|
3919
|
+
return "npm";
|
|
3920
|
+
}
|
|
3921
|
+
function getInstallCommand(pm) {
|
|
3922
|
+
switch (pm) {
|
|
3923
|
+
case "pnpm":
|
|
3924
|
+
return "pnpm install";
|
|
3925
|
+
case "yarn":
|
|
3926
|
+
return "yarn install";
|
|
3927
|
+
case "bun":
|
|
3928
|
+
return "bun install";
|
|
3929
|
+
case "npm":
|
|
3930
|
+
return "npm install";
|
|
3931
|
+
}
|
|
3932
|
+
}
|
|
3933
|
+
async function installProjectDeps(options) {
|
|
3934
|
+
const { projectRoot, npmDependencies, skipInstall = false } = options;
|
|
3935
|
+
const pkgPath = path23.join(projectRoot, "package.json");
|
|
3936
|
+
const raw = await readFileOrNull(pkgPath);
|
|
3937
|
+
if (!raw) {
|
|
3938
|
+
throw new Error(
|
|
3939
|
+
`package.json not found at ${projectRoot}. Cannot install dependencies.`
|
|
3940
|
+
);
|
|
3941
|
+
}
|
|
3942
|
+
const pkg = JSON.parse(raw);
|
|
3943
|
+
const existingDeps = {
|
|
3944
|
+
...pkg.dependencies,
|
|
3945
|
+
...pkg.devDependencies
|
|
3946
|
+
};
|
|
3947
|
+
const added = {};
|
|
3948
|
+
const existed = {};
|
|
3949
|
+
for (const [name, range] of Object.entries(npmDependencies)) {
|
|
3950
|
+
if (existingDeps[name]) {
|
|
3951
|
+
existed[name] = existingDeps[name];
|
|
3952
|
+
} else {
|
|
3953
|
+
added[name] = range;
|
|
3954
|
+
}
|
|
3955
|
+
}
|
|
3956
|
+
if (Object.keys(added).length > 0) {
|
|
3957
|
+
if (!pkg.dependencies) pkg.dependencies = {};
|
|
3958
|
+
for (const [name, range] of Object.entries(added)) {
|
|
3959
|
+
pkg.dependencies[name] = range;
|
|
3960
|
+
}
|
|
3961
|
+
pkg.dependencies = Object.fromEntries(
|
|
3962
|
+
Object.entries(pkg.dependencies).sort(([a], [b]) => a.localeCompare(b))
|
|
3963
|
+
);
|
|
3964
|
+
await fs17.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf-8");
|
|
3965
|
+
logger.info(
|
|
3966
|
+
` patched package.json: +${Object.keys(added).length} dependencies`
|
|
3967
|
+
);
|
|
3968
|
+
}
|
|
3969
|
+
const packageManager = await detectPackageManager(projectRoot);
|
|
3970
|
+
let installed = false;
|
|
3971
|
+
if (!skipInstall && Object.keys(added).length > 0) {
|
|
3972
|
+
const cmd = getInstallCommand(packageManager);
|
|
3973
|
+
logger.info(` running ${cmd}...`);
|
|
3974
|
+
try {
|
|
3975
|
+
await execAsync(cmd, { cwd: projectRoot, timeout: 12e4 });
|
|
3976
|
+
installed = true;
|
|
3977
|
+
logger.info(" install complete");
|
|
3978
|
+
} catch (err) {
|
|
3979
|
+
logger.warn(
|
|
3980
|
+
` install failed: ${err instanceof Error ? err.message : String(err)}`
|
|
3981
|
+
);
|
|
3982
|
+
logger.warn(" please run install manually");
|
|
3983
|
+
}
|
|
3984
|
+
}
|
|
3985
|
+
return { added, existed, installed, packageManager };
|
|
3986
|
+
}
|
|
3987
|
+
|
|
3988
|
+
// src/core/ui-migration-plan-template.ts
|
|
3989
|
+
import * as fs18 from "fs/promises";
|
|
3990
|
+
import * as path24 from "path";
|
|
3991
|
+
async function generateMigrationPlan(options) {
|
|
3992
|
+
const { projectRoot, strategy, isolateResult, addResult, residualImports } = options;
|
|
3993
|
+
const now = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
3994
|
+
const planDir = path24.join(projectRoot, ".qoder", "plans");
|
|
3995
|
+
await ensureDir(planDir);
|
|
3996
|
+
const planPath = path24.join(planDir, "teamix-evo-ui-migration.md");
|
|
3997
|
+
const movedSection = isolateResult.movedFiles.length > 0 ? isolateResult.movedFiles.map((f) => `| \`${f.from}\` | \`${f.to}\` |`).join("\n") : "| _(\u65E0\u642C\u8FC1)_ | |";
|
|
3998
|
+
const installedSection = addResult.orderedIds.map((id) => `- \`${id}\``).join("\n");
|
|
3999
|
+
const rewriteCount = isolateResult.importRewrites.size;
|
|
4000
|
+
const rewriteSection = rewriteCount > 0 ? [...isolateResult.importRewrites.entries()].map(([file, count]) => `- \`${file}\`: ${count} \u5904`).join("\n") : "_(\u65E0 import \u91CD\u5199)_";
|
|
4001
|
+
const strategyLabel = strategy === "isolate-progressive" ? "\u9694\u79BB + \u6E10\u8FDB\u8FC1\u79FB\uFF08Path A\uFF09" : strategy === "isolate-aggressive" ? "\u9694\u79BB + \u4E3B\u52A8\u5347\u7EA7\uFF08Path C\uFF09" : "\u8DF3\u8FC7\u5DF2\u6709\u6587\u4EF6";
|
|
4002
|
+
const residualSection = residualImports != null && residualImports > 0 ? `
|
|
4003
|
+
## \u6B8B\u7559 import \u6E05\u5355
|
|
4004
|
+
|
|
4005
|
+
\u53D1\u73B0 ${residualImports} \u5904\u6B8B\u7559\u5F15\u7528\uFF0C\u9700\u4EBA\u5DE5\u4FEE\u590D\u3002\u8BE6\u89C1 lint \u8F93\u51FA\u4E2D\u7684 \`no-legacy-shadcn-import\` \u8B66\u544A\u3002
|
|
4006
|
+
` : "";
|
|
4007
|
+
const nextSteps = strategy === "isolate-aggressive" ? `1. \u67E5\u770B \`.teamix-evo/.upgrade-staging/\` \u4E2D\u7684\u5347\u7EA7 staging
|
|
4008
|
+
2. \u89E6\u53D1 \`teamix-evo-upgrade\` skill \u8FDB\u884C\u5206\u6279\u66FF\u6362
|
|
4009
|
+
3. \u9010\u6B65\u5C06 \`shadcn-ui/\` \u4E2D\u7684\u7EC4\u4EF6\u66FF\u6362\u4E3A \`ui/\` \u7248\u672C
|
|
4010
|
+
4. \u66FF\u6362\u5B8C\u6210\u540E\u5220\u9664 \`shadcn-ui/\` \u76EE\u5F55
|
|
4011
|
+
5. \u8FD0\u884C \`npx teamix-evo tokens treat\` \u8FDB\u884C token \u6CBB\u7406` : `1. \u9010\u6B65\u5C06\u4E1A\u52A1\u4EE3\u7801\u4E2D\u7684 \`@/components/shadcn-ui/...\` \u66FF\u6362\u4E3A \`@/components/ui/...\`
|
|
4012
|
+
2. ESLint \`no-legacy-shadcn-import\` \u89C4\u5219\u4F1A\u63D0\u793A\u54EA\u4E9B\u6587\u4EF6\u8FD8\u5728\u5F15\u7528\u65E7\u7EC4\u4EF6
|
|
4013
|
+
3. \u5168\u90E8\u66FF\u6362\u5B8C\u6210\u540E\u5220\u9664 \`src/components/shadcn-ui/\` \u76EE\u5F55
|
|
4014
|
+
4. \u8FD0\u884C \`npx teamix-evo tokens treat\` \u8FDB\u884C token \u6CBB\u7406`;
|
|
4015
|
+
const content = `# teamix-evo UI \u8FC1\u79FB\u8BA1\u5212
|
|
4016
|
+
|
|
4017
|
+
> \u751F\u6210\u65F6\u95F4: ${now}
|
|
4018
|
+
> \u8FC1\u79FB\u7B56\u7565: ${strategyLabel}
|
|
4019
|
+
|
|
4020
|
+
## \u642C\u8FC1\u8BB0\u5F55
|
|
4021
|
+
|
|
4022
|
+
| \u539F\u8DEF\u5F84 | \u65B0\u8DEF\u5F84 |
|
|
4023
|
+
| ------ | ------ |
|
|
4024
|
+
${movedSection}
|
|
4025
|
+
|
|
4026
|
+
## \u5DF2\u5B89\u88C5 teamix-evo \u7EC4\u4EF6\uFF08${addResult.orderedIds.length} \u4E2A\uFF09
|
|
4027
|
+
|
|
4028
|
+
${installedSection}
|
|
4029
|
+
|
|
4030
|
+
## Import \u91CD\u5199\u8BB0\u5F55\uFF08${rewriteCount} \u4E2A\u6587\u4EF6\uFF09
|
|
4031
|
+
|
|
4032
|
+
${rewriteSection}
|
|
4033
|
+
|
|
4034
|
+
## components.json
|
|
4035
|
+
|
|
4036
|
+
${isolateResult.componentsJsonRemoved ? "\u5DF2\u5907\u4EFD\u81F3 `.teamix-evo/.backups/` \u5E76\u5220\u9664\u539F\u6587\u4EF6\u3002" : "\u672A\u53D1\u73B0 components.json\u3002"}
|
|
4037
|
+
|
|
4038
|
+
## \u5907\u4EFD\u6587\u4EF6
|
|
4039
|
+
|
|
4040
|
+
${isolateResult.backedUpFiles.length > 0 ? isolateResult.backedUpFiles.map((f) => `- \`${f}\``).join("\n") : "_(\u65E0\u5907\u4EFD)_"}
|
|
4041
|
+
${residualSection}
|
|
4042
|
+
## \u540E\u7EED\u6B65\u9AA4
|
|
4043
|
+
|
|
4044
|
+
${nextSteps}
|
|
4045
|
+
|
|
4046
|
+
## npm \u4F9D\u8D56
|
|
4047
|
+
|
|
4048
|
+
\u5DF2\u81EA\u52A8\u5B89\u88C5\u7684\u4F9D\u8D56:
|
|
4049
|
+
${Object.entries(addResult.npmDependencies).length > 0 ? Object.entries(addResult.npmDependencies).map(([name, version]) => `- \`${name}@${version}\``).join("\n") : "_(\u65E0\u989D\u5916\u4F9D\u8D56)_"}
|
|
4050
|
+
`;
|
|
4051
|
+
await fs18.writeFile(planPath, content, "utf-8");
|
|
4052
|
+
return planPath;
|
|
4053
|
+
}
|
|
4054
|
+
|
|
4055
|
+
// src/core/residual-import-detector.ts
|
|
4056
|
+
import * as fs19 from "fs/promises";
|
|
4057
|
+
import * as path25 from "path";
|
|
4058
|
+
var LEGACY_PATTERNS = [
|
|
4059
|
+
/['"`]@\/components\/shadcn-ui\//,
|
|
4060
|
+
/['"`]~\/components\/shadcn-ui\//,
|
|
4061
|
+
/['"`]\.\.?\/.*shadcn-ui\//,
|
|
4062
|
+
/from\s+['"].*shadcn-ui\//
|
|
4063
|
+
];
|
|
4064
|
+
async function detectResidualImports(projectRoot) {
|
|
4065
|
+
const srcDir = path25.join(projectRoot, "src");
|
|
4066
|
+
const SCAN_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
4067
|
+
".ts",
|
|
4068
|
+
".tsx",
|
|
4069
|
+
".js",
|
|
4070
|
+
".jsx",
|
|
4071
|
+
".mdx",
|
|
4072
|
+
".css",
|
|
4073
|
+
".scss"
|
|
4074
|
+
]);
|
|
4075
|
+
let filesScanned = 0;
|
|
4076
|
+
const entries = [];
|
|
4077
|
+
const affectedFileSet = /* @__PURE__ */ new Set();
|
|
4078
|
+
if (await fileExists(srcDir)) {
|
|
4079
|
+
const allFiles = await walkDir(srcDir, DEFAULT_SKIP_DIRS);
|
|
4080
|
+
const scanFiles = allFiles.filter(
|
|
4081
|
+
(f) => SCAN_EXTENSIONS.has(path25.extname(f))
|
|
4082
|
+
);
|
|
4083
|
+
for (const file of scanFiles) {
|
|
4084
|
+
filesScanned++;
|
|
4085
|
+
const content = await fs19.readFile(file, "utf-8");
|
|
4086
|
+
const lines = content.split("\n");
|
|
4087
|
+
const relPath = path25.relative(projectRoot, file).replace(/\\/g, "/");
|
|
4088
|
+
for (let i = 0; i < lines.length; i++) {
|
|
4089
|
+
const line = lines[i];
|
|
4090
|
+
for (const pattern of LEGACY_PATTERNS) {
|
|
4091
|
+
if (pattern.test(line)) {
|
|
4092
|
+
entries.push({
|
|
4093
|
+
file: relPath,
|
|
4094
|
+
line: i + 1,
|
|
4095
|
+
content: line.trim()
|
|
4096
|
+
});
|
|
4097
|
+
affectedFileSet.add(relPath);
|
|
4098
|
+
break;
|
|
4099
|
+
}
|
|
4100
|
+
}
|
|
4101
|
+
}
|
|
4102
|
+
}
|
|
4103
|
+
}
|
|
4104
|
+
const rootConfigFiles = [
|
|
4105
|
+
"vite.config.ts",
|
|
4106
|
+
"vite.config.js",
|
|
4107
|
+
"next.config.ts",
|
|
4108
|
+
"next.config.js",
|
|
4109
|
+
"tsconfig.json",
|
|
4110
|
+
".storybook/main.ts",
|
|
4111
|
+
".storybook/main.js"
|
|
4112
|
+
];
|
|
4113
|
+
for (const configFile of rootConfigFiles) {
|
|
4114
|
+
const configPath = path25.join(projectRoot, configFile);
|
|
4115
|
+
if (await fileExists(configPath)) {
|
|
4116
|
+
filesScanned++;
|
|
4117
|
+
const content = await fs19.readFile(configPath, "utf-8");
|
|
4118
|
+
const lines = content.split("\n");
|
|
4119
|
+
for (let i = 0; i < lines.length; i++) {
|
|
4120
|
+
const line = lines[i];
|
|
4121
|
+
for (const pattern of LEGACY_PATTERNS) {
|
|
4122
|
+
if (pattern.test(line)) {
|
|
4123
|
+
entries.push({
|
|
4124
|
+
file: configFile,
|
|
4125
|
+
line: i + 1,
|
|
4126
|
+
content: line.trim()
|
|
4127
|
+
});
|
|
4128
|
+
affectedFileSet.add(configFile);
|
|
4129
|
+
break;
|
|
4130
|
+
}
|
|
4131
|
+
}
|
|
4132
|
+
}
|
|
4133
|
+
}
|
|
4134
|
+
}
|
|
4135
|
+
return {
|
|
4136
|
+
filesScanned,
|
|
4137
|
+
entries,
|
|
4138
|
+
affectedFiles: affectedFileSet.size
|
|
4139
|
+
};
|
|
4140
|
+
}
|
|
4141
|
+
|
|
4142
|
+
// src/core/init-checklist-template.ts
|
|
4143
|
+
var STEP_LABELS = {
|
|
4144
|
+
tokens: "tokens \u2014 design tokens \u5B89\u88C5",
|
|
4145
|
+
skills: "skills \u2014 AI skills \u5B89\u88C5",
|
|
4146
|
+
"agents-md": "agents-md \u2014 AGENTS.md \u751F\u6210",
|
|
4147
|
+
"ui-init": "ui-init \u2014 UI \u914D\u7F6E\u521D\u59CB\u5316",
|
|
4148
|
+
"ui-add": "ui-add \u2014 UI \u7EC4\u4EF6\u5B89\u88C5",
|
|
4149
|
+
lint: "lint \u2014 ESLint + Stylelint \u914D\u7F6E",
|
|
4150
|
+
gitignore: "gitignore \u2014 .gitignore \u8FFD\u52A0"
|
|
4151
|
+
};
|
|
4152
|
+
function renderInitChecklist(args) {
|
|
4153
|
+
const { variant, status, steps } = args;
|
|
4154
|
+
const ts = args.timestamp ?? (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").slice(0, 19);
|
|
4155
|
+
const phaseA = steps.map((s) => {
|
|
4156
|
+
const label = STEP_LABELS[s.name] ?? s.name;
|
|
4157
|
+
const checked = s.status === "ok" ? "x" : " ";
|
|
4158
|
+
const suffix = s.status === "ok" ? "" : s.status === "skip" ? "\uFF08\u8DF3\u8FC7\uFF09" : s.status === "fail" ? `\uFF08\u5931\u8D25\uFF1A${s.detail ?? "unknown"}\uFF09` : "\uFF08\u8BA1\u5212\u4E2D\uFF09";
|
|
4159
|
+
return `- [${checked}] ${label}${suffix}`;
|
|
4160
|
+
}).join("\n");
|
|
4161
|
+
return `# teamix-evo init \u68C0\u67E5\u6E05\u5355
|
|
4162
|
+
|
|
4163
|
+
> \u7531 \`teamix-evo init\` \u81EA\u52A8\u751F\u6210 \xB7 ${ts}
|
|
4164
|
+
> variant: ${variant} \xB7 status: ${status}
|
|
4165
|
+
|
|
4166
|
+
## Phase A \xB7 CLI \u843D\u5730\uFF08\u81EA\u52A8\u5B8C\u6210\uFF09
|
|
4167
|
+
|
|
4168
|
+
${phaseA}
|
|
4169
|
+
|
|
4170
|
+
## Phase B \xB7 AI \u4E32\u573A\uFF086 \u6B65\u95ED\u73AF\uFF09
|
|
4171
|
+
|
|
4172
|
+
> \u4EE5\u4E0B\u6B65\u9AA4\u7531\u5BA2\u6237\u4FA7 AI \u5728 IDE \u4E2D\u9010\u6B65\u6267\u884C\u3002\u26A0\uFE0F \u6807\u8BB0\u7684\u6B65\u9AA4\u4E3A HARD GATE\uFF0C
|
|
4173
|
+
> \u5FC5\u987B\u6267\u884C\u5B8C\u6BD5\u5E76\u5F81\u5F97\u7528\u6237\u786E\u8BA4\u540E\u624D\u80FD\u7EE7\u7EED\uFF08\u53C2\u89C1 manage SKILL.md\u300CHARD GATE \u534F\u8BAE\u300D\uFF09\u3002
|
|
4174
|
+
|
|
4175
|
+
- [ ] \u26A0\uFE0F HARD GATE\uFF1Aadopt \u2014 \`npx teamix-evo ui add --adopt\`
|
|
4176
|
+
- [ ] \u26A0\uFE0F HARD GATE\uFF1Aupgrade staging \u2014 \`npx teamix-evo ui upgrade\`\uFF08+ \`biz-ui upgrade\`\uFF09
|
|
4177
|
+
- [ ] \u26A0\uFE0F HARD GATE\uFF1Atokens audit \u2014 \`npx teamix-evo tokens audit\`
|
|
4178
|
+
- [ ] AGENTS.md \u56DE\u586B \u2014 \u5F52\u7C7B\u9879\u76EE\u7279\u6709\u89C4\u5219\uFF0C\u585E\u56DE managed \u533A\u57DF\u4E4B\u524D
|
|
4179
|
+
- [ ] lint \u2014 \`npx teamix-evo lint init -y\` + \`pnpm lint\`
|
|
4180
|
+
- [ ] \u26A0\uFE0F HARD GATE\uFF1Atoken \u6CBB\u7406 \u2014 \u89E6\u53D1 \`teamix-evo-upgrade\` skill Part C\uFF08Token treatment pipeline\uFF09
|
|
4181
|
+
|
|
4182
|
+
## \u5907\u6CE8
|
|
4183
|
+
|
|
4184
|
+
- Phase A \u4E2D\u5931\u8D25\u6216\u8DF3\u8FC7\u7684\u6B65\u9AA4\u4E0D\u5F71\u54CD Phase B \u542F\u52A8\uFF0C\u4F46\u5EFA\u8BAE\u5148\u4FEE\u590D\u518D\u7EE7\u7EED
|
|
4185
|
+
- \u4FEE\u590D\u540E\u91CD\u8DD1 \`teamix-evo init\`\uFF08\u6BCF\u6B65\u5E42\u7B49\uFF0C\u81EA\u52A8\u8DF3\u8FC7\u5DF2\u5B8C\u6210\u9879\uFF09
|
|
4186
|
+
- \u6062\u590D\u5230\u6267\u884C\u524D\u72B6\u6001\uFF1A\`teamix-evo restore --list\` \u2192 \`teamix-evo restore <ts>\`
|
|
4187
|
+
`;
|
|
4188
|
+
}
|
|
4189
|
+
|
|
4190
|
+
// src/core/snapshot.ts
|
|
4191
|
+
import * as fs20 from "fs/promises";
|
|
4192
|
+
import * as path26 from "path";
|
|
4193
|
+
var TEAMIX_DIR3 = ".teamix-evo";
|
|
4194
|
+
var SNAPSHOTS_DIR = ".snapshots";
|
|
4195
|
+
var LOGS_DIR = "logs";
|
|
4196
|
+
var META_FILE = "_meta.json";
|
|
4197
|
+
var DEFAULT_KEEP = 5;
|
|
4198
|
+
function isoToFsSafe2(iso) {
|
|
4199
|
+
return iso.replace(/[:.]/g, "-");
|
|
4200
|
+
}
|
|
4201
|
+
function fsSafeToIso(safe) {
|
|
4202
|
+
return safe.replace(
|
|
4203
|
+
/^(\d{4}-\d{2}-\d{2})T(\d{2})-(\d{2})-(\d{2})-(\d{3})(Z)$/,
|
|
4204
|
+
"$1T$2:$3:$4.$5$6"
|
|
4205
|
+
);
|
|
4206
|
+
}
|
|
4207
|
+
async function createSnapshot(projectRoot, opts = {}) {
|
|
4208
|
+
const teamixDir = path26.join(projectRoot, TEAMIX_DIR3);
|
|
4209
|
+
try {
|
|
4210
|
+
const stat5 = await fs20.stat(teamixDir);
|
|
4211
|
+
if (!stat5.isDirectory()) return null;
|
|
4212
|
+
} catch (err) {
|
|
4213
|
+
if (err.code === "ENOENT") return null;
|
|
4214
|
+
throw err;
|
|
4215
|
+
}
|
|
4216
|
+
const isoTs = (/* @__PURE__ */ new Date()).toISOString();
|
|
4217
|
+
const ts = isoToFsSafe2(isoTs);
|
|
4218
|
+
const snapshotRoot = path26.join(teamixDir, SNAPSHOTS_DIR);
|
|
4219
|
+
const target = path26.join(snapshotRoot, ts);
|
|
4220
|
+
await fs20.mkdir(target, { recursive: true });
|
|
4221
|
+
const entries = await fs20.readdir(teamixDir, { withFileTypes: true });
|
|
4222
|
+
for (const entry of entries) {
|
|
4223
|
+
if (entry.name === SNAPSHOTS_DIR) continue;
|
|
4224
|
+
if (entry.name === LOGS_DIR) continue;
|
|
4225
|
+
const src = path26.join(teamixDir, entry.name);
|
|
4226
|
+
const dst = path26.join(target, entry.name);
|
|
4227
|
+
await fs20.cp(src, dst, { recursive: true });
|
|
4228
|
+
}
|
|
4229
|
+
const meta = {
|
|
4230
|
+
ts: isoTs,
|
|
4231
|
+
reason: opts.reason ?? "manual"
|
|
4232
|
+
};
|
|
4233
|
+
await fs20.writeFile(
|
|
4234
|
+
path26.join(target, META_FILE),
|
|
4235
|
+
JSON.stringify(meta, null, 2) + "\n",
|
|
4236
|
+
"utf-8"
|
|
4237
|
+
);
|
|
4238
|
+
logger.debug(
|
|
4239
|
+
`Snapshot created \u2192 ${path26.relative(projectRoot, target)} (${meta.reason})`
|
|
4240
|
+
);
|
|
4241
|
+
const keep = opts.keep ?? DEFAULT_KEEP;
|
|
4242
|
+
await pruneSnapshots(projectRoot, keep, { protectedTs: opts.protectedTs });
|
|
4243
|
+
return { ts, path: target };
|
|
4244
|
+
}
|
|
4245
|
+
async function listSnapshots(projectRoot) {
|
|
4246
|
+
const snapshotRoot = path26.join(projectRoot, TEAMIX_DIR3, SNAPSHOTS_DIR);
|
|
4247
|
+
let entries;
|
|
4248
|
+
try {
|
|
4249
|
+
entries = await fs20.readdir(snapshotRoot, { withFileTypes: true });
|
|
4250
|
+
} catch (err) {
|
|
4251
|
+
if (err.code === "ENOENT") return [];
|
|
4252
|
+
throw err;
|
|
4253
|
+
}
|
|
4254
|
+
const result = [];
|
|
4255
|
+
for (const entry of entries) {
|
|
4256
|
+
if (!entry.isDirectory()) continue;
|
|
4257
|
+
const dir = path26.join(snapshotRoot, entry.name);
|
|
4258
|
+
let isoTs = null;
|
|
4259
|
+
let reason = null;
|
|
4260
|
+
try {
|
|
4261
|
+
const raw = await fs20.readFile(path26.join(dir, META_FILE), "utf-8");
|
|
4262
|
+
const parsed = JSON.parse(raw);
|
|
4263
|
+
if (typeof parsed.ts === "string") isoTs = parsed.ts;
|
|
4264
|
+
if (typeof parsed.reason === "string" && ["init", "update", "switch", "restore", "manual"].includes(
|
|
4265
|
+
parsed.reason
|
|
4266
|
+
)) {
|
|
4267
|
+
reason = parsed.reason;
|
|
4268
|
+
}
|
|
4269
|
+
} catch {
|
|
4270
|
+
isoTs = fsSafeToIso(entry.name);
|
|
4271
|
+
}
|
|
4272
|
+
result.push({ ts: entry.name, isoTs, reason, path: dir });
|
|
4273
|
+
}
|
|
4274
|
+
result.sort((a, b) => a.ts < b.ts ? 1 : a.ts > b.ts ? -1 : 0);
|
|
4275
|
+
return result;
|
|
2692
4276
|
}
|
|
2693
4277
|
async function pruneSnapshots(projectRoot, keep = DEFAULT_KEEP, opts = {}) {
|
|
2694
4278
|
if (keep < 0)
|
|
@@ -2699,14 +4283,66 @@ async function pruneSnapshots(projectRoot, keep = DEFAULT_KEEP, opts = {}) {
|
|
|
2699
4283
|
const toRemove = opts.protectedTs ? tail.filter((s) => s.ts !== opts.protectedTs) : tail;
|
|
2700
4284
|
const removed = [];
|
|
2701
4285
|
for (const snap of toRemove) {
|
|
2702
|
-
await
|
|
4286
|
+
await fs20.rm(snap.path, { recursive: true, force: true });
|
|
2703
4287
|
removed.push(snap.ts);
|
|
2704
4288
|
logger.debug(`Pruned snapshot ${snap.ts}`);
|
|
2705
4289
|
}
|
|
2706
4290
|
return removed.reverse();
|
|
2707
4291
|
}
|
|
2708
4292
|
|
|
4293
|
+
// src/core/file-changes.ts
|
|
4294
|
+
import * as fs21 from "fs/promises";
|
|
4295
|
+
import * as path27 from "path";
|
|
4296
|
+
function toRelativePosix(p, projectRoot) {
|
|
4297
|
+
let rel2 = p;
|
|
4298
|
+
if (path27.isAbsolute(p)) {
|
|
4299
|
+
rel2 = path27.relative(projectRoot, p);
|
|
4300
|
+
}
|
|
4301
|
+
return rel2.split(path27.sep).join("/");
|
|
4302
|
+
}
|
|
4303
|
+
async function listBackupOriginals(projectRoot) {
|
|
4304
|
+
const backupsDir = path27.join(projectRoot, ".teamix-evo", ".backups");
|
|
4305
|
+
const out = /* @__PURE__ */ new Set();
|
|
4306
|
+
const stack = [backupsDir];
|
|
4307
|
+
while (stack.length > 0) {
|
|
4308
|
+
const dir = stack.pop();
|
|
4309
|
+
let entries;
|
|
4310
|
+
try {
|
|
4311
|
+
entries = await fs21.readdir(dir, { withFileTypes: true });
|
|
4312
|
+
} catch (err) {
|
|
4313
|
+
if (err.code === "ENOENT") continue;
|
|
4314
|
+
throw err;
|
|
4315
|
+
}
|
|
4316
|
+
for (const e of entries) {
|
|
4317
|
+
const full = path27.join(dir, e.name);
|
|
4318
|
+
if (e.isDirectory()) {
|
|
4319
|
+
stack.push(full);
|
|
4320
|
+
} else if (e.isFile() && e.name.endsWith(".bak")) {
|
|
4321
|
+
const rel2 = path27.relative(backupsDir, full);
|
|
4322
|
+
const original = stripBackupSuffix(rel2);
|
|
4323
|
+
if (original) out.add(original.split(path27.sep).join("/"));
|
|
4324
|
+
}
|
|
4325
|
+
}
|
|
4326
|
+
}
|
|
4327
|
+
return out;
|
|
4328
|
+
}
|
|
4329
|
+
function stripBackupSuffix(rel2) {
|
|
4330
|
+
const m = rel2.match(
|
|
4331
|
+
/^(.+)\.\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2}-\d{3}Z\.bak$/
|
|
4332
|
+
);
|
|
4333
|
+
return m?.[1] ?? null;
|
|
4334
|
+
}
|
|
4335
|
+
function diffBackupSet(before, after) {
|
|
4336
|
+
const out = /* @__PURE__ */ new Set();
|
|
4337
|
+
for (const p of after) {
|
|
4338
|
+
if (!before.has(p)) out.add(p);
|
|
4339
|
+
}
|
|
4340
|
+
return out;
|
|
4341
|
+
}
|
|
4342
|
+
|
|
2709
4343
|
// src/core/project-init.ts
|
|
4344
|
+
import * as fsNode from "fs/promises";
|
|
4345
|
+
import * as path28 from "path";
|
|
2710
4346
|
var BASELINE_UI_ENTRIES = [
|
|
2711
4347
|
"button",
|
|
2712
4348
|
"button-group",
|
|
@@ -2728,12 +4364,19 @@ var CRITICAL_STEPS = /* @__PURE__ */ new Set([
|
|
|
2728
4364
|
"ui-init"
|
|
2729
4365
|
]);
|
|
2730
4366
|
var IMPLEMENTED_STRATEGIES = {
|
|
4367
|
+
// 'agents-md': both 'merge-managed' (Phase 2.B — splice the
|
|
4368
|
+
// teamix-evo-skills managed region) and 'overwrite' (full rewrite) write
|
|
4369
|
+
// through `runGenerateAgentsMd`.
|
|
2731
4370
|
"agents-md": ["merge-managed", "overwrite", "skip"],
|
|
2732
4371
|
tokens: ["migrate", "overwrite", "skip"],
|
|
2733
4372
|
"components-json": ["overwrite", "skip"],
|
|
2734
4373
|
"shadcn-source": ["overwrite", "skip-existing", "skip"],
|
|
2735
4374
|
"tailwind-config": ["skip"],
|
|
2736
|
-
"index-css": ["skip"]
|
|
4375
|
+
"index-css": ["skip"],
|
|
4376
|
+
// Phase 3.E: lint conflict strategies are honored end-to-end by
|
|
4377
|
+
// `runLintInit` (backup user file + write template, optional AI-assist hint).
|
|
4378
|
+
"eslint-config": ["merge", "backup-overwrite", "skip", "overwrite"],
|
|
4379
|
+
"stylelint-config": ["merge", "backup-overwrite", "skip", "overwrite"]
|
|
2737
4380
|
};
|
|
2738
4381
|
function pickIde(ides) {
|
|
2739
4382
|
return ides[0] ?? "qoder";
|
|
@@ -2751,11 +4394,75 @@ async function resolveUiEntries(options) {
|
|
|
2751
4394
|
}
|
|
2752
4395
|
return [...BASELINE_UI_ENTRIES];
|
|
2753
4396
|
}
|
|
4397
|
+
function deriveTokensChanges(result, projectRoot) {
|
|
4398
|
+
if (result.status !== "installed") return [];
|
|
4399
|
+
return result.resources.map((r) => ({
|
|
4400
|
+
kind: "created",
|
|
4401
|
+
path: toRelativePosix(r.target, projectRoot),
|
|
4402
|
+
step: "tokens",
|
|
4403
|
+
detail: r.strategy
|
|
4404
|
+
}));
|
|
4405
|
+
}
|
|
4406
|
+
function deriveSkillsChanges(result, projectRoot) {
|
|
4407
|
+
if (result.status !== "installed") return [];
|
|
4408
|
+
return result.addedSkillIds.map((id) => ({
|
|
4409
|
+
kind: "created",
|
|
4410
|
+
path: `.teamix-evo/skills/${id}/SKILL.md`,
|
|
4411
|
+
step: "skills",
|
|
4412
|
+
detail: "skill installed (source mirror + IDE mirrors)"
|
|
4413
|
+
}));
|
|
4414
|
+
}
|
|
4415
|
+
function deriveUiAddChanges(result, projectRoot) {
|
|
4416
|
+
const out = [];
|
|
4417
|
+
let remaining = result.written;
|
|
4418
|
+
for (let i = result.resources.length - 1; i >= 0 && remaining > 0; i--) {
|
|
4419
|
+
const r = result.resources[i];
|
|
4420
|
+
out.unshift({
|
|
4421
|
+
kind: "created",
|
|
4422
|
+
path: toRelativePosix(r.target, projectRoot),
|
|
4423
|
+
step: "ui-add",
|
|
4424
|
+
detail: r.strategy
|
|
4425
|
+
});
|
|
4426
|
+
remaining--;
|
|
4427
|
+
}
|
|
4428
|
+
return out;
|
|
4429
|
+
}
|
|
4430
|
+
function deriveLintChanges(result) {
|
|
4431
|
+
if (result.status !== "installed") return [];
|
|
4432
|
+
const out = [];
|
|
4433
|
+
if (result.eslint) {
|
|
4434
|
+
out.push({
|
|
4435
|
+
kind: "created",
|
|
4436
|
+
path: "eslint.config.js",
|
|
4437
|
+
step: "lint",
|
|
4438
|
+
detail: "@teamix-evo/eslint-config consumer preset"
|
|
4439
|
+
});
|
|
4440
|
+
}
|
|
4441
|
+
if (result.stylelint) {
|
|
4442
|
+
out.push({
|
|
4443
|
+
kind: "created",
|
|
4444
|
+
path: "stylelint.config.cjs",
|
|
4445
|
+
step: "lint",
|
|
4446
|
+
detail: "@teamix-evo/stylelint-config consumer preset"
|
|
4447
|
+
});
|
|
4448
|
+
}
|
|
4449
|
+
if (result.packageJsonPatched) {
|
|
4450
|
+
out.push({
|
|
4451
|
+
kind: "created",
|
|
4452
|
+
path: "package.json",
|
|
4453
|
+
step: "lint",
|
|
4454
|
+
detail: 'scripts.lint / scripts["lint:css"]'
|
|
4455
|
+
});
|
|
4456
|
+
}
|
|
4457
|
+
return out;
|
|
4458
|
+
}
|
|
2754
4459
|
async function runProjectInit(options) {
|
|
2755
4460
|
const { projectRoot, answers, dryRun = false, onStep } = options;
|
|
2756
4461
|
const ide = pickIde(answers.ides);
|
|
2757
4462
|
const steps = [];
|
|
2758
4463
|
const pending = [];
|
|
4464
|
+
const allChanges = [];
|
|
4465
|
+
const backupsBefore = dryRun ? /* @__PURE__ */ new Set() : await listBackupOriginals(projectRoot).catch(() => /* @__PURE__ */ new Set());
|
|
2759
4466
|
let snapshot = null;
|
|
2760
4467
|
let snapshotError;
|
|
2761
4468
|
if (!dryRun) {
|
|
@@ -2772,6 +4479,9 @@ async function runProjectInit(options) {
|
|
|
2772
4479
|
function record(step) {
|
|
2773
4480
|
steps.push(step);
|
|
2774
4481
|
onStep?.(step);
|
|
4482
|
+
if (step.changes && step.changes.length > 0) {
|
|
4483
|
+
allChanges.push(...step.changes);
|
|
4484
|
+
}
|
|
2775
4485
|
}
|
|
2776
4486
|
function recordPending(key) {
|
|
2777
4487
|
const strategy = answers.conflictDecisions[key];
|
|
@@ -2831,7 +4541,8 @@ async function runProjectInit(options) {
|
|
|
2831
4541
|
record({
|
|
2832
4542
|
name: "tokens",
|
|
2833
4543
|
status: "ok",
|
|
2834
|
-
detail
|
|
4544
|
+
detail,
|
|
4545
|
+
changes: deriveTokensChanges(result, projectRoot)
|
|
2835
4546
|
});
|
|
2836
4547
|
} catch (err) {
|
|
2837
4548
|
recordFailure("tokens", err);
|
|
@@ -2863,7 +4574,8 @@ async function runProjectInit(options) {
|
|
|
2863
4574
|
record({
|
|
2864
4575
|
name: "skills",
|
|
2865
4576
|
status: "ok",
|
|
2866
|
-
detail: result.status === "installed" ? `added: ${result.addedSkillIds.join(", ") || "none"}; existing: ${result.skippedSkillIds.join(", ") || "none"}` : result.status
|
|
4577
|
+
detail: result.status === "installed" ? `added: ${result.addedSkillIds.join(", ") || "none"}; existing: ${result.skippedSkillIds.join(", ") || "none"}` : result.status,
|
|
4578
|
+
changes: deriveSkillsChanges(result, projectRoot)
|
|
2867
4579
|
});
|
|
2868
4580
|
} catch (err) {
|
|
2869
4581
|
recordFailure("skills", err);
|
|
@@ -2891,12 +4603,25 @@ async function runProjectInit(options) {
|
|
|
2891
4603
|
const result = await runGenerateAgentsMd({
|
|
2892
4604
|
projectRoot,
|
|
2893
4605
|
variant: answers.variant,
|
|
2894
|
-
skillIds: agentsMdSkillIds
|
|
4606
|
+
skillIds: agentsMdSkillIds,
|
|
4607
|
+
// Phase 2.B: when the user picked `merge-managed` on conflict, only
|
|
4608
|
+
// rewrite the `teamix-evo-skills` managed region so hand-written
|
|
4609
|
+
// sections survive the regenerate step. `overwrite` keeps the
|
|
4610
|
+
// historical full-rewrite default.
|
|
4611
|
+
mode: agentsMdDecision === "merge-managed" ? "merge-managed" : "overwrite"
|
|
2895
4612
|
});
|
|
2896
4613
|
record({
|
|
2897
4614
|
name: "agents-md",
|
|
2898
4615
|
status: "ok",
|
|
2899
|
-
detail: `${result.skillCount} skill index${result.missingSkillIds.length > 0 ? ` (missing SKILL.md: ${result.missingSkillIds.join(", ")})` : ""}
|
|
4616
|
+
detail: `${result.skillCount} skill index${result.missingSkillIds.length > 0 ? ` (missing SKILL.md: ${result.missingSkillIds.join(", ")})` : ""}`,
|
|
4617
|
+
changes: [
|
|
4618
|
+
{
|
|
4619
|
+
kind: result.backedUp ? "modified" : "created",
|
|
4620
|
+
path: toRelativePosix(result.path, projectRoot),
|
|
4621
|
+
step: "agents-md",
|
|
4622
|
+
detail: "skill-trigger fallback (ADR 0038)"
|
|
4623
|
+
}
|
|
4624
|
+
]
|
|
2900
4625
|
});
|
|
2901
4626
|
} catch (err) {
|
|
2902
4627
|
recordFailure("agents-md", err);
|
|
@@ -2906,6 +4631,7 @@ async function runProjectInit(options) {
|
|
|
2906
4631
|
const componentsJsonDecision = answers.conflictDecisions["components-json"];
|
|
2907
4632
|
const shadcnDecision = answers.conflictDecisions["shadcn-source"];
|
|
2908
4633
|
const skipUiInit = !answers.withUi || componentsJsonDecision === "skip";
|
|
4634
|
+
let uiDecisionRequired;
|
|
2909
4635
|
if (skipUiInit) {
|
|
2910
4636
|
record({
|
|
2911
4637
|
name: "ui-init",
|
|
@@ -2971,18 +4697,177 @@ async function runProjectInit(options) {
|
|
|
2971
4697
|
} else {
|
|
2972
4698
|
try {
|
|
2973
4699
|
const entries = await resolveUiEntries(options);
|
|
2974
|
-
const
|
|
4700
|
+
const { manifest } = await loadUiData("@teamix-evo/ui");
|
|
4701
|
+
const existingConfig = await readProjectConfig(projectRoot).catch(
|
|
4702
|
+
() => null
|
|
4703
|
+
);
|
|
4704
|
+
const aliases = existingConfig?.packages?.ui?.aliases ?? DEFAULT_UI_ALIASES;
|
|
4705
|
+
const conflictReport = await detectUiConflicts({
|
|
2975
4706
|
projectRoot,
|
|
2976
|
-
|
|
2977
|
-
|
|
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)`
|
|
4707
|
+
aliases,
|
|
4708
|
+
manifest
|
|
2985
4709
|
});
|
|
4710
|
+
if (conflictReport.shouldBlock) {
|
|
4711
|
+
let effectiveStrategy = options.uiConflictStrategy;
|
|
4712
|
+
if (!effectiveStrategy) {
|
|
4713
|
+
if (options.nonInteractive) {
|
|
4714
|
+
effectiveStrategy = "isolate-progressive";
|
|
4715
|
+
} else {
|
|
4716
|
+
uiDecisionRequired = {
|
|
4717
|
+
report: conflictReport,
|
|
4718
|
+
options: [
|
|
4719
|
+
{
|
|
4720
|
+
strategy: "isolate-progressive",
|
|
4721
|
+
label: "\u9694\u79BB + \u6E10\u8FDB\u8FC1\u79FB\uFF08\u63A8\u8350\uFF09",
|
|
4722
|
+
description: "\u5C06\u73B0\u6709 UI \u7EC4\u4EF6\u642C\u8FC1\u5230 shadcn-ui/ \u76EE\u5F55\uFF0C\u81EA\u52A8\u91CD\u5199 import \u8DEF\u5F84\uFF0C\u843D\u5730\u5168\u65B0 teamix-evo \u7EC4\u4EF6\u5230 ui/\uFF0C\u6309\u81EA\u5DF1\u8282\u594F\u9010\u6B65\u8FC1\u79FB\u3002"
|
|
4723
|
+
},
|
|
4724
|
+
{
|
|
4725
|
+
strategy: "isolate-aggressive",
|
|
4726
|
+
label: "\u9694\u79BB + \u4E3B\u52A8\u5347\u7EA7",
|
|
4727
|
+
description: "\u5728\u6E10\u8FDB\u8FC1\u79FB\u57FA\u7840\u4E0A\uFF0C\u7ACB\u5373\u751F\u6210\u5347\u7EA7 staging\uFF0C\u7531 teamix-evo-upgrade skill \u5F15\u5BFC\u5206\u6279\u66FF\u6362\u3002"
|
|
4728
|
+
},
|
|
4729
|
+
{
|
|
4730
|
+
strategy: "frozen-skip",
|
|
4731
|
+
label: "\u8DF3\u8FC7\u5DF2\u6709\u6587\u4EF6\uFF08\u65E7\u6A21\u5F0F\uFF09",
|
|
4732
|
+
description: "\u4FDD\u6301\u73B0\u6709\u884C\u4E3A\uFF1A\u5DF2\u6709\u7EC4\u4EF6\u6587\u4EF6\u4E0D\u8986\u76D6\uFF0C\u65B0\u7EC4\u4EF6\u6B63\u5E38\u5B89\u88C5\u3002\u4E0D\u63A8\u8350 \u2014\u2014 \u53EF\u80FD\u5BFC\u81F4\u98CE\u683C\u6DF7\u7528\u3002"
|
|
4733
|
+
}
|
|
4734
|
+
]
|
|
4735
|
+
};
|
|
4736
|
+
record({
|
|
4737
|
+
name: "ui-add",
|
|
4738
|
+
status: "skip",
|
|
4739
|
+
detail: `UI conflict detected (${conflictReport.conflictEntries.length} files), awaiting user decision`
|
|
4740
|
+
});
|
|
4741
|
+
effectiveStrategy = void 0;
|
|
4742
|
+
}
|
|
4743
|
+
}
|
|
4744
|
+
if (effectiveStrategy) {
|
|
4745
|
+
if (effectiveStrategy === "frozen-skip") {
|
|
4746
|
+
const addResult = await runUiAdd({
|
|
4747
|
+
projectRoot,
|
|
4748
|
+
ids: entries,
|
|
4749
|
+
overwrite: shadcnDecision === "overwrite"
|
|
4750
|
+
});
|
|
4751
|
+
record({
|
|
4752
|
+
name: "ui-add",
|
|
4753
|
+
status: "ok",
|
|
4754
|
+
detail: `${addResult.orderedIds.length} entries [frozen-skip] (${addResult.written} written, ${addResult.skipped} skipped)`,
|
|
4755
|
+
changes: deriveUiAddChanges(addResult, projectRoot)
|
|
4756
|
+
});
|
|
4757
|
+
} else {
|
|
4758
|
+
const isolateResult = await runUiIsolate({
|
|
4759
|
+
projectRoot,
|
|
4760
|
+
aliases,
|
|
4761
|
+
conflictReport
|
|
4762
|
+
});
|
|
4763
|
+
logger.info(
|
|
4764
|
+
` isolated ${isolateResult.movedFiles.length} files, rewrote imports in ${isolateResult.importRewrites.size} files`
|
|
4765
|
+
);
|
|
4766
|
+
const addResult = await runUiAdd({
|
|
4767
|
+
projectRoot,
|
|
4768
|
+
ids: entries,
|
|
4769
|
+
overwrite: true
|
|
4770
|
+
// after isolation, ui/ is empty → safe to write
|
|
4771
|
+
});
|
|
4772
|
+
let stagingDetail = "";
|
|
4773
|
+
if (effectiveStrategy === "isolate-aggressive") {
|
|
4774
|
+
try {
|
|
4775
|
+
const upgradeResult = await runUiUpgrade({
|
|
4776
|
+
projectRoot,
|
|
4777
|
+
category: "ui",
|
|
4778
|
+
trigger: "ui-upgrade"
|
|
4779
|
+
});
|
|
4780
|
+
if (upgradeResult.status === "staged") {
|
|
4781
|
+
stagingDetail = `; upgrade staging generated (${upgradeResult.manifest.entries.length} entries)`;
|
|
4782
|
+
}
|
|
4783
|
+
} catch (upgradeErr) {
|
|
4784
|
+
stagingDetail = `; upgrade staging failed: ${getErrorMessage(
|
|
4785
|
+
upgradeErr
|
|
4786
|
+
)}`;
|
|
4787
|
+
}
|
|
4788
|
+
}
|
|
4789
|
+
record({
|
|
4790
|
+
name: "ui-add",
|
|
4791
|
+
status: "ok",
|
|
4792
|
+
detail: `${addResult.orderedIds.length} entries [${effectiveStrategy}] (${addResult.written} written, ${addResult.skipped} skipped; isolated ${isolateResult.movedFiles.length} files${stagingDetail})`,
|
|
4793
|
+
changes: deriveUiAddChanges(addResult, projectRoot)
|
|
4794
|
+
});
|
|
4795
|
+
try {
|
|
4796
|
+
const deps = addResult.npmDependencies;
|
|
4797
|
+
if (deps && Object.keys(deps).length > 0) {
|
|
4798
|
+
await installProjectDeps({
|
|
4799
|
+
projectRoot,
|
|
4800
|
+
npmDependencies: deps,
|
|
4801
|
+
skipInstall: options.skipInstall
|
|
4802
|
+
});
|
|
4803
|
+
}
|
|
4804
|
+
} catch (depsErr) {
|
|
4805
|
+
logger.warn(
|
|
4806
|
+
` deps install failed (non-fatal): ${getErrorMessage(
|
|
4807
|
+
depsErr
|
|
4808
|
+
)}`
|
|
4809
|
+
);
|
|
4810
|
+
}
|
|
4811
|
+
let residualCount = 0;
|
|
4812
|
+
try {
|
|
4813
|
+
const residualReport = await detectResidualImports(
|
|
4814
|
+
projectRoot
|
|
4815
|
+
);
|
|
4816
|
+
residualCount = residualReport.entries.length;
|
|
4817
|
+
if (residualCount > 0) {
|
|
4818
|
+
logger.warn(
|
|
4819
|
+
` \u26A0\uFE0F ${residualCount} residual import(s) to shadcn-ui/ detected \u2014 see lint warnings`
|
|
4820
|
+
);
|
|
4821
|
+
}
|
|
4822
|
+
} catch {
|
|
4823
|
+
}
|
|
4824
|
+
try {
|
|
4825
|
+
const planPath = await generateMigrationPlan({
|
|
4826
|
+
projectRoot,
|
|
4827
|
+
strategy: effectiveStrategy,
|
|
4828
|
+
isolateResult,
|
|
4829
|
+
addResult,
|
|
4830
|
+
residualImports: residualCount
|
|
4831
|
+
});
|
|
4832
|
+
logger.info(
|
|
4833
|
+
` wrote migration plan: ${path28.relative(
|
|
4834
|
+
projectRoot,
|
|
4835
|
+
planPath
|
|
4836
|
+
)}`
|
|
4837
|
+
);
|
|
4838
|
+
} catch {
|
|
4839
|
+
}
|
|
4840
|
+
}
|
|
4841
|
+
}
|
|
4842
|
+
} else {
|
|
4843
|
+
const addResult = await runUiAdd({
|
|
4844
|
+
projectRoot,
|
|
4845
|
+
ids: entries,
|
|
4846
|
+
overwrite: shadcnDecision === "overwrite"
|
|
4847
|
+
});
|
|
4848
|
+
record({
|
|
4849
|
+
name: "ui-add",
|
|
4850
|
+
status: "ok",
|
|
4851
|
+
detail: `${addResult.orderedIds.length} entries (${addResult.written} written, ${addResult.skipped} skipped)`,
|
|
4852
|
+
changes: deriveUiAddChanges(addResult, projectRoot)
|
|
4853
|
+
});
|
|
4854
|
+
try {
|
|
4855
|
+
const deps = addResult.npmDependencies;
|
|
4856
|
+
if (deps && Object.keys(deps).length > 0) {
|
|
4857
|
+
await installProjectDeps({
|
|
4858
|
+
projectRoot,
|
|
4859
|
+
npmDependencies: deps,
|
|
4860
|
+
skipInstall: options.skipInstall
|
|
4861
|
+
});
|
|
4862
|
+
}
|
|
4863
|
+
} catch (depsErr) {
|
|
4864
|
+
logger.warn(
|
|
4865
|
+
` deps install failed (non-fatal): ${getErrorMessage(
|
|
4866
|
+
depsErr
|
|
4867
|
+
)}`
|
|
4868
|
+
);
|
|
4869
|
+
}
|
|
4870
|
+
}
|
|
2986
4871
|
} catch (err) {
|
|
2987
4872
|
recordFailure("ui-add", err);
|
|
2988
4873
|
}
|
|
@@ -3005,771 +4890,449 @@ async function runProjectInit(options) {
|
|
|
3005
4890
|
});
|
|
3006
4891
|
} else {
|
|
3007
4892
|
try {
|
|
4893
|
+
const eslintStrategy = answers.conflictDecisions["eslint-config"];
|
|
4894
|
+
const stylelintStrategy = answers.conflictDecisions["stylelint-config"];
|
|
4895
|
+
const eslintExistingPaths = options.legacyEslintPaths ?? [];
|
|
4896
|
+
const stylelintExistingPaths = options.legacyStylelintPaths ?? [];
|
|
3008
4897
|
const result = await runLintInit({
|
|
3009
4898
|
projectRoot,
|
|
3010
|
-
skipInstall: options.skipInstall ?? false
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
-
|
|
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)
|
|
4899
|
+
skipInstall: options.skipInstall ?? false,
|
|
4900
|
+
eslintStrategy: eslintStrategy === "merge" || eslintStrategy === "backup-overwrite" || eslintStrategy === "skip" || eslintStrategy === "overwrite" ? eslintStrategy : "overwrite",
|
|
4901
|
+
stylelintStrategy: stylelintStrategy === "merge" || stylelintStrategy === "backup-overwrite" || stylelintStrategy === "skip" || stylelintStrategy === "overwrite" ? stylelintStrategy : "overwrite",
|
|
4902
|
+
eslintExistingPaths,
|
|
4903
|
+
stylelintExistingPaths
|
|
3196
4904
|
});
|
|
3197
|
-
|
|
3198
|
-
|
|
3199
|
-
|
|
3200
|
-
|
|
3201
|
-
|
|
3202
|
-
|
|
3203
|
-
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
|
|
3207
|
-
|
|
3208
|
-
|
|
3209
|
-
|
|
4905
|
+
const detailParts = [];
|
|
4906
|
+
if (result.status === "installed") {
|
|
4907
|
+
detailParts.push(
|
|
4908
|
+
`eslint=${result.eslint}, stylelint=${result.stylelint}`
|
|
4909
|
+
);
|
|
4910
|
+
if (result.eslintMergeRequested) {
|
|
4911
|
+
detailParts.push("eslint:AI-merge-pending");
|
|
4912
|
+
}
|
|
4913
|
+
if (result.stylelintMergeRequested) {
|
|
4914
|
+
detailParts.push("stylelint:AI-merge-pending");
|
|
4915
|
+
}
|
|
4916
|
+
if (result.eslintSkipped) detailParts.push("eslint:skipped");
|
|
4917
|
+
if (result.stylelintSkipped) detailParts.push("stylelint:skipped");
|
|
4918
|
+
if (result.stylelintIgnoreFilesWarning) {
|
|
4919
|
+
detailParts.push("stylelint:ignoreFiles-warning");
|
|
4920
|
+
}
|
|
4921
|
+
} else {
|
|
4922
|
+
detailParts.push(result.status);
|
|
3210
4923
|
}
|
|
3211
|
-
|
|
3212
|
-
|
|
3213
|
-
|
|
4924
|
+
record({
|
|
4925
|
+
name: "lint",
|
|
4926
|
+
status: "ok",
|
|
4927
|
+
detail: detailParts.join(" / "),
|
|
4928
|
+
changes: deriveLintChanges(result)
|
|
3214
4929
|
});
|
|
3215
|
-
|
|
4930
|
+
} catch (err) {
|
|
4931
|
+
recordFailure("lint", err);
|
|
3216
4932
|
}
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
|
|
3221
|
-
|
|
3222
|
-
|
|
3223
|
-
|
|
3224
|
-
|
|
4933
|
+
}
|
|
4934
|
+
recordPending("tailwind-config");
|
|
4935
|
+
recordPending("index-css");
|
|
4936
|
+
const GITIGNORE_MARKER_START = "# >>> teamix-evo:managed >>>";
|
|
4937
|
+
const GITIGNORE_MARKER_END = "# <<< teamix-evo:managed <<<";
|
|
4938
|
+
const GITIGNORE_RULES = [
|
|
4939
|
+
".teamix-evo/.backups/",
|
|
4940
|
+
".teamix-evo/.upgrade-staging/",
|
|
4941
|
+
".teamix-evo/.upgrade-hints/"
|
|
4942
|
+
];
|
|
4943
|
+
if (dryRun) {
|
|
4944
|
+
record({
|
|
4945
|
+
name: "gitignore",
|
|
4946
|
+
status: "planned",
|
|
4947
|
+
detail: "append teamix-evo runtime artifact rules"
|
|
4948
|
+
});
|
|
4949
|
+
} else {
|
|
4950
|
+
try {
|
|
4951
|
+
try {
|
|
4952
|
+
await fsNode.access(projectRoot);
|
|
4953
|
+
} catch {
|
|
4954
|
+
record({
|
|
4955
|
+
name: "gitignore",
|
|
4956
|
+
status: "skip",
|
|
4957
|
+
detail: "projectRoot does not exist"
|
|
4958
|
+
});
|
|
4959
|
+
throw { __skip: true };
|
|
4960
|
+
}
|
|
4961
|
+
const giPath = path28.join(projectRoot, ".gitignore");
|
|
4962
|
+
let giContent = "";
|
|
4963
|
+
try {
|
|
4964
|
+
giContent = await fsNode.readFile(giPath, "utf-8");
|
|
4965
|
+
} catch {
|
|
4966
|
+
}
|
|
4967
|
+
if (giContent.includes(GITIGNORE_MARKER_START)) {
|
|
4968
|
+
record({
|
|
4969
|
+
name: "gitignore",
|
|
4970
|
+
status: "skip",
|
|
4971
|
+
detail: "teamix-evo markers already present"
|
|
4972
|
+
});
|
|
4973
|
+
} else {
|
|
4974
|
+
const block = [
|
|
4975
|
+
"",
|
|
4976
|
+
GITIGNORE_MARKER_START,
|
|
4977
|
+
...GITIGNORE_RULES,
|
|
4978
|
+
GITIGNORE_MARKER_END,
|
|
4979
|
+
""
|
|
4980
|
+
].join("\n");
|
|
4981
|
+
const separator = giContent.length > 0 && !giContent.endsWith("\n") ? "\n" : "";
|
|
4982
|
+
await fsNode.writeFile(giPath, giContent + separator + block, "utf-8");
|
|
4983
|
+
record({
|
|
4984
|
+
name: "gitignore",
|
|
4985
|
+
status: "ok",
|
|
4986
|
+
detail: GITIGNORE_RULES.length + " rules appended",
|
|
4987
|
+
changes: [
|
|
4988
|
+
{
|
|
4989
|
+
kind: "modified",
|
|
4990
|
+
path: ".gitignore",
|
|
4991
|
+
step: "gitignore",
|
|
4992
|
+
detail: "teamix-evo runtime artifact rules"
|
|
4993
|
+
}
|
|
4994
|
+
]
|
|
3225
4995
|
});
|
|
3226
4996
|
}
|
|
4997
|
+
} catch (err) {
|
|
4998
|
+
if (err && typeof err === "object" && "__skip" in err) {
|
|
4999
|
+
} else {
|
|
5000
|
+
recordFailure("gitignore", err);
|
|
5001
|
+
}
|
|
3227
5002
|
}
|
|
3228
|
-
refreshedResources.push(resource);
|
|
3229
5003
|
}
|
|
3230
|
-
if (
|
|
3231
|
-
|
|
3232
|
-
|
|
3233
|
-
|
|
3234
|
-
|
|
3235
|
-
|
|
3236
|
-
|
|
5004
|
+
if (!dryRun) {
|
|
5005
|
+
try {
|
|
5006
|
+
const backupsAfter = await listBackupOriginals(projectRoot);
|
|
5007
|
+
const newlyBackedUp = diffBackupSet(backupsBefore, backupsAfter);
|
|
5008
|
+
if (newlyBackedUp.size > 0) {
|
|
5009
|
+
for (const change of allChanges) {
|
|
5010
|
+
if (change.kind === "created" && newlyBackedUp.has(change.path)) {
|
|
5011
|
+
change.kind = "modified";
|
|
5012
|
+
}
|
|
5013
|
+
}
|
|
5014
|
+
for (const rel2 of newlyBackedUp) {
|
|
5015
|
+
allChanges.push({
|
|
5016
|
+
kind: "backed-up",
|
|
5017
|
+
path: rel2,
|
|
5018
|
+
step: "backup",
|
|
5019
|
+
detail: ".teamix-evo/.backups/<\u540C\u8DEF\u5F84>.<isoTs>.bak"
|
|
5020
|
+
});
|
|
5021
|
+
}
|
|
5022
|
+
}
|
|
5023
|
+
} catch {
|
|
3237
5024
|
}
|
|
3238
|
-
return {
|
|
3239
|
-
status: "up-to-date",
|
|
3240
|
-
packageName,
|
|
3241
|
-
variant: currentVariant,
|
|
3242
|
-
version: currentVersion,
|
|
3243
|
-
frozenDrift
|
|
3244
|
-
};
|
|
3245
5025
|
}
|
|
3246
|
-
const
|
|
3247
|
-
|
|
3248
|
-
|
|
3249
|
-
|
|
3250
|
-
|
|
3251
|
-
|
|
3252
|
-
|
|
3253
|
-
},
|
|
3254
|
-
packageVersion: catalog.version,
|
|
3255
|
-
linked: variantEntry.linked,
|
|
3256
|
-
installedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
5026
|
+
const status = dryRun ? "dry-run" : steps.some((s) => s.status === "fail") ? "partial" : "installed";
|
|
5027
|
+
const out = {
|
|
5028
|
+
status,
|
|
5029
|
+
steps,
|
|
5030
|
+
pendingConflictWork: pending,
|
|
5031
|
+
changes: allChanges,
|
|
5032
|
+
snapshot
|
|
3257
5033
|
};
|
|
3258
|
-
|
|
3259
|
-
|
|
3260
|
-
|
|
3261
|
-
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
5034
|
+
if (snapshotError) out.snapshotError = snapshotError;
|
|
5035
|
+
if (uiDecisionRequired) out.uiDecisionRequired = uiDecisionRequired;
|
|
5036
|
+
if (firstFailure.value) {
|
|
5037
|
+
out.resumeHint = {
|
|
5038
|
+
failedAt: firstFailure.value.step,
|
|
5039
|
+
completed: steps.filter((s) => s.status === "ok").map((s) => s.name),
|
|
5040
|
+
failed: steps.filter((s) => s.status === "fail").map((s) => s.name),
|
|
5041
|
+
error: firstFailure.value.error,
|
|
5042
|
+
// Every sub-step is idempotent (returns `already-initialized` when its
|
|
5043
|
+
// state file already exists), so a plain re-run resumes from the
|
|
5044
|
+
// failed step. A richer --resume model lands with batch 3 (lock
|
|
5045
|
+
// snapshot + restore).
|
|
5046
|
+
resumeCommand: "teamix-evo init"
|
|
3270
5047
|
};
|
|
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
5048
|
}
|
|
3291
|
-
|
|
3292
|
-
|
|
3293
|
-
|
|
3294
|
-
|
|
3295
|
-
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
|
|
3300
|
-
|
|
3301
|
-
|
|
3302
|
-
|
|
3303
|
-
|
|
3304
|
-
|
|
3305
|
-
|
|
3306
|
-
|
|
3307
|
-
|
|
3308
|
-
|
|
3309
|
-
if (consumer === consumerBasename) return upstream;
|
|
5049
|
+
if (!dryRun) {
|
|
5050
|
+
try {
|
|
5051
|
+
const checklistContent = renderInitChecklist({
|
|
5052
|
+
variant: answers.variant,
|
|
5053
|
+
status,
|
|
5054
|
+
steps
|
|
5055
|
+
});
|
|
5056
|
+
const checklistPath = path28.join(
|
|
5057
|
+
projectRoot,
|
|
5058
|
+
".teamix-evo",
|
|
5059
|
+
"init-checklist.md"
|
|
5060
|
+
);
|
|
5061
|
+
await fsNode.mkdir(path28.dirname(checklistPath), { recursive: true });
|
|
5062
|
+
await fsNode.writeFile(checklistPath, checklistContent, "utf-8");
|
|
5063
|
+
logger.info(" wrote .teamix-evo/init-checklist.md");
|
|
5064
|
+
} catch {
|
|
5065
|
+
logger.warn(" failed to write init-checklist.md (non-fatal)");
|
|
5066
|
+
}
|
|
3310
5067
|
}
|
|
3311
|
-
return
|
|
5068
|
+
return out;
|
|
3312
5069
|
}
|
|
3313
5070
|
|
|
3314
|
-
// src/core/
|
|
3315
|
-
import * as
|
|
3316
|
-
import
|
|
3317
|
-
|
|
3318
|
-
|
|
3319
|
-
|
|
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
|
-
}
|
|
5071
|
+
// src/core/project-update.ts
|
|
5072
|
+
import * as path31 from "path";
|
|
5073
|
+
import {
|
|
5074
|
+
loadTokensPackageManifest as loadTokensPackageManifest3,
|
|
5075
|
+
getVariantEntry as getVariantEntry3
|
|
5076
|
+
} from "@teamix-evo/registry";
|
|
3400
5077
|
|
|
3401
|
-
// src/core/
|
|
3402
|
-
import * as
|
|
3403
|
-
import
|
|
5078
|
+
// src/core/tokens-update.ts
|
|
5079
|
+
import * as path30 from "path";
|
|
5080
|
+
import * as fs22 from "fs/promises";
|
|
3404
5081
|
import {
|
|
3405
|
-
|
|
3406
|
-
|
|
5082
|
+
loadTokensPackageManifest as loadTokensPackageManifest2,
|
|
5083
|
+
getVariantEntry as getVariantEntry2
|
|
3407
5084
|
} from "@teamix-evo/registry";
|
|
3408
5085
|
|
|
3409
|
-
// src/core/
|
|
3410
|
-
import * as
|
|
5086
|
+
// src/core/upgrade-hints.ts
|
|
5087
|
+
import * as path29 from "path";
|
|
3411
5088
|
var TEAMIX_DIR4 = ".teamix-evo";
|
|
3412
|
-
var
|
|
3413
|
-
var PACKAGE_NAME2 = {
|
|
3414
|
-
ui: "@teamix-evo/ui",
|
|
3415
|
-
"biz-ui": "@teamix-evo/biz-ui"
|
|
3416
|
-
};
|
|
5089
|
+
var HINTS_DIR = ".upgrade-hints";
|
|
3417
5090
|
function isoToFsSafe3(iso) {
|
|
3418
5091
|
return iso.replace(/[:.]/g, "-");
|
|
3419
5092
|
}
|
|
3420
|
-
async function
|
|
3421
|
-
|
|
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;
|
|
5093
|
+
async function writeTokensUpgradeHint(options) {
|
|
5094
|
+
if (options.renames.length === 0) return null;
|
|
3428
5095
|
const isoTs = options.isoTs ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
3429
5096
|
const fsTs = isoToFsSafe3(isoTs);
|
|
3430
|
-
const
|
|
5097
|
+
const filename = `tokens-${fsTs}.json`;
|
|
5098
|
+
const target = path29.join(
|
|
3431
5099
|
options.projectRoot,
|
|
3432
5100
|
TEAMIX_DIR4,
|
|
3433
|
-
|
|
3434
|
-
|
|
3435
|
-
);
|
|
3436
|
-
const entryMap = new Map(
|
|
3437
|
-
options.manifest.entries.map((e) => [e.id, e])
|
|
5101
|
+
HINTS_DIR,
|
|
5102
|
+
filename
|
|
3438
5103
|
);
|
|
3439
|
-
const
|
|
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 = {
|
|
5104
|
+
const payload = {
|
|
3472
5105
|
schemaVersion: 1,
|
|
3473
5106
|
ts: isoTs,
|
|
3474
|
-
package:
|
|
5107
|
+
package: "tokens",
|
|
3475
5108
|
trigger: options.trigger,
|
|
3476
|
-
|
|
3477
|
-
|
|
3478
|
-
|
|
3479
|
-
|
|
3480
|
-
|
|
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
|
|
5109
|
+
fromVariant: options.fromVariant,
|
|
5110
|
+
toVariant: options.toVariant,
|
|
5111
|
+
fromVersion: options.fromVersion,
|
|
5112
|
+
toVersion: options.toVersion,
|
|
5113
|
+
renames: options.renames
|
|
3553
5114
|
};
|
|
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);
|
|
5115
|
+
await writeFileSafe(target, JSON.stringify(payload, null, 2) + "\n");
|
|
3566
5116
|
return {
|
|
3567
|
-
|
|
3568
|
-
|
|
3569
|
-
|
|
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
|
-
}
|
|
5117
|
+
path: target,
|
|
5118
|
+
ts: fsTs,
|
|
5119
|
+
renameCount: options.renames.length
|
|
3582
5120
|
};
|
|
3583
5121
|
}
|
|
3584
|
-
|
|
3585
|
-
|
|
3586
|
-
|
|
3587
|
-
|
|
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
|
-
};
|
|
5122
|
+
function selectApplicableRenames(renames, fromVersion, toVersion) {
|
|
5123
|
+
return renames.filter(
|
|
5124
|
+
(r) => compareSemver2(r.sinceVersion, fromVersion) > 0 && compareSemver2(r.sinceVersion, toVersion) <= 0
|
|
5125
|
+
).sort((a, b) => compareSemver2(a.sinceVersion, b.sinceVersion));
|
|
3605
5126
|
}
|
|
3606
|
-
function
|
|
3607
|
-
|
|
3608
|
-
|
|
5127
|
+
function compareSemver2(a, b) {
|
|
5128
|
+
const [aMain = "", aRest = ""] = a.split("-", 2);
|
|
5129
|
+
const [bMain = "", bRest = ""] = b.split("-", 2);
|
|
5130
|
+
const aParts = aMain.split(".").map((n) => Number.parseInt(n, 10));
|
|
5131
|
+
const bParts = bMain.split(".").map((n) => Number.parseInt(n, 10));
|
|
5132
|
+
for (let i = 0; i < Math.max(aParts.length, bParts.length); i++) {
|
|
5133
|
+
const ai = aParts[i] ?? 0;
|
|
5134
|
+
const bi = bParts[i] ?? 0;
|
|
5135
|
+
if (ai !== bi) return ai - bi;
|
|
3609
5136
|
}
|
|
3610
|
-
|
|
3611
|
-
|
|
3612
|
-
|
|
3613
|
-
|
|
3614
|
-
|
|
3615
|
-
|
|
3616
|
-
|
|
3617
|
-
|
|
3618
|
-
|
|
3619
|
-
|
|
3620
|
-
|
|
3621
|
-
|
|
3622
|
-
|
|
3623
|
-
|
|
3624
|
-
|
|
3625
|
-
|
|
3626
|
-
|
|
3627
|
-
|
|
3628
|
-
|
|
3629
|
-
|
|
3630
|
-
riskLevel = "upgradable-low";
|
|
5137
|
+
if (aRest === "" && bRest !== "") return 1;
|
|
5138
|
+
if (aRest !== "" && bRest === "") return -1;
|
|
5139
|
+
return aRest.localeCompare(bRest, void 0, { numeric: true });
|
|
5140
|
+
}
|
|
5141
|
+
|
|
5142
|
+
// src/core/managed-merge.ts
|
|
5143
|
+
import { hasManagedRegion as hasManagedRegion3, replaceManagedRegion as replaceManagedRegion3 } from "@teamix-evo/registry";
|
|
5144
|
+
function mergeManagedRegions(upstreamContent, consumerContent) {
|
|
5145
|
+
let updated = consumerContent;
|
|
5146
|
+
const re = /<!-- teamix-evo:managed:start id="([^"]+)" -->([\s\S]*?)<!-- teamix-evo:managed:end(?: id="\1")? -->/g;
|
|
5147
|
+
let match;
|
|
5148
|
+
while ((match = re.exec(upstreamContent)) !== null) {
|
|
5149
|
+
const id = match[1];
|
|
5150
|
+
const body = match[2].replace(/^\n/, "").replace(/\n$/, "");
|
|
5151
|
+
if (!hasManagedRegion3(updated, id)) {
|
|
5152
|
+
throw new Error(
|
|
5153
|
+
`Managed region "${id}" missing from consumer file \u2014 refusing to silently rewrite (ADR 0003).`
|
|
5154
|
+
);
|
|
5155
|
+
}
|
|
5156
|
+
updated = replaceManagedRegion3(updated, id, body);
|
|
3631
5157
|
}
|
|
3632
|
-
return
|
|
5158
|
+
return updated;
|
|
3633
5159
|
}
|
|
3634
|
-
|
|
3635
|
-
|
|
3636
|
-
|
|
3637
|
-
|
|
3638
|
-
|
|
3639
|
-
|
|
5160
|
+
|
|
5161
|
+
// src/core/tokens-update.ts
|
|
5162
|
+
var DEFAULT_TOKENS_PACKAGE2 = "@teamix-evo/tokens";
|
|
5163
|
+
var CONSUMER_BASENAME_BY_UPSTREAM = {
|
|
5164
|
+
"theme.css": "tokens.theme.css",
|
|
5165
|
+
"overrides.css": "tokens.overrides.css"
|
|
5166
|
+
};
|
|
5167
|
+
async function runTokensUpdate(options) {
|
|
5168
|
+
const { projectRoot } = options;
|
|
5169
|
+
const packageName = options.packageName ?? DEFAULT_TOKENS_PACKAGE2;
|
|
5170
|
+
const config = await readProjectConfig(projectRoot);
|
|
5171
|
+
if (!config?.packages?.tokens) {
|
|
5172
|
+
return { status: "not-initialized" };
|
|
5173
|
+
}
|
|
5174
|
+
const currentVariant = config.packages.tokens.variant;
|
|
5175
|
+
const currentVersion = config.packages.tokens.version;
|
|
5176
|
+
const packageRoot = options.packageRoot ?? resolveTokensPackageRoot(packageName);
|
|
5177
|
+
const catalog = await loadTokensPackageManifest2(packageRoot);
|
|
5178
|
+
const variantEntry = getVariantEntry2(catalog, currentVariant);
|
|
5179
|
+
if (!variantEntry) {
|
|
5180
|
+
throw new Error(
|
|
5181
|
+
`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.`
|
|
5182
|
+
);
|
|
3640
5183
|
}
|
|
3641
|
-
|
|
3642
|
-
|
|
5184
|
+
const upstreamByBasename = /* @__PURE__ */ new Map();
|
|
5185
|
+
for (const fileRel of variantEntry.files) {
|
|
5186
|
+
upstreamByBasename.set(
|
|
5187
|
+
path30.basename(fileRel),
|
|
5188
|
+
path30.join(packageRoot, fileRel)
|
|
5189
|
+
);
|
|
3643
5190
|
}
|
|
3644
|
-
|
|
3645
|
-
|
|
3646
|
-
|
|
3647
|
-
|
|
3648
|
-
|
|
3649
|
-
|
|
3650
|
-
|
|
3651
|
-
|
|
3652
|
-
|
|
5191
|
+
const prior = await readInstalledManifest(projectRoot) ?? {
|
|
5192
|
+
schemaVersion: 1,
|
|
5193
|
+
installed: []
|
|
5194
|
+
};
|
|
5195
|
+
const installedIdx = prior.installed.findIndex(
|
|
5196
|
+
(p) => p.package === packageName
|
|
5197
|
+
);
|
|
5198
|
+
const priorResources = installedIdx >= 0 ? prior.installed[installedIdx].resources : [];
|
|
5199
|
+
const rewritten = [];
|
|
5200
|
+
const managedReplaced = [];
|
|
5201
|
+
const preserved = [];
|
|
5202
|
+
const frozenDrift = [];
|
|
5203
|
+
const refreshedResources = [];
|
|
5204
|
+
for (const resource of priorResources) {
|
|
5205
|
+
const consumerAbs = path30.isAbsolute(resource.target) ? resource.target : path30.join(projectRoot, resource.target);
|
|
5206
|
+
const consumerBasename = path30.basename(resource.target);
|
|
5207
|
+
const upstreamBasename = lookupUpstreamBasename(consumerBasename);
|
|
5208
|
+
const upstreamAbs = upstreamBasename ? upstreamByBasename.get(upstreamBasename) : void 0;
|
|
5209
|
+
if (resource.strategy === "regenerable") {
|
|
5210
|
+
if (!upstreamAbs) {
|
|
5211
|
+
refreshedResources.push(resource);
|
|
5212
|
+
continue;
|
|
5213
|
+
}
|
|
5214
|
+
const content = await fs22.readFile(upstreamAbs, "utf-8");
|
|
5215
|
+
await writeFileSafe(consumerAbs, content);
|
|
5216
|
+
rewritten.push(resource.target);
|
|
5217
|
+
refreshedResources.push({
|
|
5218
|
+
...resource,
|
|
5219
|
+
hash: computeHash(content)
|
|
5220
|
+
});
|
|
5221
|
+
continue;
|
|
3653
5222
|
}
|
|
3654
|
-
|
|
3655
|
-
|
|
3656
|
-
|
|
3657
|
-
|
|
3658
|
-
|
|
3659
|
-
|
|
3660
|
-
|
|
3661
|
-
|
|
3662
|
-
|
|
3663
|
-
|
|
3664
|
-
|
|
3665
|
-
|
|
3666
|
-
|
|
3667
|
-
|
|
3668
|
-
|
|
5223
|
+
if (resource.strategy === "managed") {
|
|
5224
|
+
if (!upstreamAbs || !await fileExists(consumerAbs)) {
|
|
5225
|
+
refreshedResources.push(resource);
|
|
5226
|
+
continue;
|
|
5227
|
+
}
|
|
5228
|
+
const upstreamContent = await fs22.readFile(upstreamAbs, "utf-8");
|
|
5229
|
+
const consumerContent = await fs22.readFile(consumerAbs, "utf-8");
|
|
5230
|
+
const merged = mergeManagedRegions(upstreamContent, consumerContent);
|
|
5231
|
+
if (merged !== consumerContent) {
|
|
5232
|
+
await writeFileSafe(consumerAbs, merged);
|
|
5233
|
+
managedReplaced.push(resource.target);
|
|
5234
|
+
}
|
|
5235
|
+
refreshedResources.push({
|
|
5236
|
+
...resource,
|
|
5237
|
+
hash: computeHash(merged)
|
|
5238
|
+
});
|
|
5239
|
+
continue;
|
|
3669
5240
|
}
|
|
3670
|
-
|
|
3671
|
-
|
|
3672
|
-
|
|
3673
|
-
|
|
3674
|
-
|
|
3675
|
-
|
|
3676
|
-
|
|
3677
|
-
|
|
3678
|
-
|
|
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
|
-
}
|
|
5241
|
+
if (await fileExists(consumerAbs)) preserved.push(resource.target);
|
|
5242
|
+
if (upstreamAbs) {
|
|
5243
|
+
const upstreamContent = await fs22.readFile(upstreamAbs, "utf-8");
|
|
5244
|
+
const upstreamHash = computeHash(upstreamContent);
|
|
5245
|
+
if (resource.hash && upstreamHash !== resource.hash) {
|
|
5246
|
+
frozenDrift.push({
|
|
5247
|
+
target: resource.target,
|
|
5248
|
+
reason: "upstream-changed"
|
|
5249
|
+
});
|
|
3690
5250
|
}
|
|
3691
5251
|
}
|
|
5252
|
+
refreshedResources.push(resource);
|
|
3692
5253
|
}
|
|
3693
|
-
|
|
3694
|
-
|
|
3695
|
-
|
|
3696
|
-
|
|
3697
|
-
|
|
3698
|
-
|
|
3699
|
-
|
|
3700
|
-
|
|
3701
|
-
|
|
3702
|
-
|
|
3703
|
-
|
|
5254
|
+
if (variantEntry.version === currentVersion) {
|
|
5255
|
+
if (installedIdx >= 0) {
|
|
5256
|
+
prior.installed[installedIdx] = {
|
|
5257
|
+
...prior.installed[installedIdx],
|
|
5258
|
+
resources: refreshedResources
|
|
5259
|
+
};
|
|
5260
|
+
await writeInstalledManifest(projectRoot, prior);
|
|
5261
|
+
}
|
|
5262
|
+
return {
|
|
5263
|
+
status: "up-to-date",
|
|
5264
|
+
packageName,
|
|
5265
|
+
variant: currentVariant,
|
|
5266
|
+
version: currentVersion,
|
|
5267
|
+
frozenDrift
|
|
5268
|
+
};
|
|
3704
5269
|
}
|
|
3705
|
-
|
|
3706
|
-
|
|
3707
|
-
|
|
3708
|
-
|
|
3709
|
-
|
|
3710
|
-
|
|
3711
|
-
|
|
5270
|
+
const lock = {
|
|
5271
|
+
schemaVersion: 1,
|
|
5272
|
+
variant: {
|
|
5273
|
+
name: variantEntry.name,
|
|
5274
|
+
displayName: variantEntry.displayName,
|
|
5275
|
+
version: variantEntry.version,
|
|
5276
|
+
from: packageName
|
|
5277
|
+
},
|
|
5278
|
+
packageVersion: catalog.version,
|
|
5279
|
+
linked: variantEntry.linked,
|
|
5280
|
+
installedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
5281
|
+
};
|
|
5282
|
+
await writeFileSafe(
|
|
5283
|
+
path30.join(projectRoot, ".teamix-evo", "tokens-lock.json"),
|
|
5284
|
+
JSON.stringify(lock, null, 2) + "\n"
|
|
5285
|
+
);
|
|
5286
|
+
config.packages.tokens.version = variantEntry.version;
|
|
5287
|
+
await writeProjectConfig(projectRoot, config);
|
|
5288
|
+
if (installedIdx >= 0) {
|
|
5289
|
+
prior.installed[installedIdx] = {
|
|
5290
|
+
...prior.installed[installedIdx],
|
|
5291
|
+
version: variantEntry.version,
|
|
5292
|
+
installedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5293
|
+
resources: refreshedResources
|
|
5294
|
+
};
|
|
5295
|
+
await writeInstalledManifest(projectRoot, prior);
|
|
3712
5296
|
}
|
|
3713
|
-
|
|
3714
|
-
|
|
3715
|
-
|
|
3716
|
-
|
|
3717
|
-
|
|
3718
|
-
|
|
3719
|
-
|
|
3720
|
-
|
|
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({
|
|
5297
|
+
const renames = selectApplicableRenames(
|
|
5298
|
+
variantEntry.renames ?? [],
|
|
5299
|
+
currentVersion,
|
|
5300
|
+
variantEntry.version
|
|
5301
|
+
);
|
|
5302
|
+
let hintPath;
|
|
5303
|
+
if (renames.length > 0) {
|
|
5304
|
+
const hint = await writeTokensUpgradeHint({
|
|
3728
5305
|
projectRoot,
|
|
3729
|
-
|
|
3730
|
-
|
|
3731
|
-
|
|
3732
|
-
|
|
3733
|
-
|
|
3734
|
-
|
|
3735
|
-
onlyIds
|
|
5306
|
+
trigger: "update",
|
|
5307
|
+
fromVariant: currentVariant,
|
|
5308
|
+
toVariant: currentVariant,
|
|
5309
|
+
fromVersion: currentVersion,
|
|
5310
|
+
toVersion: variantEntry.version,
|
|
5311
|
+
renames
|
|
3736
5312
|
});
|
|
5313
|
+
if (hint) hintPath = hint.path;
|
|
3737
5314
|
}
|
|
3738
|
-
|
|
3739
|
-
|
|
3740
|
-
|
|
3741
|
-
|
|
3742
|
-
|
|
3743
|
-
|
|
3744
|
-
|
|
3745
|
-
|
|
3746
|
-
|
|
3747
|
-
|
|
3748
|
-
|
|
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
|
|
5315
|
+
return {
|
|
5316
|
+
status: "updated",
|
|
5317
|
+
packageName,
|
|
5318
|
+
variant: currentVariant,
|
|
5319
|
+
from: currentVersion,
|
|
5320
|
+
to: variantEntry.version,
|
|
5321
|
+
rewritten,
|
|
5322
|
+
managedReplaced,
|
|
5323
|
+
preserved,
|
|
5324
|
+
frozenDrift,
|
|
5325
|
+
renames,
|
|
5326
|
+
...hintPath ? { hintPath } : {}
|
|
3761
5327
|
};
|
|
3762
|
-
|
|
3763
|
-
|
|
3764
|
-
|
|
3765
|
-
|
|
3766
|
-
|
|
3767
|
-
|
|
3768
|
-
|
|
3769
|
-
|
|
3770
|
-
trigger,
|
|
3771
|
-
onlyIds
|
|
3772
|
-
});
|
|
5328
|
+
}
|
|
5329
|
+
function lookupUpstreamBasename(consumerBasename) {
|
|
5330
|
+
for (const [upstream, consumer] of Object.entries(
|
|
5331
|
+
CONSUMER_BASENAME_BY_UPSTREAM
|
|
5332
|
+
)) {
|
|
5333
|
+
if (consumer === consumerBasename) return upstream;
|
|
5334
|
+
}
|
|
5335
|
+
return consumerBasename;
|
|
3773
5336
|
}
|
|
3774
5337
|
|
|
3775
5338
|
// src/core/project-update.ts
|
|
@@ -4019,7 +5582,7 @@ async function runComponentSourceStep(category, args) {
|
|
|
4019
5582
|
});
|
|
4020
5583
|
return;
|
|
4021
5584
|
}
|
|
4022
|
-
const stagingRel =
|
|
5585
|
+
const stagingRel = path31.relative(projectRoot, built.stagingDir);
|
|
4023
5586
|
const summary = summarizeStagingRisk(built.manifest.summary.byRisk);
|
|
4024
5587
|
record({
|
|
4025
5588
|
name: category,
|
|
@@ -4062,8 +5625,8 @@ async function planTokensUpdate(tokensPackage, config) {
|
|
|
4062
5625
|
}
|
|
4063
5626
|
|
|
4064
5627
|
// src/core/installer.ts
|
|
4065
|
-
import * as
|
|
4066
|
-
import * as
|
|
5628
|
+
import * as path32 from "path";
|
|
5629
|
+
import * as fs23 from "fs/promises";
|
|
4067
5630
|
async function installResources(options) {
|
|
4068
5631
|
const { projectRoot, manifest, data, variantDir, packageRoot } = options;
|
|
4069
5632
|
const installedResources = [];
|
|
@@ -4100,13 +5663,13 @@ async function installSingleResource(resource, projectRoot, data, variantDir, pa
|
|
|
4100
5663
|
variantDir,
|
|
4101
5664
|
packageRoot
|
|
4102
5665
|
);
|
|
4103
|
-
const targetPath =
|
|
5666
|
+
const targetPath = path32.join(projectRoot, resource.target);
|
|
4104
5667
|
let content;
|
|
4105
5668
|
if (resource.template) {
|
|
4106
5669
|
const templateContent = await loadTemplateFile(sourcePath);
|
|
4107
5670
|
content = renderTemplate(templateContent, data);
|
|
4108
5671
|
} else {
|
|
4109
|
-
content = await
|
|
5672
|
+
content = await fs23.readFile(sourcePath, "utf-8");
|
|
4110
5673
|
}
|
|
4111
5674
|
await writeFileSafe(targetPath, content);
|
|
4112
5675
|
const hash = computeHash(content);
|
|
@@ -4124,13 +5687,13 @@ async function installRecursiveResource(resource, projectRoot, data, variantDir,
|
|
|
4124
5687
|
variantDir,
|
|
4125
5688
|
packageRoot
|
|
4126
5689
|
);
|
|
4127
|
-
const targetDir =
|
|
5690
|
+
const targetDir = path32.join(projectRoot, resource.target);
|
|
4128
5691
|
const results = [];
|
|
4129
5692
|
await ensureDir(targetDir);
|
|
4130
5693
|
const entries = await walkDir(sourcePath);
|
|
4131
5694
|
for (const entry of entries) {
|
|
4132
|
-
const relPath =
|
|
4133
|
-
let targetFile =
|
|
5695
|
+
const relPath = path32.relative(sourcePath, entry);
|
|
5696
|
+
let targetFile = path32.join(targetDir, relPath);
|
|
4134
5697
|
if (resource.template && targetFile.endsWith(".hbs")) {
|
|
4135
5698
|
targetFile = targetFile.slice(0, -4);
|
|
4136
5699
|
}
|
|
@@ -4139,11 +5702,11 @@ async function installRecursiveResource(resource, projectRoot, data, variantDir,
|
|
|
4139
5702
|
const templateContent = await loadTemplateFile(entry);
|
|
4140
5703
|
content = renderTemplate(templateContent, data);
|
|
4141
5704
|
} else {
|
|
4142
|
-
content = await
|
|
5705
|
+
content = await fs23.readFile(entry, "utf-8");
|
|
4143
5706
|
}
|
|
4144
5707
|
await writeFileSafe(targetFile, content);
|
|
4145
5708
|
const hash = computeHash(content);
|
|
4146
|
-
const targetRel =
|
|
5709
|
+
const targetRel = path32.relative(projectRoot, targetFile);
|
|
4147
5710
|
results.push({
|
|
4148
5711
|
id: `${resource.id}:${relPath}`,
|
|
4149
5712
|
target: targetRel,
|
|
@@ -4156,25 +5719,25 @@ async function installRecursiveResource(resource, projectRoot, data, variantDir,
|
|
|
4156
5719
|
}
|
|
4157
5720
|
|
|
4158
5721
|
// src/core/registry-client.ts
|
|
4159
|
-
import * as
|
|
4160
|
-
import * as
|
|
5722
|
+
import * as path33 from "path";
|
|
5723
|
+
import * as fs24 from "fs/promises";
|
|
4161
5724
|
import { createRequire as createRequire6 } from "module";
|
|
4162
5725
|
import { loadVariantManifest } from "@teamix-evo/registry";
|
|
4163
5726
|
var require6 = createRequire6(import.meta.url);
|
|
4164
5727
|
function resolvePackageRoot5(packageName) {
|
|
4165
5728
|
const pkgJsonPath = require6.resolve(`${packageName}/package.json`);
|
|
4166
|
-
return
|
|
5729
|
+
return path33.dirname(pkgJsonPath);
|
|
4167
5730
|
}
|
|
4168
5731
|
async function loadVariantData(packageName, variant) {
|
|
4169
5732
|
const packageRoot = resolvePackageRoot5(packageName);
|
|
4170
|
-
const variantDir =
|
|
5733
|
+
const variantDir = path33.join(packageRoot, "library", variant);
|
|
4171
5734
|
logger.debug(`Resolved variant dir: ${variantDir}`);
|
|
4172
5735
|
logger.debug(`Package root: ${packageRoot}`);
|
|
4173
5736
|
const manifest = await loadVariantManifest(variantDir);
|
|
4174
5737
|
let data = {};
|
|
4175
|
-
const dataPath =
|
|
5738
|
+
const dataPath = path33.join(variantDir, "_data.json");
|
|
4176
5739
|
try {
|
|
4177
|
-
const raw = await
|
|
5740
|
+
const raw = await fs24.readFile(dataPath, "utf-8");
|
|
4178
5741
|
data = JSON.parse(raw);
|
|
4179
5742
|
} catch (err) {
|
|
4180
5743
|
if (err.code !== "ENOENT") {
|