teamix-evo 0.10.1 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -2
- package/dist/core/index.d.ts +38 -293
- package/dist/core/index.js +627 -2724
- package/dist/core/index.js.map +1 -1
- package/dist/index.js +1020 -2101
- package/dist/index.js.map +1 -1
- package/package.json +6 -6
package/dist/core/index.js
CHANGED
|
@@ -235,12 +235,6 @@ async function writeSkillsLock(projectRoot, lock) {
|
|
|
235
235
|
await writeFileSafe(lockPath, JSON.stringify(lock, null, 2) + "\n");
|
|
236
236
|
logger.debug(`Wrote skills lock \u2192 ${lockPath}`);
|
|
237
237
|
}
|
|
238
|
-
function findInstalledPackage(installed, packageName) {
|
|
239
|
-
if (!installed) return null;
|
|
240
|
-
const matches = installed.installed.filter((p) => p.package === packageName);
|
|
241
|
-
if (matches.length === 0) return null;
|
|
242
|
-
return matches[matches.length - 1] ?? null;
|
|
243
|
-
}
|
|
244
238
|
|
|
245
239
|
// src/core/skills-client.ts
|
|
246
240
|
import * as path3 from "path";
|
|
@@ -369,12 +363,6 @@ function resolveSourcePath(source, variantDir, packageRoot) {
|
|
|
369
363
|
}
|
|
370
364
|
return path6.join(variantDir, source);
|
|
371
365
|
}
|
|
372
|
-
var DEFAULT_SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
373
|
-
"node_modules",
|
|
374
|
-
"dist",
|
|
375
|
-
"build",
|
|
376
|
-
".teamix-evo"
|
|
377
|
-
]);
|
|
378
366
|
async function walkDir(dir, skipDirs) {
|
|
379
367
|
const files = [];
|
|
380
368
|
const entries = await fs4.readdir(dir, { withFileTypes: true });
|
|
@@ -430,9 +418,9 @@ async function writeSkillSource(skill, options) {
|
|
|
430
418
|
const { data, packageRoot, projectRoot } = options;
|
|
431
419
|
const sourceAbs = path7.resolve(packageRoot, skill.source);
|
|
432
420
|
const targetDir = getSkillsSourceDir(projectRoot, skill.name);
|
|
433
|
-
const
|
|
421
|
+
const stat3 = await fs5.stat(sourceAbs);
|
|
434
422
|
const records = [];
|
|
435
|
-
if (
|
|
423
|
+
if (stat3.isFile()) {
|
|
436
424
|
const targetFile = path7.join(targetDir, "SKILL.md");
|
|
437
425
|
const content = await renderSkillContent(sourceAbs, skill, data);
|
|
438
426
|
await writeFileSafe(targetFile, content);
|
|
@@ -582,8 +570,8 @@ async function rewriteSkillSource(skill, options, summary) {
|
|
|
582
570
|
const { data, packageRoot, projectRoot } = options;
|
|
583
571
|
const sourceAbs = path7.resolve(packageRoot, skill.source);
|
|
584
572
|
const targetDir = getSkillsSourceDir(projectRoot, skill.name);
|
|
585
|
-
const
|
|
586
|
-
if (!
|
|
573
|
+
const stat3 = await fs5.stat(sourceAbs);
|
|
574
|
+
if (!stat3.isFile()) {
|
|
587
575
|
await ensureDir(targetDir);
|
|
588
576
|
const entries = await walkDir(sourceAbs);
|
|
589
577
|
const records = [];
|
|
@@ -820,13 +808,13 @@ async function pruneEmptyIdeSkillDirs(args) {
|
|
|
820
808
|
}
|
|
821
809
|
for (const name of entries) {
|
|
822
810
|
const dir = path7.join(skillsRoot, name);
|
|
823
|
-
let
|
|
811
|
+
let stat3;
|
|
824
812
|
try {
|
|
825
|
-
|
|
813
|
+
stat3 = await fs5.stat(dir);
|
|
826
814
|
} catch {
|
|
827
815
|
continue;
|
|
828
816
|
}
|
|
829
|
-
if (!
|
|
817
|
+
if (!stat3.isDirectory()) continue;
|
|
830
818
|
let children;
|
|
831
819
|
try {
|
|
832
820
|
children = await fs5.readdir(dir);
|
|
@@ -949,10 +937,25 @@ async function runSkillsInit(options) {
|
|
|
949
937
|
return { status: "already-initialized" };
|
|
950
938
|
}
|
|
951
939
|
if (onlyIds.length === 0) {
|
|
940
|
+
let autoUpdatedSkillIds = [];
|
|
941
|
+
if (outdatedSkills.length > 0) {
|
|
942
|
+
autoUpdatedSkillIds = await autoUpgradeOutdatedSkills({
|
|
943
|
+
projectRoot,
|
|
944
|
+
packageName,
|
|
945
|
+
manifest,
|
|
946
|
+
data,
|
|
947
|
+
packageRoot,
|
|
948
|
+
ides,
|
|
949
|
+
scope,
|
|
950
|
+
outdatedSkills,
|
|
951
|
+
existing,
|
|
952
|
+
existingConfig
|
|
953
|
+
});
|
|
954
|
+
}
|
|
952
955
|
return {
|
|
953
956
|
status: "installed",
|
|
954
957
|
packageName,
|
|
955
|
-
version:
|
|
958
|
+
version: manifest.version,
|
|
956
959
|
ides,
|
|
957
960
|
scope,
|
|
958
961
|
skillCount: 0,
|
|
@@ -960,7 +963,8 @@ async function runSkillsInit(options) {
|
|
|
960
963
|
resources: [],
|
|
961
964
|
addedSkillIds: [],
|
|
962
965
|
skippedSkillIds,
|
|
963
|
-
|
|
966
|
+
autoUpdatedSkillIds,
|
|
967
|
+
outdatedSkills: []
|
|
964
968
|
};
|
|
965
969
|
}
|
|
966
970
|
return finalizeSkillsInstall({
|
|
@@ -1021,10 +1025,25 @@ async function runSkillsAdd(options) {
|
|
|
1021
1025
|
existing
|
|
1022
1026
|
);
|
|
1023
1027
|
if (onlyIds.length === 0) {
|
|
1028
|
+
let autoUpdatedSkillIds = [];
|
|
1029
|
+
if (outdatedSkills.length > 0) {
|
|
1030
|
+
autoUpdatedSkillIds = await autoUpgradeOutdatedSkills({
|
|
1031
|
+
projectRoot,
|
|
1032
|
+
packageName,
|
|
1033
|
+
manifest,
|
|
1034
|
+
data,
|
|
1035
|
+
packageRoot,
|
|
1036
|
+
ides,
|
|
1037
|
+
scope,
|
|
1038
|
+
outdatedSkills,
|
|
1039
|
+
existing,
|
|
1040
|
+
existingConfig
|
|
1041
|
+
});
|
|
1042
|
+
}
|
|
1024
1043
|
return {
|
|
1025
1044
|
status: "installed",
|
|
1026
1045
|
packageName,
|
|
1027
|
-
version:
|
|
1046
|
+
version: manifest.version,
|
|
1028
1047
|
ides,
|
|
1029
1048
|
scope,
|
|
1030
1049
|
skillCount: 0,
|
|
@@ -1032,7 +1051,8 @@ async function runSkillsAdd(options) {
|
|
|
1032
1051
|
resources: [],
|
|
1033
1052
|
addedSkillIds: [],
|
|
1034
1053
|
skippedSkillIds,
|
|
1035
|
-
|
|
1054
|
+
autoUpdatedSkillIds,
|
|
1055
|
+
outdatedSkills: []
|
|
1036
1056
|
};
|
|
1037
1057
|
}
|
|
1038
1058
|
return finalizeSkillsInstall({
|
|
@@ -1186,6 +1206,21 @@ async function finalizeSkillsInstall(args) {
|
|
|
1186
1206
|
await pruneEmptyIdeSkillDirs({ projectRoot, ides, scope });
|
|
1187
1207
|
} catch {
|
|
1188
1208
|
}
|
|
1209
|
+
let autoUpdatedSkillIds = [];
|
|
1210
|
+
if (outdatedSkills && outdatedSkills.length > 0) {
|
|
1211
|
+
autoUpdatedSkillIds = await autoUpgradeOutdatedSkills({
|
|
1212
|
+
projectRoot,
|
|
1213
|
+
packageName,
|
|
1214
|
+
manifest,
|
|
1215
|
+
data,
|
|
1216
|
+
packageRoot,
|
|
1217
|
+
ides,
|
|
1218
|
+
scope,
|
|
1219
|
+
outdatedSkills,
|
|
1220
|
+
existing,
|
|
1221
|
+
existingConfig: existingConfig ?? config
|
|
1222
|
+
});
|
|
1223
|
+
}
|
|
1189
1224
|
return {
|
|
1190
1225
|
status: "installed",
|
|
1191
1226
|
packageName,
|
|
@@ -1197,7 +1232,8 @@ async function finalizeSkillsInstall(args) {
|
|
|
1197
1232
|
resources: result.resources,
|
|
1198
1233
|
addedSkillIds: onlyIds,
|
|
1199
1234
|
skippedSkillIds,
|
|
1200
|
-
|
|
1235
|
+
autoUpdatedSkillIds,
|
|
1236
|
+
outdatedSkills: []
|
|
1201
1237
|
};
|
|
1202
1238
|
}
|
|
1203
1239
|
function mergeInstalledResources(existing, next) {
|
|
@@ -1207,6 +1243,66 @@ function mergeInstalledResources(existing, next) {
|
|
|
1207
1243
|
for (const r of next) map.set(key(r), r);
|
|
1208
1244
|
return [...map.values()];
|
|
1209
1245
|
}
|
|
1246
|
+
async function autoUpgradeOutdatedSkills(args) {
|
|
1247
|
+
const {
|
|
1248
|
+
projectRoot,
|
|
1249
|
+
packageName,
|
|
1250
|
+
manifest,
|
|
1251
|
+
data,
|
|
1252
|
+
packageRoot,
|
|
1253
|
+
ides,
|
|
1254
|
+
scope,
|
|
1255
|
+
outdatedSkills,
|
|
1256
|
+
existing
|
|
1257
|
+
} = args;
|
|
1258
|
+
const targetIds = outdatedSkills.map((o) => o.id);
|
|
1259
|
+
await updateSkills({
|
|
1260
|
+
projectRoot,
|
|
1261
|
+
manifest,
|
|
1262
|
+
data,
|
|
1263
|
+
packageRoot,
|
|
1264
|
+
ides,
|
|
1265
|
+
scope,
|
|
1266
|
+
onlyIds: targetIds
|
|
1267
|
+
});
|
|
1268
|
+
const lock = existing.lock ?? {
|
|
1269
|
+
schemaVersion: 1,
|
|
1270
|
+
skills: {}
|
|
1271
|
+
};
|
|
1272
|
+
const installedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1273
|
+
const manifestById = new Map(manifest.skills.map((s) => [s.id, s]));
|
|
1274
|
+
for (const id of targetIds) {
|
|
1275
|
+
const skillDef = manifestById.get(id);
|
|
1276
|
+
if (!skillDef) continue;
|
|
1277
|
+
const mirroredTo = skillDef.ides.filter((i) => ides.includes(i));
|
|
1278
|
+
lock.skills[id] = {
|
|
1279
|
+
version: skillDef.version,
|
|
1280
|
+
from: packageName,
|
|
1281
|
+
installedAt,
|
|
1282
|
+
scope,
|
|
1283
|
+
mirroredTo
|
|
1284
|
+
};
|
|
1285
|
+
}
|
|
1286
|
+
await writeSkillsLock(projectRoot, lock);
|
|
1287
|
+
const installedManifest = await readInstalledManifest(projectRoot) ?? {
|
|
1288
|
+
schemaVersion: 1,
|
|
1289
|
+
installed: []
|
|
1290
|
+
};
|
|
1291
|
+
const idx = installedManifest.installed.findIndex(
|
|
1292
|
+
(p) => p.package === packageName
|
|
1293
|
+
);
|
|
1294
|
+
if (idx >= 0) {
|
|
1295
|
+
installedManifest.installed[idx].version = manifest.version;
|
|
1296
|
+
installedManifest.installed[idx].installedAt = installedAt;
|
|
1297
|
+
}
|
|
1298
|
+
await writeInstalledManifest(projectRoot, installedManifest);
|
|
1299
|
+
logger.debug(
|
|
1300
|
+
`Auto-upgraded ${targetIds.length} outdated skill(s): ${targetIds.join(
|
|
1301
|
+
", "
|
|
1302
|
+
)}`
|
|
1303
|
+
);
|
|
1304
|
+
return targetIds;
|
|
1305
|
+
}
|
|
1210
1306
|
|
|
1211
1307
|
// src/core/tokens-init.ts
|
|
1212
1308
|
var DEFAULT_SKILLS_PACKAGE2 = "@teamix-evo/skills";
|
|
@@ -1638,6 +1734,7 @@ var DEFAULT_UI_ALIASES = {
|
|
|
1638
1734
|
utils: "src/lib/utils",
|
|
1639
1735
|
lib: "src/lib",
|
|
1640
1736
|
business: "src/components/business",
|
|
1737
|
+
blocks: "src/blocks",
|
|
1641
1738
|
templates: "src/templates"
|
|
1642
1739
|
};
|
|
1643
1740
|
var DEFAULT_UI_ICON_LIBRARY = "lucide";
|
|
@@ -1655,6 +1752,7 @@ async function runUiInit(options) {
|
|
|
1655
1752
|
utils: options.aliases?.utils ?? DEFAULT_UI_ALIASES.utils,
|
|
1656
1753
|
lib: options.aliases?.lib ?? DEFAULT_UI_ALIASES.lib,
|
|
1657
1754
|
business: options.aliases?.business ?? DEFAULT_UI_ALIASES.business,
|
|
1755
|
+
blocks: options.aliases?.blocks ?? DEFAULT_UI_ALIASES.blocks,
|
|
1658
1756
|
templates: options.aliases?.templates ?? DEFAULT_UI_ALIASES.templates
|
|
1659
1757
|
};
|
|
1660
1758
|
const iconLibrary = options.iconLibrary ?? DEFAULT_UI_ICON_LIBRARY;
|
|
@@ -1723,9 +1821,11 @@ var SOURCE_ROOT_TO_ALIAS_KEY = {
|
|
|
1723
1821
|
components: "components",
|
|
1724
1822
|
hooks: "hooks",
|
|
1725
1823
|
utils: "utils",
|
|
1726
|
-
lib: "lib"
|
|
1824
|
+
lib: "lib",
|
|
1825
|
+
blocks: "blocks"
|
|
1727
1826
|
};
|
|
1728
|
-
function rewriteImports(source, aliases) {
|
|
1827
|
+
function rewriteImports(source, aliases, opts) {
|
|
1828
|
+
const shouldFlatten = opts?.flatten !== false;
|
|
1729
1829
|
return source.replace(
|
|
1730
1830
|
/(['"])@\/([a-z][a-z0-9-]*)(\/[^'"]*)?\1/g,
|
|
1731
1831
|
(full, quote, root, rest) => {
|
|
@@ -1733,8 +1833,8 @@ function rewriteImports(source, aliases) {
|
|
|
1733
1833
|
if (!aliasKey) return full;
|
|
1734
1834
|
const alias = aliases[aliasKey];
|
|
1735
1835
|
const normalized = aliasToImportPath(alias);
|
|
1736
|
-
const
|
|
1737
|
-
return `${quote}${normalized}${
|
|
1836
|
+
const resolvedRest = shouldFlatten ? flattenRestPath(rest) : rest ?? "";
|
|
1837
|
+
return `${quote}${normalized}${resolvedRest}${quote}`;
|
|
1738
1838
|
}
|
|
1739
1839
|
);
|
|
1740
1840
|
}
|
|
@@ -1763,7 +1863,8 @@ async function installUiEntries(options) {
|
|
|
1763
1863
|
entryPackageRoot,
|
|
1764
1864
|
aliases,
|
|
1765
1865
|
requested,
|
|
1766
|
-
skipExisting = true
|
|
1866
|
+
skipExisting = true,
|
|
1867
|
+
flatten = true
|
|
1767
1868
|
} = options;
|
|
1768
1869
|
const orderedIds = resolveUiEntryOrder(manifest.entries, requested);
|
|
1769
1870
|
const idToEntry = new Map(manifest.entries.map((e) => [e.id, e]));
|
|
@@ -1790,7 +1891,7 @@ async function installUiEntries(options) {
|
|
|
1790
1891
|
const rootForEntry = entryPackageRoot?.get(entry.id) ?? packageRoot;
|
|
1791
1892
|
const sourceAbs = path11.resolve(rootForEntry, file.source);
|
|
1792
1893
|
const raw = await fs8.readFile(sourceAbs, "utf-8");
|
|
1793
|
-
const transformed = rewriteImports(raw, aliases);
|
|
1894
|
+
const transformed = rewriteImports(raw, aliases, { flatten });
|
|
1794
1895
|
if (exists) {
|
|
1795
1896
|
await backupFile(targetAbs, projectRoot);
|
|
1796
1897
|
}
|
|
@@ -2646,8 +2747,8 @@ var STYLELINT_CONFIG_CANDIDATES = [
|
|
|
2646
2747
|
];
|
|
2647
2748
|
async function isDir(target) {
|
|
2648
2749
|
try {
|
|
2649
|
-
const
|
|
2650
|
-
return
|
|
2750
|
+
const stat3 = await fs12.stat(target);
|
|
2751
|
+
return stat3.isDirectory();
|
|
2651
2752
|
} catch {
|
|
2652
2753
|
return false;
|
|
2653
2754
|
}
|
|
@@ -2871,2762 +2972,565 @@ async function detectStylelintConfig(cwd) {
|
|
|
2871
2972
|
};
|
|
2872
2973
|
}
|
|
2873
2974
|
|
|
2874
|
-
// src/core/
|
|
2875
|
-
import * as path17 from "path";
|
|
2975
|
+
// src/core/deps-install.ts
|
|
2876
2976
|
import * as fs13 from "fs/promises";
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
|
|
2884
|
-
|
|
2977
|
+
import * as path17 from "path";
|
|
2978
|
+
import { exec } from "child_process";
|
|
2979
|
+
import { promisify } from "util";
|
|
2980
|
+
var execAsync = promisify(exec);
|
|
2981
|
+
async function detectPackageManager(projectRoot) {
|
|
2982
|
+
if (await fileExists(path17.join(projectRoot, "pnpm-lock.yaml"))) return "pnpm";
|
|
2983
|
+
if (await fileExists(path17.join(projectRoot, "pnpm-workspace.yaml")))
|
|
2984
|
+
return "pnpm";
|
|
2985
|
+
if (await fileExists(path17.join(projectRoot, "bun.lockb"))) return "bun";
|
|
2986
|
+
if (await fileExists(path17.join(projectRoot, "bun.lock"))) return "bun";
|
|
2987
|
+
if (await fileExists(path17.join(projectRoot, "yarn.lock"))) return "yarn";
|
|
2988
|
+
return "npm";
|
|
2885
2989
|
}
|
|
2886
|
-
function
|
|
2887
|
-
|
|
2888
|
-
|
|
2889
|
-
|
|
2890
|
-
"
|
|
2891
|
-
|
|
2892
|
-
|
|
2990
|
+
function getInstallCommand(pm) {
|
|
2991
|
+
switch (pm) {
|
|
2992
|
+
case "pnpm":
|
|
2993
|
+
return "pnpm install";
|
|
2994
|
+
case "yarn":
|
|
2995
|
+
return "yarn install";
|
|
2996
|
+
case "bun":
|
|
2997
|
+
return "bun install";
|
|
2998
|
+
case "npm":
|
|
2999
|
+
return "npm install";
|
|
3000
|
+
}
|
|
2893
3001
|
}
|
|
2894
|
-
async function
|
|
2895
|
-
const { projectRoot,
|
|
2896
|
-
const
|
|
2897
|
-
const
|
|
2898
|
-
|
|
2899
|
-
|
|
2900
|
-
|
|
2901
|
-
|
|
2902
|
-
uniqueLegacy.push(norm);
|
|
2903
|
-
}
|
|
3002
|
+
async function installProjectDeps(options) {
|
|
3003
|
+
const { projectRoot, npmDependencies, skipInstall = false } = options;
|
|
3004
|
+
const pkgPath = path17.join(projectRoot, "package.json");
|
|
3005
|
+
const raw = await readFileOrNull(pkgPath);
|
|
3006
|
+
if (!raw) {
|
|
3007
|
+
throw new Error(
|
|
3008
|
+
`package.json not found at ${projectRoot}. Cannot install dependencies.`
|
|
3009
|
+
);
|
|
2904
3010
|
}
|
|
2905
|
-
const
|
|
2906
|
-
|
|
2907
|
-
|
|
2908
|
-
|
|
2909
|
-
|
|
2910
|
-
const
|
|
2911
|
-
const
|
|
2912
|
-
const
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
for (const legacyRel of uniqueLegacy) {
|
|
2918
|
-
const legacyAbs = path17.join(projectRoot, legacyRel);
|
|
2919
|
-
const content = await readFileOrNull(legacyAbs);
|
|
2920
|
-
if (content === null) {
|
|
2921
|
-
skipped.push({ from: legacyRel, reason: "not-found" });
|
|
2922
|
-
continue;
|
|
2923
|
-
}
|
|
2924
|
-
if (content.trim() === "") {
|
|
2925
|
-
skipped.push({ from: legacyRel, reason: "empty" });
|
|
2926
|
-
continue;
|
|
3011
|
+
const pkg = JSON.parse(raw);
|
|
3012
|
+
const existingDeps = {
|
|
3013
|
+
...pkg.dependencies,
|
|
3014
|
+
...pkg.devDependencies
|
|
3015
|
+
};
|
|
3016
|
+
const added = {};
|
|
3017
|
+
const existed = {};
|
|
3018
|
+
for (const [name, range] of Object.entries(npmDependencies)) {
|
|
3019
|
+
if (existingDeps[name]) {
|
|
3020
|
+
existed[name] = existingDeps[name];
|
|
3021
|
+
} else {
|
|
3022
|
+
added[name] = range;
|
|
2927
3023
|
}
|
|
2928
|
-
const banner = buildMigrationBanner(legacyRel, isoTimestamp);
|
|
2929
|
-
const separator = appendedPayload === "" && baseContent.endsWith("\n\n") ? "" : "\n";
|
|
2930
|
-
const block = `${separator}${banner}${content}${content.endsWith("\n") ? "" : "\n"}`;
|
|
2931
|
-
appendedPayload += block;
|
|
2932
|
-
const backupRel = dryRun ? null : computeBackupRel(legacyRel, isoTimestamp);
|
|
2933
|
-
migrated.push({
|
|
2934
|
-
from: legacyRel,
|
|
2935
|
-
backupTo: backupRel,
|
|
2936
|
-
bytesAppended: Buffer.byteLength(block, "utf-8")
|
|
2937
|
-
});
|
|
2938
3024
|
}
|
|
2939
|
-
if (
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
|
|
3025
|
+
if (Object.keys(added).length > 0) {
|
|
3026
|
+
if (!pkg.dependencies) pkg.dependencies = {};
|
|
3027
|
+
for (const [name, range] of Object.entries(added)) {
|
|
3028
|
+
pkg.dependencies[name] = range;
|
|
3029
|
+
}
|
|
3030
|
+
pkg.dependencies = Object.fromEntries(
|
|
3031
|
+
Object.entries(pkg.dependencies).sort(([a], [b]) => a.localeCompare(b))
|
|
3032
|
+
);
|
|
3033
|
+
await fs13.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf-8");
|
|
3034
|
+
logger.info(
|
|
3035
|
+
` patched package.json: +${Object.keys(added).length} dependencies`
|
|
3036
|
+
);
|
|
2947
3037
|
}
|
|
2948
|
-
await
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
|
|
2960
|
-
`legacy-tokens-migrate: source disappeared before backup: ${entry.from}`
|
|
3038
|
+
const packageManager = await detectPackageManager(projectRoot);
|
|
3039
|
+
let installed = false;
|
|
3040
|
+
if (!skipInstall && Object.keys(added).length > 0) {
|
|
3041
|
+
const cmd = getInstallCommand(packageManager);
|
|
3042
|
+
logger.info(` running ${cmd}...`);
|
|
3043
|
+
try {
|
|
3044
|
+
await execAsync(cmd, { cwd: projectRoot, timeout: 12e4 });
|
|
3045
|
+
installed = true;
|
|
3046
|
+
logger.info(" install complete");
|
|
3047
|
+
} catch (err) {
|
|
3048
|
+
logger.warn(
|
|
3049
|
+
` install failed: ${err instanceof Error ? err.message : String(err)}`
|
|
2961
3050
|
);
|
|
2962
|
-
|
|
2963
|
-
}
|
|
2964
|
-
await fs13.writeFile(backupAbs, srcContent, "utf-8");
|
|
2965
|
-
if (await fileExists(legacyAbs)) {
|
|
2966
|
-
await fs13.unlink(legacyAbs);
|
|
3051
|
+
logger.warn(" please run install manually");
|
|
2967
3052
|
}
|
|
2968
|
-
logger.debug(
|
|
2969
|
-
`legacy-tokens-migrate: ${entry.from} \u2192 ${overridesRel} (backup: ${entry.backupTo})`
|
|
2970
|
-
);
|
|
2971
3053
|
}
|
|
2972
|
-
return {
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
|
|
3054
|
+
return { added, existed, installed, packageManager };
|
|
3055
|
+
}
|
|
3056
|
+
|
|
3057
|
+
// src/core/init-checklist-template.ts
|
|
3058
|
+
var STEP_LABELS = {
|
|
3059
|
+
tokens: "tokens \u2014 design tokens \u5B89\u88C5",
|
|
3060
|
+
skills: "skills \u2014 AI skills \u6279\u91CF\u81EA\u4E3E",
|
|
3061
|
+
"agents-md": "agents-md \u2014 AGENTS.md \u751F\u6210",
|
|
3062
|
+
"ui-init": "ui-init \u2014 UI \u914D\u7F6E\u521D\u59CB\u5316",
|
|
3063
|
+
"ui-add": "ui-add \u2014 UI \u7EC4\u4EF6\u5168\u91CF\u5B89\u88C5",
|
|
3064
|
+
"biz-ui-add": "biz-ui-add \u2014 \u4E1A\u52A1\u7EC4\u4EF6\u5168\u91CF\u5B89\u88C5",
|
|
3065
|
+
lint: "lint \u2014 ESLint + Stylelint \u914D\u7F6E",
|
|
3066
|
+
gitignore: "gitignore \u2014 .gitignore \u8FFD\u52A0"
|
|
3067
|
+
};
|
|
3068
|
+
function renderInitChecklist(args) {
|
|
3069
|
+
const { variant, status, steps } = args;
|
|
3070
|
+
const ts = args.timestamp ?? (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").slice(0, 19);
|
|
3071
|
+
const stepLines = steps.map((s) => {
|
|
3072
|
+
const label = STEP_LABELS[s.name] ?? s.name;
|
|
3073
|
+
const checked = s.status === "ok" ? "x" : " ";
|
|
3074
|
+
const suffix = s.status === "ok" ? "" : s.status === "skip" ? `\uFF08\u8DF3\u8FC7${s.detail ? "\uFF1A" + s.detail : ""}\uFF09` : s.status === "fail" ? `\uFF08\u5931\u8D25\uFF1A${s.detail ?? "unknown"}\uFF09` : "\uFF08\u8BA1\u5212\u4E2D\uFF09";
|
|
3075
|
+
return `- [${checked}] ${label}${suffix}`;
|
|
3076
|
+
}).join("\n");
|
|
3077
|
+
return `# teamix-evo init \u5B89\u88C5\u6458\u8981
|
|
3078
|
+
|
|
3079
|
+
> \u7531 \`teamix-evo init\` \u81EA\u52A8\u751F\u6210 \xB7 ${ts}
|
|
3080
|
+
> variant: ${variant} \xB7 status: ${status}
|
|
3081
|
+
|
|
3082
|
+
## \u5B89\u88C5\u6B65\u9AA4
|
|
3083
|
+
|
|
3084
|
+
${stepLines}
|
|
3085
|
+
|
|
3086
|
+
## \u5907\u6CE8
|
|
3087
|
+
|
|
3088
|
+
- \u5931\u8D25\u6216\u8DF3\u8FC7\u7684\u6B65\u9AA4\u4E0D\u5F71\u54CD\u540E\u7EED\u6B65\u9AA4\u72EC\u7ACB\u6267\u884C
|
|
3089
|
+
- \u4FEE\u590D\u540E\u91CD\u8DD1 \`teamix-evo init\`\uFF08\u6BCF\u6B65\u5E42\u7B49\uFF0C\u81EA\u52A8\u8DF3\u8FC7\u5DF2\u5B8C\u6210\u9879\uFF09
|
|
3090
|
+
- \u5982\u9700\u5168\u91CF\u8986\u76D6\uFF1A\`teamix-evo init --force\`
|
|
3091
|
+
`;
|
|
2979
3092
|
}
|
|
2980
3093
|
|
|
2981
|
-
// src/core/
|
|
3094
|
+
// src/core/file-changes.ts
|
|
2982
3095
|
import * as fs14 from "fs/promises";
|
|
2983
3096
|
import * as path18 from "path";
|
|
2984
|
-
function
|
|
2985
|
-
|
|
2986
|
-
if (
|
|
2987
|
-
|
|
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
|
-
}
|
|
3097
|
+
function toRelativePosix(p, projectRoot) {
|
|
3098
|
+
let rel2 = p;
|
|
3099
|
+
if (path18.isAbsolute(p)) {
|
|
3100
|
+
rel2 = path18.relative(projectRoot, p);
|
|
3018
3101
|
}
|
|
3019
|
-
return
|
|
3020
|
-
conflictEntries: conflicts,
|
|
3021
|
-
unconflictedTargets: totalChecked - conflicts.length,
|
|
3022
|
-
totalEntries: totalChecked,
|
|
3023
|
-
shouldBlock: conflicts.length > 0,
|
|
3024
|
-
conflictDirs: [...conflictDirSet]
|
|
3025
|
-
};
|
|
3102
|
+
return rel2.split(path18.sep).join("/");
|
|
3026
3103
|
}
|
|
3027
3104
|
|
|
3028
|
-
// src/core/
|
|
3029
|
-
import * as
|
|
3105
|
+
// src/core/project-init.ts
|
|
3106
|
+
import * as fsNode from "fs/promises";
|
|
3030
3107
|
import * as path19 from "path";
|
|
3031
|
-
var
|
|
3032
|
-
|
|
3033
|
-
|
|
3034
|
-
|
|
3035
|
-
|
|
3108
|
+
var CRITICAL_STEPS = /* @__PURE__ */ new Set([
|
|
3109
|
+
"tokens",
|
|
3110
|
+
"skills",
|
|
3111
|
+
"ui-init"
|
|
3112
|
+
]);
|
|
3113
|
+
var GITIGNORE_MARKER_START = "# >>> teamix-evo:managed >>>";
|
|
3114
|
+
var GITIGNORE_MARKER_END = "# <<< teamix-evo:managed <<<";
|
|
3115
|
+
var GITIGNORE_RULES = [
|
|
3116
|
+
"# IDE skill mirrors (regenerable via `teamix-evo skills sync`; ADR 0013)",
|
|
3117
|
+
".qoder/skills/",
|
|
3118
|
+
".claude/skills/",
|
|
3119
|
+
"",
|
|
3120
|
+
"# Runtime artifacts (regenerable / archives)",
|
|
3121
|
+
".teamix-evo/.snapshots/",
|
|
3122
|
+
".teamix-evo/logs/",
|
|
3123
|
+
".teamix-evo/.backups/",
|
|
3124
|
+
".teamix-evo/.upgrade-staging/",
|
|
3125
|
+
".teamix-evo/.upgrade-hints/"
|
|
3126
|
+
];
|
|
3127
|
+
async function runProjectInit(options) {
|
|
3128
|
+
const {
|
|
3036
3129
|
projectRoot,
|
|
3037
|
-
|
|
3038
|
-
|
|
3039
|
-
|
|
3040
|
-
|
|
3041
|
-
|
|
3042
|
-
|
|
3043
|
-
|
|
3044
|
-
|
|
3045
|
-
|
|
3046
|
-
|
|
3047
|
-
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
|
|
3051
|
-
|
|
3052
|
-
|
|
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 });
|
|
3130
|
+
variant,
|
|
3131
|
+
ides,
|
|
3132
|
+
scope = "project",
|
|
3133
|
+
dryRun = false,
|
|
3134
|
+
onStep
|
|
3135
|
+
} = options;
|
|
3136
|
+
const ide = ides[0] ?? "qoder";
|
|
3137
|
+
const steps = [];
|
|
3138
|
+
const allChanges = [];
|
|
3139
|
+
let aborted = false;
|
|
3140
|
+
const firstFailure = { value: null };
|
|
3141
|
+
function record(step) {
|
|
3142
|
+
steps.push(step);
|
|
3143
|
+
onStep?.(step);
|
|
3144
|
+
if (step.changes && step.changes.length > 0) {
|
|
3145
|
+
allChanges.push(...step.changes);
|
|
3072
3146
|
}
|
|
3073
|
-
await pruneEmptyDirs(componentsDir);
|
|
3074
|
-
logger.info(
|
|
3075
|
-
` moved ${movedFiles.length} files \u2192 ${path19.relative(
|
|
3076
|
-
projectRoot,
|
|
3077
|
-
legacyDir
|
|
3078
|
-
)}/`
|
|
3079
|
-
);
|
|
3080
3147
|
}
|
|
3081
|
-
|
|
3082
|
-
|
|
3083
|
-
|
|
3084
|
-
|
|
3085
|
-
|
|
3086
|
-
|
|
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);
|
|
3132
|
-
}
|
|
3133
|
-
}
|
|
3134
|
-
logger.info(` rewrote imports in ${importRewrites.size} files`);
|
|
3148
|
+
function recordFailure(name, err) {
|
|
3149
|
+
const message = getErrorMessage(err);
|
|
3150
|
+
record({ name, status: "fail", detail: message });
|
|
3151
|
+
if (!firstFailure.value)
|
|
3152
|
+
firstFailure.value = { step: name, error: message };
|
|
3153
|
+
if (CRITICAL_STEPS.has(name)) aborted = true;
|
|
3135
3154
|
}
|
|
3136
|
-
|
|
3137
|
-
|
|
3138
|
-
|
|
3139
|
-
|
|
3155
|
+
if (dryRun) {
|
|
3156
|
+
record({
|
|
3157
|
+
name: "tokens",
|
|
3158
|
+
status: "planned",
|
|
3159
|
+
detail: `runTokensInit(variant=${variant})`
|
|
3160
|
+
});
|
|
3161
|
+
} else {
|
|
3140
3162
|
try {
|
|
3141
|
-
await
|
|
3142
|
-
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
|
|
3163
|
+
const result = await runTokensInit({ projectRoot, variant, ide });
|
|
3164
|
+
const detail = result.status === "installed" ? `${result.packageName}@${result.version} (${result.count} files)` : result.status;
|
|
3165
|
+
record({
|
|
3166
|
+
name: "tokens",
|
|
3167
|
+
status: "ok",
|
|
3168
|
+
detail,
|
|
3169
|
+
changes: result.status === "installed" ? result.resources.map((r) => ({
|
|
3170
|
+
kind: "created",
|
|
3171
|
+
path: toRelativePosix(r.target, projectRoot),
|
|
3172
|
+
step: "tokens",
|
|
3173
|
+
detail: r.strategy
|
|
3174
|
+
})) : []
|
|
3175
|
+
});
|
|
3176
|
+
} catch (err) {
|
|
3177
|
+
recordFailure("tokens", err);
|
|
3146
3178
|
}
|
|
3147
3179
|
}
|
|
3148
|
-
|
|
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;
|
|
4276
|
-
}
|
|
4277
|
-
async function pruneSnapshots(projectRoot, keep = DEFAULT_KEEP, opts = {}) {
|
|
4278
|
-
if (keep < 0)
|
|
4279
|
-
throw new Error(`pruneSnapshots: keep must be >= 0, got ${keep}`);
|
|
4280
|
-
const snapshots = await listSnapshots(projectRoot);
|
|
4281
|
-
if (snapshots.length <= keep) return [];
|
|
4282
|
-
const tail = snapshots.slice(keep);
|
|
4283
|
-
const toRemove = opts.protectedTs ? tail.filter((s) => s.ts !== opts.protectedTs) : tail;
|
|
4284
|
-
const removed = [];
|
|
4285
|
-
for (const snap of toRemove) {
|
|
4286
|
-
await fs20.rm(snap.path, { recursive: true, force: true });
|
|
4287
|
-
removed.push(snap.ts);
|
|
4288
|
-
logger.debug(`Pruned snapshot ${snap.ts}`);
|
|
4289
|
-
}
|
|
4290
|
-
return removed.reverse();
|
|
4291
|
-
}
|
|
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
|
-
|
|
4343
|
-
// src/core/project-init.ts
|
|
4344
|
-
import * as fsNode from "fs/promises";
|
|
4345
|
-
import * as path28 from "path";
|
|
4346
|
-
var BASELINE_UI_ENTRIES = [
|
|
4347
|
-
"button",
|
|
4348
|
-
"button-group",
|
|
4349
|
-
"input",
|
|
4350
|
-
"form",
|
|
4351
|
-
"card",
|
|
4352
|
-
"collapsible",
|
|
4353
|
-
"dialog",
|
|
4354
|
-
"dropdown-menu",
|
|
4355
|
-
"tabs",
|
|
4356
|
-
"table",
|
|
4357
|
-
"sidebar",
|
|
4358
|
-
"page-shell",
|
|
4359
|
-
"page-header"
|
|
4360
|
-
];
|
|
4361
|
-
var CRITICAL_STEPS = /* @__PURE__ */ new Set([
|
|
4362
|
-
"tokens",
|
|
4363
|
-
"skills",
|
|
4364
|
-
"ui-init"
|
|
4365
|
-
]);
|
|
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`.
|
|
4370
|
-
"agents-md": ["merge-managed", "overwrite", "skip"],
|
|
4371
|
-
tokens: ["migrate", "overwrite", "skip"],
|
|
4372
|
-
"components-json": ["overwrite", "skip"],
|
|
4373
|
-
"shadcn-source": ["overwrite", "skip-existing", "skip"],
|
|
4374
|
-
"tailwind-config": ["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"]
|
|
4380
|
-
};
|
|
4381
|
-
function pickIde(ides) {
|
|
4382
|
-
return ides[0] ?? "qoder";
|
|
4383
|
-
}
|
|
4384
|
-
function strategyImplemented(key, strategy) {
|
|
4385
|
-
return IMPLEMENTED_STRATEGIES[key]?.includes(strategy) ?? false;
|
|
4386
|
-
}
|
|
4387
|
-
async function resolveUiEntries(options) {
|
|
4388
|
-
if (options.uiEntries && options.uiEntries.length > 0) {
|
|
4389
|
-
return [...options.uiEntries];
|
|
4390
|
-
}
|
|
4391
|
-
if (options.answers.uiSelection === "all") {
|
|
4392
|
-
const { manifest } = await loadUiData("@teamix-evo/ui");
|
|
4393
|
-
return manifest.entries.map((e) => e.id);
|
|
4394
|
-
}
|
|
4395
|
-
return [...BASELINE_UI_ENTRIES];
|
|
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
|
-
}
|
|
4459
|
-
async function runProjectInit(options) {
|
|
4460
|
-
const { projectRoot, answers, dryRun = false, onStep } = options;
|
|
4461
|
-
const ide = pickIde(answers.ides);
|
|
4462
|
-
const steps = [];
|
|
4463
|
-
const pending = [];
|
|
4464
|
-
const allChanges = [];
|
|
4465
|
-
const backupsBefore = dryRun ? /* @__PURE__ */ new Set() : await listBackupOriginals(projectRoot).catch(() => /* @__PURE__ */ new Set());
|
|
4466
|
-
let snapshot = null;
|
|
4467
|
-
let snapshotError;
|
|
4468
|
-
if (!dryRun) {
|
|
4469
|
-
try {
|
|
4470
|
-
snapshot = await createSnapshot(projectRoot, { reason: "init" });
|
|
4471
|
-
} catch (err) {
|
|
4472
|
-
snapshotError = getErrorMessage(err);
|
|
4473
|
-
}
|
|
4474
|
-
}
|
|
4475
|
-
let aborted = false;
|
|
4476
|
-
const firstFailure = {
|
|
4477
|
-
value: null
|
|
4478
|
-
};
|
|
4479
|
-
function record(step) {
|
|
4480
|
-
steps.push(step);
|
|
4481
|
-
onStep?.(step);
|
|
4482
|
-
if (step.changes && step.changes.length > 0) {
|
|
4483
|
-
allChanges.push(...step.changes);
|
|
4484
|
-
}
|
|
4485
|
-
}
|
|
4486
|
-
function recordPending(key) {
|
|
4487
|
-
const strategy = answers.conflictDecisions[key];
|
|
4488
|
-
if (!strategy) return;
|
|
4489
|
-
if (strategyImplemented(key, strategy)) return;
|
|
4490
|
-
pending.push({
|
|
4491
|
-
key,
|
|
4492
|
-
strategy,
|
|
4493
|
-
reason: `Strategy "${strategy}" requires the managed-region engine (batch 4); recorded for follow-up.`
|
|
4494
|
-
});
|
|
4495
|
-
}
|
|
4496
|
-
function recordFailure(name, err) {
|
|
4497
|
-
const message = getErrorMessage(err);
|
|
4498
|
-
record({ name, status: "fail", detail: message });
|
|
4499
|
-
if (!firstFailure.value)
|
|
4500
|
-
firstFailure.value = { step: name, error: message };
|
|
4501
|
-
if (CRITICAL_STEPS.has(name)) aborted = true;
|
|
4502
|
-
}
|
|
4503
|
-
const tokensDecision = answers.conflictDecisions.tokens;
|
|
4504
|
-
const legacyTokensPaths = options.legacyTokensPaths ?? [];
|
|
4505
|
-
const wantMigrate = tokensDecision === "migrate" && legacyTokensPaths.length > 0;
|
|
4506
|
-
if (tokensDecision === "skip") {
|
|
4507
|
-
record({
|
|
4508
|
-
name: "tokens",
|
|
4509
|
-
status: "skip",
|
|
4510
|
-
detail: "conflict strategy = skip"
|
|
4511
|
-
});
|
|
4512
|
-
} else if (dryRun) {
|
|
4513
|
-
const planDetail = wantMigrate ? `runTokensInit(variant=${answers.variant}); migrateLegacyTokens(${legacyTokensPaths.length} file${legacyTokensPaths.length === 1 ? "" : "s"})` : `runTokensInit(variant=${answers.variant})`;
|
|
4514
|
-
record({
|
|
4515
|
-
name: "tokens",
|
|
4516
|
-
status: "planned",
|
|
4517
|
-
detail: planDetail
|
|
4518
|
-
});
|
|
4519
|
-
} else {
|
|
4520
|
-
try {
|
|
4521
|
-
const result = await runTokensInit({
|
|
4522
|
-
projectRoot,
|
|
4523
|
-
variant: answers.variant,
|
|
4524
|
-
ide
|
|
4525
|
-
});
|
|
4526
|
-
let detail = result.status === "installed" ? `${result.packageName}@${result.version} (${result.count} files)` : result.status;
|
|
4527
|
-
if (wantMigrate) {
|
|
4528
|
-
try {
|
|
4529
|
-
const m = await migrateLegacyTokens({
|
|
4530
|
-
projectRoot,
|
|
4531
|
-
legacyPaths: legacyTokensPaths
|
|
4532
|
-
});
|
|
4533
|
-
detail += `; migrated ${m.migrated.length}/${legacyTokensPaths.length} legacy file${legacyTokensPaths.length === 1 ? "" : "s"} \u2192 ${m.overridesPath}`;
|
|
4534
|
-
if (m.skipped.length > 0) {
|
|
4535
|
-
detail += ` (skipped ${m.skipped.length})`;
|
|
4536
|
-
}
|
|
4537
|
-
} catch (err) {
|
|
4538
|
-
detail += `; migrate failed: ${getErrorMessage(err)}`;
|
|
4539
|
-
}
|
|
4540
|
-
}
|
|
4541
|
-
record({
|
|
4542
|
-
name: "tokens",
|
|
4543
|
-
status: "ok",
|
|
4544
|
-
detail,
|
|
4545
|
-
changes: deriveTokensChanges(result, projectRoot)
|
|
4546
|
-
});
|
|
4547
|
-
} catch (err) {
|
|
4548
|
-
recordFailure("tokens", err);
|
|
4549
|
-
}
|
|
4550
|
-
}
|
|
4551
|
-
recordPending("tokens");
|
|
4552
|
-
const codeSkillId = `teamix-evo-code-${answers.variant}`;
|
|
4553
|
-
if (dryRun) {
|
|
4554
|
-
record({
|
|
4555
|
-
name: "skills",
|
|
4556
|
-
status: "planned",
|
|
4557
|
-
detail: `runSkillsAdd(${codeSkillId})`
|
|
4558
|
-
});
|
|
4559
|
-
} else if (aborted) {
|
|
4560
|
-
record({
|
|
4561
|
-
name: "skills",
|
|
4562
|
-
status: "skip",
|
|
4563
|
-
detail: "aborted: earlier critical step failed"
|
|
4564
|
-
});
|
|
4565
|
-
} else {
|
|
4566
|
-
try {
|
|
4567
|
-
const result = await runSkillsAdd({
|
|
4568
|
-
projectRoot,
|
|
4569
|
-
names: [codeSkillId],
|
|
4570
|
-
ides: answers.ides,
|
|
4571
|
-
scope: answers.scope,
|
|
4572
|
-
ide
|
|
4573
|
-
});
|
|
4574
|
-
record({
|
|
4575
|
-
name: "skills",
|
|
4576
|
-
status: "ok",
|
|
4577
|
-
detail: result.status === "installed" ? `added: ${result.addedSkillIds.join(", ") || "none"}; existing: ${result.skippedSkillIds.join(", ") || "none"}` : result.status,
|
|
4578
|
-
changes: deriveSkillsChanges(result, projectRoot)
|
|
4579
|
-
});
|
|
4580
|
-
} catch (err) {
|
|
4581
|
-
recordFailure("skills", err);
|
|
4582
|
-
}
|
|
4583
|
-
}
|
|
4584
|
-
const agentsMdDecision = answers.conflictDecisions["agents-md"];
|
|
4585
|
-
const agentsMdSkillIds = [
|
|
4586
|
-
`teamix-evo-design-${answers.variant}`,
|
|
4587
|
-
codeSkillId
|
|
4588
|
-
];
|
|
4589
|
-
if (agentsMdDecision === "skip") {
|
|
4590
|
-
record({
|
|
4591
|
-
name: "agents-md",
|
|
4592
|
-
status: "skip",
|
|
4593
|
-
detail: "conflict strategy = skip"
|
|
4594
|
-
});
|
|
4595
|
-
} else if (dryRun) {
|
|
4596
|
-
record({
|
|
4597
|
-
name: "agents-md",
|
|
4598
|
-
status: "planned",
|
|
4599
|
-
detail: `runGenerateAgentsMd(${agentsMdSkillIds.length} skills)`
|
|
4600
|
-
});
|
|
4601
|
-
} else {
|
|
4602
|
-
try {
|
|
4603
|
-
const result = await runGenerateAgentsMd({
|
|
4604
|
-
projectRoot,
|
|
4605
|
-
variant: answers.variant,
|
|
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"
|
|
4612
|
-
});
|
|
4613
|
-
record({
|
|
4614
|
-
name: "agents-md",
|
|
4615
|
-
status: "ok",
|
|
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
|
-
]
|
|
4625
|
-
});
|
|
4626
|
-
} catch (err) {
|
|
4627
|
-
recordFailure("agents-md", err);
|
|
4628
|
-
}
|
|
4629
|
-
}
|
|
4630
|
-
recordPending("agents-md");
|
|
4631
|
-
const componentsJsonDecision = answers.conflictDecisions["components-json"];
|
|
4632
|
-
const shadcnDecision = answers.conflictDecisions["shadcn-source"];
|
|
4633
|
-
const skipUiInit = !answers.withUi || componentsJsonDecision === "skip";
|
|
4634
|
-
let uiDecisionRequired;
|
|
4635
|
-
if (skipUiInit) {
|
|
4636
|
-
record({
|
|
4637
|
-
name: "ui-init",
|
|
4638
|
-
status: "skip",
|
|
4639
|
-
detail: !answers.withUi ? "withUi = false" : "components.json conflict strategy = skip"
|
|
4640
|
-
});
|
|
4641
|
-
record({
|
|
4642
|
-
name: "ui-add",
|
|
4643
|
-
status: "skip",
|
|
4644
|
-
detail: !answers.withUi ? "withUi = false" : "components.json conflict strategy = skip"
|
|
4645
|
-
});
|
|
4646
|
-
} else if (dryRun) {
|
|
4647
|
-
record({
|
|
4648
|
-
name: "ui-init",
|
|
4649
|
-
status: "planned",
|
|
4650
|
-
detail: "runUiInit()"
|
|
4651
|
-
});
|
|
4652
|
-
const entries = await resolveUiEntries(options).catch(() => [
|
|
4653
|
-
...BASELINE_UI_ENTRIES
|
|
4654
|
-
]);
|
|
3180
|
+
if (dryRun) {
|
|
4655
3181
|
record({
|
|
4656
|
-
name: "
|
|
3182
|
+
name: "skills",
|
|
4657
3183
|
status: "planned",
|
|
4658
|
-
detail: `
|
|
3184
|
+
detail: `runSkillsInit(scope=${scope})`
|
|
4659
3185
|
});
|
|
4660
|
-
} else {
|
|
4661
|
-
if (aborted) {
|
|
4662
|
-
record({
|
|
4663
|
-
name: "ui-init",
|
|
4664
|
-
status: "skip",
|
|
4665
|
-
detail: "aborted: earlier critical step failed"
|
|
4666
|
-
});
|
|
4667
|
-
record({
|
|
4668
|
-
name: "ui-add",
|
|
4669
|
-
status: "skip",
|
|
4670
|
-
detail: "aborted: earlier critical step failed"
|
|
4671
|
-
});
|
|
4672
|
-
} else {
|
|
4673
|
-
let uiInitOk = false;
|
|
4674
|
-
try {
|
|
4675
|
-
const initResult = await runUiInit({ projectRoot, ide });
|
|
4676
|
-
record({
|
|
4677
|
-
name: "ui-init",
|
|
4678
|
-
status: "ok",
|
|
4679
|
-
detail: initResult.status === "installed" ? "config.json packages.ui written" : initResult.status
|
|
4680
|
-
});
|
|
4681
|
-
uiInitOk = true;
|
|
4682
|
-
} catch (err) {
|
|
4683
|
-
recordFailure("ui-init", err);
|
|
4684
|
-
}
|
|
4685
|
-
if (!uiInitOk) {
|
|
4686
|
-
record({
|
|
4687
|
-
name: "ui-add",
|
|
4688
|
-
status: "skip",
|
|
4689
|
-
detail: "aborted: ui-init failed"
|
|
4690
|
-
});
|
|
4691
|
-
} else if (shadcnDecision === "skip") {
|
|
4692
|
-
record({
|
|
4693
|
-
name: "ui-add",
|
|
4694
|
-
status: "skip",
|
|
4695
|
-
detail: "shadcn-source conflict strategy = skip"
|
|
4696
|
-
});
|
|
4697
|
-
} else {
|
|
4698
|
-
try {
|
|
4699
|
-
const entries = await resolveUiEntries(options);
|
|
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({
|
|
4706
|
-
projectRoot,
|
|
4707
|
-
aliases,
|
|
4708
|
-
manifest
|
|
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
|
-
}
|
|
4871
|
-
} catch (err) {
|
|
4872
|
-
recordFailure("ui-add", err);
|
|
4873
|
-
}
|
|
4874
|
-
}
|
|
4875
|
-
}
|
|
4876
|
-
}
|
|
4877
|
-
recordPending("components-json");
|
|
4878
|
-
recordPending("shadcn-source");
|
|
4879
|
-
if (!answers.withLint) {
|
|
3186
|
+
} else if (aborted) {
|
|
4880
3187
|
record({
|
|
4881
|
-
name: "
|
|
3188
|
+
name: "skills",
|
|
4882
3189
|
status: "skip",
|
|
4883
|
-
detail: "
|
|
4884
|
-
});
|
|
4885
|
-
} else
|
|
4886
|
-
record({
|
|
4887
|
-
name: "lint",
|
|
4888
|
-
status: "planned",
|
|
4889
|
-
detail: "runLintInit()"
|
|
4890
|
-
});
|
|
4891
|
-
} else {
|
|
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 ?? [];
|
|
4897
|
-
const result = await runLintInit({
|
|
4898
|
-
projectRoot,
|
|
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
|
|
4904
|
-
});
|
|
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);
|
|
4923
|
-
}
|
|
4924
|
-
record({
|
|
4925
|
-
name: "lint",
|
|
4926
|
-
status: "ok",
|
|
4927
|
-
detail: detailParts.join(" / "),
|
|
4928
|
-
changes: deriveLintChanges(result)
|
|
4929
|
-
});
|
|
4930
|
-
} catch (err) {
|
|
4931
|
-
recordFailure("lint", err);
|
|
4932
|
-
}
|
|
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
|
-
]
|
|
4995
|
-
});
|
|
4996
|
-
}
|
|
4997
|
-
} catch (err) {
|
|
4998
|
-
if (err && typeof err === "object" && "__skip" in err) {
|
|
4999
|
-
} else {
|
|
5000
|
-
recordFailure("gitignore", err);
|
|
5001
|
-
}
|
|
5002
|
-
}
|
|
5003
|
-
}
|
|
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 {
|
|
5024
|
-
}
|
|
5025
|
-
}
|
|
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
|
|
5033
|
-
};
|
|
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"
|
|
5047
|
-
};
|
|
5048
|
-
}
|
|
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
|
-
}
|
|
5067
|
-
}
|
|
5068
|
-
return out;
|
|
5069
|
-
}
|
|
5070
|
-
|
|
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";
|
|
5077
|
-
|
|
5078
|
-
// src/core/tokens-update.ts
|
|
5079
|
-
import * as path30 from "path";
|
|
5080
|
-
import * as fs22 from "fs/promises";
|
|
5081
|
-
import {
|
|
5082
|
-
loadTokensPackageManifest as loadTokensPackageManifest2,
|
|
5083
|
-
getVariantEntry as getVariantEntry2
|
|
5084
|
-
} from "@teamix-evo/registry";
|
|
5085
|
-
|
|
5086
|
-
// src/core/upgrade-hints.ts
|
|
5087
|
-
import * as path29 from "path";
|
|
5088
|
-
var TEAMIX_DIR4 = ".teamix-evo";
|
|
5089
|
-
var HINTS_DIR = ".upgrade-hints";
|
|
5090
|
-
function isoToFsSafe3(iso) {
|
|
5091
|
-
return iso.replace(/[:.]/g, "-");
|
|
5092
|
-
}
|
|
5093
|
-
async function writeTokensUpgradeHint(options) {
|
|
5094
|
-
if (options.renames.length === 0) return null;
|
|
5095
|
-
const isoTs = options.isoTs ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
5096
|
-
const fsTs = isoToFsSafe3(isoTs);
|
|
5097
|
-
const filename = `tokens-${fsTs}.json`;
|
|
5098
|
-
const target = path29.join(
|
|
5099
|
-
options.projectRoot,
|
|
5100
|
-
TEAMIX_DIR4,
|
|
5101
|
-
HINTS_DIR,
|
|
5102
|
-
filename
|
|
5103
|
-
);
|
|
5104
|
-
const payload = {
|
|
5105
|
-
schemaVersion: 1,
|
|
5106
|
-
ts: isoTs,
|
|
5107
|
-
package: "tokens",
|
|
5108
|
-
trigger: options.trigger,
|
|
5109
|
-
fromVariant: options.fromVariant,
|
|
5110
|
-
toVariant: options.toVariant,
|
|
5111
|
-
fromVersion: options.fromVersion,
|
|
5112
|
-
toVersion: options.toVersion,
|
|
5113
|
-
renames: options.renames
|
|
5114
|
-
};
|
|
5115
|
-
await writeFileSafe(target, JSON.stringify(payload, null, 2) + "\n");
|
|
5116
|
-
return {
|
|
5117
|
-
path: target,
|
|
5118
|
-
ts: fsTs,
|
|
5119
|
-
renameCount: options.renames.length
|
|
5120
|
-
};
|
|
5121
|
-
}
|
|
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));
|
|
5126
|
-
}
|
|
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;
|
|
5136
|
-
}
|
|
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);
|
|
5157
|
-
}
|
|
5158
|
-
return updated;
|
|
5159
|
-
}
|
|
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
|
-
);
|
|
5183
|
-
}
|
|
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
|
-
);
|
|
5190
|
-
}
|
|
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;
|
|
5222
|
-
}
|
|
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;
|
|
5240
|
-
}
|
|
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
|
-
});
|
|
5250
|
-
}
|
|
5251
|
-
}
|
|
5252
|
-
refreshedResources.push(resource);
|
|
5253
|
-
}
|
|
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
|
-
};
|
|
5269
|
-
}
|
|
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);
|
|
5296
|
-
}
|
|
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({
|
|
5305
|
-
projectRoot,
|
|
5306
|
-
trigger: "update",
|
|
5307
|
-
fromVariant: currentVariant,
|
|
5308
|
-
toVariant: currentVariant,
|
|
5309
|
-
fromVersion: currentVersion,
|
|
5310
|
-
toVersion: variantEntry.version,
|
|
5311
|
-
renames
|
|
5312
|
-
});
|
|
5313
|
-
if (hint) hintPath = hint.path;
|
|
5314
|
-
}
|
|
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 } : {}
|
|
5327
|
-
};
|
|
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;
|
|
5336
|
-
}
|
|
5337
|
-
|
|
5338
|
-
// src/core/project-update.ts
|
|
5339
|
-
var DEFAULT_TOKENS_PACKAGE3 = "@teamix-evo/tokens";
|
|
5340
|
-
var DEFAULT_SKILLS_PACKAGE4 = "@teamix-evo/skills";
|
|
5341
|
-
var CRITICAL_STEPS2 = /* @__PURE__ */ new Set(["tokens"]);
|
|
5342
|
-
async function runProjectUpdate(options) {
|
|
5343
|
-
const { projectRoot, dryRun = false, onStep } = options;
|
|
5344
|
-
const tokensPackage = options.tokensPackageName ?? DEFAULT_TOKENS_PACKAGE3;
|
|
5345
|
-
const skillsPackage = options.skillsPackageName ?? DEFAULT_SKILLS_PACKAGE4;
|
|
5346
|
-
const config = await readProjectConfig(projectRoot);
|
|
5347
|
-
if (!config) {
|
|
5348
|
-
return { status: "not-initialized" };
|
|
5349
|
-
}
|
|
5350
|
-
let snapshot = null;
|
|
5351
|
-
let snapshotError;
|
|
5352
|
-
if (!dryRun) {
|
|
3190
|
+
detail: "aborted: earlier critical step failed"
|
|
3191
|
+
});
|
|
3192
|
+
} else {
|
|
5353
3193
|
try {
|
|
5354
|
-
|
|
3194
|
+
const result = await runSkillsInit({ projectRoot, ides, scope, ide });
|
|
3195
|
+
if (result.status === "already-initialized") {
|
|
3196
|
+
record({ name: "skills", status: "ok", detail: "already-initialized" });
|
|
3197
|
+
} else {
|
|
3198
|
+
record({
|
|
3199
|
+
name: "skills",
|
|
3200
|
+
status: "ok",
|
|
3201
|
+
detail: `${result.skillCount} skills, ${result.fileCount} files (added: ${result.addedSkillIds.join(", ") || "none"})`,
|
|
3202
|
+
changes: result.addedSkillIds.map((id) => ({
|
|
3203
|
+
kind: "created",
|
|
3204
|
+
path: `.teamix-evo/skills/${id}/SKILL.md`,
|
|
3205
|
+
step: "skills",
|
|
3206
|
+
detail: "skill installed"
|
|
3207
|
+
}))
|
|
3208
|
+
});
|
|
3209
|
+
}
|
|
5355
3210
|
} catch (err) {
|
|
5356
|
-
|
|
5357
|
-
}
|
|
5358
|
-
}
|
|
5359
|
-
const steps = [];
|
|
5360
|
-
let aborted = false;
|
|
5361
|
-
const firstFailure = { value: null };
|
|
5362
|
-
function record(step) {
|
|
5363
|
-
steps.push(step);
|
|
5364
|
-
onStep?.(step);
|
|
5365
|
-
}
|
|
5366
|
-
function recordFailure(name, err) {
|
|
5367
|
-
const message = getErrorMessage(err);
|
|
5368
|
-
record({ name, status: "fail", detail: message });
|
|
5369
|
-
if (!firstFailure.value) {
|
|
5370
|
-
firstFailure.value = { step: name, error: message };
|
|
3211
|
+
recordFailure("skills", err);
|
|
5371
3212
|
}
|
|
5372
|
-
if (CRITICAL_STEPS2.has(name)) aborted = true;
|
|
5373
3213
|
}
|
|
5374
|
-
|
|
5375
|
-
if (!config.packages?.tokens) {
|
|
3214
|
+
if (dryRun) {
|
|
5376
3215
|
record({
|
|
5377
|
-
name: "
|
|
5378
|
-
status: "
|
|
5379
|
-
detail: "
|
|
3216
|
+
name: "agents-md",
|
|
3217
|
+
status: "planned",
|
|
3218
|
+
detail: "runGenerateAgentsMd()"
|
|
5380
3219
|
});
|
|
5381
|
-
} else
|
|
3220
|
+
} else {
|
|
5382
3221
|
try {
|
|
5383
|
-
const
|
|
5384
|
-
|
|
3222
|
+
const result = await runGenerateAgentsMd({
|
|
3223
|
+
projectRoot,
|
|
3224
|
+
variant,
|
|
3225
|
+
skillIds: [
|
|
3226
|
+
`teamix-evo-design-${variant}`,
|
|
3227
|
+
`teamix-evo-code-${variant}`
|
|
3228
|
+
],
|
|
3229
|
+
mode: "merge-managed"
|
|
3230
|
+
});
|
|
3231
|
+
record({
|
|
3232
|
+
name: "agents-md",
|
|
3233
|
+
status: "ok",
|
|
3234
|
+
detail: `${result.skillCount} skill index`,
|
|
3235
|
+
changes: [
|
|
3236
|
+
{
|
|
3237
|
+
kind: result.backedUp ? "modified" : "created",
|
|
3238
|
+
path: toRelativePosix(result.path, projectRoot),
|
|
3239
|
+
step: "agents-md",
|
|
3240
|
+
detail: "skill-trigger fallback (ADR 0038)"
|
|
3241
|
+
}
|
|
3242
|
+
]
|
|
3243
|
+
});
|
|
5385
3244
|
} catch (err) {
|
|
5386
|
-
recordFailure("
|
|
3245
|
+
recordFailure("agents-md", err);
|
|
5387
3246
|
}
|
|
3247
|
+
}
|
|
3248
|
+
if (dryRun) {
|
|
3249
|
+
record({ name: "ui-init", status: "planned", detail: "runUiInit()" });
|
|
3250
|
+
} else if (aborted) {
|
|
3251
|
+
record({
|
|
3252
|
+
name: "ui-init",
|
|
3253
|
+
status: "skip",
|
|
3254
|
+
detail: "aborted: earlier critical step failed"
|
|
3255
|
+
});
|
|
5388
3256
|
} else {
|
|
5389
3257
|
try {
|
|
5390
|
-
const
|
|
5391
|
-
|
|
5392
|
-
|
|
3258
|
+
const initResult = await runUiInit({ projectRoot, ide });
|
|
3259
|
+
record({
|
|
3260
|
+
name: "ui-init",
|
|
3261
|
+
status: "ok",
|
|
3262
|
+
detail: initResult.status === "installed" ? "config.json packages.ui written" : initResult.status
|
|
5393
3263
|
});
|
|
5394
|
-
if (result.status === "not-initialized") {
|
|
5395
|
-
record({
|
|
5396
|
-
name: "tokens",
|
|
5397
|
-
status: "skip",
|
|
5398
|
-
detail: "tokens not installed"
|
|
5399
|
-
});
|
|
5400
|
-
} else if (result.status === "up-to-date") {
|
|
5401
|
-
const driftSuffix = result.frozenDrift.length > 0 ? `, frozen drift: ${result.frozenDrift.length}` : "";
|
|
5402
|
-
record({
|
|
5403
|
-
name: "tokens",
|
|
5404
|
-
status: "ok",
|
|
5405
|
-
detail: `up-to-date (${result.variant} v${result.version}${driftSuffix})`
|
|
5406
|
-
});
|
|
5407
|
-
} else {
|
|
5408
|
-
anyChanged = true;
|
|
5409
|
-
const extras = [];
|
|
5410
|
-
if (result.managedReplaced.length > 0)
|
|
5411
|
-
extras.push(`managed: ${result.managedReplaced.length}`);
|
|
5412
|
-
if (result.frozenDrift.length > 0)
|
|
5413
|
-
extras.push(`frozen drift: ${result.frozenDrift.length}`);
|
|
5414
|
-
const suffix = extras.length > 0 ? ` [${extras.join(", ")}]` : "";
|
|
5415
|
-
record({
|
|
5416
|
-
name: "tokens",
|
|
5417
|
-
status: "ok",
|
|
5418
|
-
detail: `${result.variant} v${result.from} \u2192 v${result.to}${suffix}`
|
|
5419
|
-
});
|
|
5420
|
-
}
|
|
5421
3264
|
} catch (err) {
|
|
5422
|
-
recordFailure("
|
|
3265
|
+
recordFailure("ui-init", err);
|
|
5423
3266
|
}
|
|
5424
3267
|
}
|
|
5425
|
-
|
|
3268
|
+
const collectedNpmDeps = {};
|
|
3269
|
+
if (dryRun) {
|
|
3270
|
+
const { manifest } = await loadUiData("@teamix-evo/ui").catch(() => ({
|
|
3271
|
+
manifest: { entries: [] }
|
|
3272
|
+
}));
|
|
5426
3273
|
record({
|
|
5427
|
-
name: "
|
|
5428
|
-
status: "
|
|
5429
|
-
detail:
|
|
3274
|
+
name: "ui-add",
|
|
3275
|
+
status: "planned",
|
|
3276
|
+
detail: `runUiAdd(${manifest.entries.length} entries, --all)`
|
|
5430
3277
|
});
|
|
5431
3278
|
} else if (aborted) {
|
|
5432
3279
|
record({
|
|
5433
|
-
name: "
|
|
3280
|
+
name: "ui-add",
|
|
5434
3281
|
status: "skip",
|
|
5435
3282
|
detail: "aborted: earlier critical step failed"
|
|
5436
3283
|
});
|
|
5437
3284
|
} else {
|
|
5438
3285
|
try {
|
|
5439
|
-
const
|
|
3286
|
+
const { manifest } = await loadUiData("@teamix-evo/ui");
|
|
3287
|
+
const allIds = manifest.entries.map((e) => e.id);
|
|
3288
|
+
const addResult = await runUiAdd({
|
|
5440
3289
|
projectRoot,
|
|
5441
|
-
|
|
5442
|
-
|
|
3290
|
+
ids: allIds,
|
|
3291
|
+
overwrite: true
|
|
3292
|
+
});
|
|
3293
|
+
const writtenResources = addResult.written > 0 ? addResult.resources.slice(-addResult.written) : [];
|
|
3294
|
+
record({
|
|
3295
|
+
name: "ui-add",
|
|
3296
|
+
status: "ok",
|
|
3297
|
+
detail: `${addResult.orderedIds.length} entries (${addResult.written} written, ${addResult.skipped} skipped)`,
|
|
3298
|
+
changes: writtenResources.map((r) => ({
|
|
3299
|
+
kind: "created",
|
|
3300
|
+
path: toRelativePosix(r.target, projectRoot),
|
|
3301
|
+
step: "ui-add",
|
|
3302
|
+
detail: r.strategy
|
|
3303
|
+
}))
|
|
3304
|
+
});
|
|
3305
|
+
Object.assign(collectedNpmDeps, addResult.npmDependencies);
|
|
3306
|
+
} catch (err) {
|
|
3307
|
+
recordFailure("ui-add", err);
|
|
3308
|
+
}
|
|
3309
|
+
}
|
|
3310
|
+
if (dryRun) {
|
|
3311
|
+
try {
|
|
3312
|
+
const listing = await listBizUiEntries(variant);
|
|
3313
|
+
record({
|
|
3314
|
+
name: "biz-ui-add",
|
|
3315
|
+
status: "planned",
|
|
3316
|
+
detail: `runBizUiAdd(variant=${variant}, ${listing.entries.length} entries)`
|
|
5443
3317
|
});
|
|
5444
|
-
|
|
3318
|
+
} catch {
|
|
3319
|
+
record({
|
|
3320
|
+
name: "biz-ui-add",
|
|
3321
|
+
status: "planned",
|
|
3322
|
+
detail: `runBizUiAdd(variant=${variant})`
|
|
3323
|
+
});
|
|
3324
|
+
}
|
|
3325
|
+
} else if (aborted) {
|
|
3326
|
+
record({
|
|
3327
|
+
name: "biz-ui-add",
|
|
3328
|
+
status: "skip",
|
|
3329
|
+
detail: "aborted: earlier critical step failed"
|
|
3330
|
+
});
|
|
3331
|
+
} else {
|
|
3332
|
+
try {
|
|
3333
|
+
const listing = await listBizUiEntries(variant);
|
|
3334
|
+
if (listing.entries.length === 0) {
|
|
5445
3335
|
record({
|
|
5446
|
-
name: "
|
|
3336
|
+
name: "biz-ui-add",
|
|
5447
3337
|
status: "skip",
|
|
5448
|
-
detail:
|
|
3338
|
+
detail: `no biz-ui entries for variant "${variant}"`
|
|
3339
|
+
});
|
|
3340
|
+
} else {
|
|
3341
|
+
const allIds = listing.entries.map((e) => e.id);
|
|
3342
|
+
const result = await runBizUiAdd({
|
|
3343
|
+
projectRoot,
|
|
3344
|
+
variant,
|
|
3345
|
+
ids: allIds,
|
|
3346
|
+
overwrite: true
|
|
5449
3347
|
});
|
|
5450
|
-
} else if (result.status === "no-changes") {
|
|
5451
3348
|
record({
|
|
5452
|
-
name: "
|
|
3349
|
+
name: "biz-ui-add",
|
|
5453
3350
|
status: "ok",
|
|
5454
|
-
detail:
|
|
3351
|
+
detail: `${result.orderedIds.length} entries (${result.written} written, ${result.skipped} skipped)`
|
|
5455
3352
|
});
|
|
5456
|
-
|
|
5457
|
-
|
|
5458
|
-
|
|
5459
|
-
record({ name: "skills", status: "planned", detail });
|
|
5460
|
-
} else {
|
|
5461
|
-
anyChanged = true;
|
|
5462
|
-
const summary = result.updatedSkillIds.length > 0 ? `updated: ${result.updatedSkillIds.join(", ")} (v${result.version})` : `refreshed at v${result.version}`;
|
|
5463
|
-
record({ name: "skills", status: "ok", detail: summary });
|
|
3353
|
+
if (result.npmDependencies) {
|
|
3354
|
+
Object.assign(collectedNpmDeps, result.npmDependencies);
|
|
3355
|
+
}
|
|
5464
3356
|
}
|
|
5465
3357
|
} catch (err) {
|
|
5466
|
-
recordFailure("
|
|
3358
|
+
recordFailure("biz-ui-add", err);
|
|
5467
3359
|
}
|
|
5468
3360
|
}
|
|
5469
|
-
|
|
5470
|
-
|
|
5471
|
-
|
|
5472
|
-
|
|
5473
|
-
|
|
5474
|
-
|
|
5475
|
-
|
|
5476
|
-
|
|
5477
|
-
|
|
5478
|
-
|
|
5479
|
-
|
|
5480
|
-
|
|
5481
|
-
|
|
5482
|
-
});
|
|
5483
|
-
const out = (() => {
|
|
5484
|
-
if (firstFailure.value) {
|
|
5485
|
-
return {
|
|
5486
|
-
status: "partial",
|
|
5487
|
-
steps,
|
|
5488
|
-
resumeHint: {
|
|
5489
|
-
failedAt: firstFailure.value.step,
|
|
5490
|
-
completed: steps.filter((s) => s.status === "ok").map((s) => s.name),
|
|
5491
|
-
failed: steps.filter((s) => s.status === "fail").map((s) => s.name),
|
|
5492
|
-
error: firstFailure.value.error,
|
|
5493
|
-
resumeCommand: "teamix-evo update"
|
|
5494
|
-
},
|
|
5495
|
-
snapshot,
|
|
5496
|
-
...snapshotError ? { snapshotError } : {}
|
|
5497
|
-
};
|
|
3361
|
+
if (!dryRun && !aborted && Object.keys(collectedNpmDeps).length > 0) {
|
|
3362
|
+
try {
|
|
3363
|
+
await installProjectDeps({
|
|
3364
|
+
projectRoot,
|
|
3365
|
+
npmDependencies: collectedNpmDeps,
|
|
3366
|
+
skipInstall: options.skipInstall ?? false
|
|
3367
|
+
});
|
|
3368
|
+
} catch (err) {
|
|
3369
|
+
logger.warn(
|
|
3370
|
+
`\u5B89\u88C5 ui/biz-ui \u4F20\u9012\u4F9D\u8D56\u5931\u8D25\uFF1A${getErrorMessage(
|
|
3371
|
+
err
|
|
3372
|
+
)}\uFF08\u53EF\u624B\u52A8\u8FD0\u884C \`npm install\` \u91CD\u8BD5\uFF09`
|
|
3373
|
+
);
|
|
5498
3374
|
}
|
|
5499
|
-
if (dryRun) return { status: "dry-run", steps, snapshot: null };
|
|
5500
|
-
if (anyChanged)
|
|
5501
|
-
return {
|
|
5502
|
-
status: "updated",
|
|
5503
|
-
steps,
|
|
5504
|
-
snapshot,
|
|
5505
|
-
...snapshotError ? { snapshotError } : {}
|
|
5506
|
-
};
|
|
5507
|
-
return {
|
|
5508
|
-
status: "up-to-date",
|
|
5509
|
-
steps,
|
|
5510
|
-
snapshot,
|
|
5511
|
-
...snapshotError ? { snapshotError } : {}
|
|
5512
|
-
};
|
|
5513
|
-
})();
|
|
5514
|
-
return out;
|
|
5515
|
-
}
|
|
5516
|
-
async function runComponentSourceStep(category, args) {
|
|
5517
|
-
const { projectRoot, config, dryRun, record, recordFailure } = args;
|
|
5518
|
-
const cfgKey = category === "ui" ? "ui" : "biz-ui";
|
|
5519
|
-
if (!config.packages?.[cfgKey]) {
|
|
5520
|
-
record({
|
|
5521
|
-
name: category,
|
|
5522
|
-
status: "skip",
|
|
5523
|
-
detail: `${category} not installed`
|
|
5524
|
-
});
|
|
5525
|
-
return;
|
|
5526
3375
|
}
|
|
5527
|
-
|
|
5528
|
-
|
|
5529
|
-
|
|
5530
|
-
|
|
5531
|
-
|
|
5532
|
-
|
|
5533
|
-
|
|
5534
|
-
|
|
5535
|
-
|
|
5536
|
-
|
|
5537
|
-
|
|
5538
|
-
|
|
5539
|
-
|
|
5540
|
-
|
|
5541
|
-
|
|
5542
|
-
|
|
5543
|
-
|
|
5544
|
-
|
|
5545
|
-
|
|
5546
|
-
|
|
5547
|
-
|
|
5548
|
-
|
|
5549
|
-
|
|
5550
|
-
|
|
5551
|
-
|
|
5552
|
-
|
|
5553
|
-
|
|
5554
|
-
|
|
5555
|
-
|
|
5556
|
-
}
|
|
5557
|
-
return;
|
|
3376
|
+
if (dryRun) {
|
|
3377
|
+
record({ name: "lint", status: "planned", detail: "runLintInit()" });
|
|
3378
|
+
} else {
|
|
3379
|
+
try {
|
|
3380
|
+
const result = await runLintInit({
|
|
3381
|
+
projectRoot,
|
|
3382
|
+
skipInstall: options.skipInstall ?? false,
|
|
3383
|
+
eslintStrategy: "overwrite",
|
|
3384
|
+
stylelintStrategy: "overwrite",
|
|
3385
|
+
eslintExistingPaths: [],
|
|
3386
|
+
stylelintExistingPaths: []
|
|
3387
|
+
});
|
|
3388
|
+
const detailParts = [];
|
|
3389
|
+
if (result.status === "installed") {
|
|
3390
|
+
detailParts.push(
|
|
3391
|
+
`eslint=${result.eslint}, stylelint=${result.stylelint}`
|
|
3392
|
+
);
|
|
3393
|
+
if (result.packageJsonPatched) detailParts.push("package.json patched");
|
|
3394
|
+
} else {
|
|
3395
|
+
detailParts.push(result.status);
|
|
3396
|
+
}
|
|
3397
|
+
record({
|
|
3398
|
+
name: "lint",
|
|
3399
|
+
status: "ok",
|
|
3400
|
+
detail: detailParts.join(" / "),
|
|
3401
|
+
changes: deriveLintChanges(result)
|
|
3402
|
+
});
|
|
3403
|
+
} catch (err) {
|
|
3404
|
+
recordFailure("lint", err);
|
|
3405
|
+
}
|
|
5558
3406
|
}
|
|
5559
3407
|
if (dryRun) {
|
|
5560
|
-
const total = report.registeredIds.length + report.unregisteredIds.length;
|
|
5561
3408
|
record({
|
|
5562
|
-
name:
|
|
3409
|
+
name: "gitignore",
|
|
5563
3410
|
status: "planned",
|
|
5564
|
-
detail:
|
|
3411
|
+
detail: "append teamix-evo runtime rules"
|
|
5565
3412
|
});
|
|
5566
|
-
|
|
3413
|
+
} else {
|
|
3414
|
+
try {
|
|
3415
|
+
let projectRootExists = true;
|
|
3416
|
+
try {
|
|
3417
|
+
await fsNode.access(projectRoot);
|
|
3418
|
+
} catch {
|
|
3419
|
+
projectRootExists = false;
|
|
3420
|
+
}
|
|
3421
|
+
if (!projectRootExists) {
|
|
3422
|
+
record({
|
|
3423
|
+
name: "gitignore",
|
|
3424
|
+
status: "skip",
|
|
3425
|
+
detail: "projectRoot does not exist"
|
|
3426
|
+
});
|
|
3427
|
+
} else {
|
|
3428
|
+
const giPath = path19.join(projectRoot, ".gitignore");
|
|
3429
|
+
let giContent = "";
|
|
3430
|
+
try {
|
|
3431
|
+
giContent = await fsNode.readFile(giPath, "utf-8");
|
|
3432
|
+
} catch {
|
|
3433
|
+
}
|
|
3434
|
+
if (giContent.includes(GITIGNORE_MARKER_START)) {
|
|
3435
|
+
record({
|
|
3436
|
+
name: "gitignore",
|
|
3437
|
+
status: "skip",
|
|
3438
|
+
detail: "teamix-evo markers already present"
|
|
3439
|
+
});
|
|
3440
|
+
} else {
|
|
3441
|
+
const block = [
|
|
3442
|
+
"",
|
|
3443
|
+
GITIGNORE_MARKER_START,
|
|
3444
|
+
...GITIGNORE_RULES,
|
|
3445
|
+
GITIGNORE_MARKER_END,
|
|
3446
|
+
""
|
|
3447
|
+
].join("\n");
|
|
3448
|
+
const separator = giContent.length > 0 && !giContent.endsWith("\n") ? "\n" : "";
|
|
3449
|
+
await fsNode.writeFile(
|
|
3450
|
+
giPath,
|
|
3451
|
+
giContent + separator + block,
|
|
3452
|
+
"utf-8"
|
|
3453
|
+
);
|
|
3454
|
+
record({
|
|
3455
|
+
name: "gitignore",
|
|
3456
|
+
status: "ok",
|
|
3457
|
+
detail: `${GITIGNORE_RULES.filter((r) => r && !r.startsWith("#")).length} rules appended`,
|
|
3458
|
+
changes: [
|
|
3459
|
+
{
|
|
3460
|
+
kind: "modified",
|
|
3461
|
+
path: ".gitignore",
|
|
3462
|
+
step: "gitignore",
|
|
3463
|
+
detail: "teamix-evo runtime artifact rules"
|
|
3464
|
+
}
|
|
3465
|
+
]
|
|
3466
|
+
});
|
|
3467
|
+
}
|
|
3468
|
+
}
|
|
3469
|
+
} catch (err) {
|
|
3470
|
+
recordFailure("gitignore", err);
|
|
3471
|
+
}
|
|
5567
3472
|
}
|
|
5568
|
-
|
|
5569
|
-
|
|
5570
|
-
|
|
5571
|
-
|
|
5572
|
-
|
|
5573
|
-
|
|
5574
|
-
|
|
5575
|
-
|
|
5576
|
-
|
|
5577
|
-
|
|
5578
|
-
|
|
5579
|
-
|
|
5580
|
-
|
|
5581
|
-
|
|
5582
|
-
|
|
5583
|
-
|
|
3473
|
+
const status = dryRun ? "dry-run" : steps.some((s) => s.status === "fail") ? "partial" : "installed";
|
|
3474
|
+
const out = { status, steps, changes: allChanges };
|
|
3475
|
+
if (firstFailure.value) {
|
|
3476
|
+
out.resumeHint = {
|
|
3477
|
+
failedAt: firstFailure.value.step,
|
|
3478
|
+
completed: steps.filter((s) => s.status === "ok").map((s) => s.name),
|
|
3479
|
+
failed: steps.filter((s) => s.status === "fail").map((s) => s.name),
|
|
3480
|
+
error: firstFailure.value.error,
|
|
3481
|
+
resumeCommand: "teamix-evo init"
|
|
3482
|
+
};
|
|
3483
|
+
}
|
|
3484
|
+
if (!dryRun) {
|
|
3485
|
+
try {
|
|
3486
|
+
const checklistContent = renderInitChecklist({ variant, status, steps });
|
|
3487
|
+
const checklistPath = path19.join(
|
|
3488
|
+
projectRoot,
|
|
3489
|
+
".teamix-evo",
|
|
3490
|
+
"init-checklist.md"
|
|
3491
|
+
);
|
|
3492
|
+
await fsNode.mkdir(path19.dirname(checklistPath), { recursive: true });
|
|
3493
|
+
await fsNode.writeFile(checklistPath, checklistContent, "utf-8");
|
|
3494
|
+
logger.info(" wrote .teamix-evo/init-checklist.md");
|
|
3495
|
+
} catch {
|
|
3496
|
+
logger.warn(" failed to write init-checklist.md (non-fatal)");
|
|
5584
3497
|
}
|
|
5585
|
-
const stagingRel = path31.relative(projectRoot, built.stagingDir);
|
|
5586
|
-
const summary = summarizeStagingRisk(built.manifest.summary.byRisk);
|
|
5587
|
-
record({
|
|
5588
|
-
name: category,
|
|
5589
|
-
status: "ok",
|
|
5590
|
-
detail: `${built.manifest.summary.total} component(s) staged${summary ? ` (${summary})` : ""}, see ${stagingRel}`
|
|
5591
|
-
});
|
|
5592
|
-
} catch (err) {
|
|
5593
|
-
recordFailure(category, err);
|
|
5594
3498
|
}
|
|
3499
|
+
return out;
|
|
5595
3500
|
}
|
|
5596
|
-
function
|
|
5597
|
-
|
|
5598
|
-
|
|
5599
|
-
|
|
5600
|
-
|
|
5601
|
-
|
|
5602
|
-
|
|
5603
|
-
|
|
5604
|
-
|
|
5605
|
-
|
|
5606
|
-
|
|
5607
|
-
|
|
5608
|
-
|
|
5609
|
-
|
|
5610
|
-
|
|
5611
|
-
|
|
5612
|
-
|
|
5613
|
-
|
|
5614
|
-
if (!tokensCfg) return "tokens not installed";
|
|
5615
|
-
const packageRoot = resolveTokensPackageRoot(tokensPackage);
|
|
5616
|
-
const catalog = await loadTokensPackageManifest3(packageRoot);
|
|
5617
|
-
const variantEntry = getVariantEntry3(catalog, tokensCfg.variant);
|
|
5618
|
-
if (!variantEntry) {
|
|
5619
|
-
return `variant "${tokensCfg.variant}" no longer in ${tokensPackage}@${catalog.version} \u2014 uninstall + re-init to switch`;
|
|
3501
|
+
function deriveLintChanges(result) {
|
|
3502
|
+
if (result.status !== "installed") return [];
|
|
3503
|
+
const out = [];
|
|
3504
|
+
if (result.eslint) {
|
|
3505
|
+
out.push({
|
|
3506
|
+
kind: "created",
|
|
3507
|
+
path: "eslint.config.js",
|
|
3508
|
+
step: "lint",
|
|
3509
|
+
detail: "@teamix-evo/eslint-config"
|
|
3510
|
+
});
|
|
3511
|
+
}
|
|
3512
|
+
if (result.stylelint) {
|
|
3513
|
+
out.push({
|
|
3514
|
+
kind: "created",
|
|
3515
|
+
path: "stylelint.config.cjs",
|
|
3516
|
+
step: "lint",
|
|
3517
|
+
detail: "@teamix-evo/stylelint-config"
|
|
3518
|
+
});
|
|
5620
3519
|
}
|
|
5621
|
-
if (
|
|
5622
|
-
|
|
3520
|
+
if (result.packageJsonPatched) {
|
|
3521
|
+
out.push({
|
|
3522
|
+
kind: "created",
|
|
3523
|
+
path: "package.json",
|
|
3524
|
+
step: "lint",
|
|
3525
|
+
detail: 'scripts.lint / scripts["lint:css"]'
|
|
3526
|
+
});
|
|
5623
3527
|
}
|
|
5624
|
-
return
|
|
3528
|
+
return out;
|
|
5625
3529
|
}
|
|
5626
3530
|
|
|
5627
3531
|
// src/core/installer.ts
|
|
5628
|
-
import * as
|
|
5629
|
-
import * as
|
|
3532
|
+
import * as path20 from "path";
|
|
3533
|
+
import * as fs15 from "fs/promises";
|
|
5630
3534
|
async function installResources(options) {
|
|
5631
3535
|
const { projectRoot, manifest, data, variantDir, packageRoot } = options;
|
|
5632
3536
|
const installedResources = [];
|
|
@@ -5663,13 +3567,13 @@ async function installSingleResource(resource, projectRoot, data, variantDir, pa
|
|
|
5663
3567
|
variantDir,
|
|
5664
3568
|
packageRoot
|
|
5665
3569
|
);
|
|
5666
|
-
const targetPath =
|
|
3570
|
+
const targetPath = path20.join(projectRoot, resource.target);
|
|
5667
3571
|
let content;
|
|
5668
3572
|
if (resource.template) {
|
|
5669
3573
|
const templateContent = await loadTemplateFile(sourcePath);
|
|
5670
3574
|
content = renderTemplate(templateContent, data);
|
|
5671
3575
|
} else {
|
|
5672
|
-
content = await
|
|
3576
|
+
content = await fs15.readFile(sourcePath, "utf-8");
|
|
5673
3577
|
}
|
|
5674
3578
|
await writeFileSafe(targetPath, content);
|
|
5675
3579
|
const hash = computeHash(content);
|
|
@@ -5687,13 +3591,13 @@ async function installRecursiveResource(resource, projectRoot, data, variantDir,
|
|
|
5687
3591
|
variantDir,
|
|
5688
3592
|
packageRoot
|
|
5689
3593
|
);
|
|
5690
|
-
const targetDir =
|
|
3594
|
+
const targetDir = path20.join(projectRoot, resource.target);
|
|
5691
3595
|
const results = [];
|
|
5692
3596
|
await ensureDir(targetDir);
|
|
5693
3597
|
const entries = await walkDir(sourcePath);
|
|
5694
3598
|
for (const entry of entries) {
|
|
5695
|
-
const relPath =
|
|
5696
|
-
let targetFile =
|
|
3599
|
+
const relPath = path20.relative(sourcePath, entry);
|
|
3600
|
+
let targetFile = path20.join(targetDir, relPath);
|
|
5697
3601
|
if (resource.template && targetFile.endsWith(".hbs")) {
|
|
5698
3602
|
targetFile = targetFile.slice(0, -4);
|
|
5699
3603
|
}
|
|
@@ -5702,11 +3606,11 @@ async function installRecursiveResource(resource, projectRoot, data, variantDir,
|
|
|
5702
3606
|
const templateContent = await loadTemplateFile(entry);
|
|
5703
3607
|
content = renderTemplate(templateContent, data);
|
|
5704
3608
|
} else {
|
|
5705
|
-
content = await
|
|
3609
|
+
content = await fs15.readFile(entry, "utf-8");
|
|
5706
3610
|
}
|
|
5707
3611
|
await writeFileSafe(targetFile, content);
|
|
5708
3612
|
const hash = computeHash(content);
|
|
5709
|
-
const targetRel =
|
|
3613
|
+
const targetRel = path20.relative(projectRoot, targetFile);
|
|
5710
3614
|
results.push({
|
|
5711
3615
|
id: `${resource.id}:${relPath}`,
|
|
5712
3616
|
target: targetRel,
|
|
@@ -5719,25 +3623,25 @@ async function installRecursiveResource(resource, projectRoot, data, variantDir,
|
|
|
5719
3623
|
}
|
|
5720
3624
|
|
|
5721
3625
|
// src/core/registry-client.ts
|
|
5722
|
-
import * as
|
|
5723
|
-
import * as
|
|
5724
|
-
import { createRequire as
|
|
3626
|
+
import * as path21 from "path";
|
|
3627
|
+
import * as fs16 from "fs/promises";
|
|
3628
|
+
import { createRequire as createRequire5 } from "module";
|
|
5725
3629
|
import { loadVariantManifest } from "@teamix-evo/registry";
|
|
5726
|
-
var require6 =
|
|
5727
|
-
function
|
|
3630
|
+
var require6 = createRequire5(import.meta.url);
|
|
3631
|
+
function resolvePackageRoot4(packageName) {
|
|
5728
3632
|
const pkgJsonPath = require6.resolve(`${packageName}/package.json`);
|
|
5729
|
-
return
|
|
3633
|
+
return path21.dirname(pkgJsonPath);
|
|
5730
3634
|
}
|
|
5731
3635
|
async function loadVariantData(packageName, variant) {
|
|
5732
|
-
const packageRoot =
|
|
5733
|
-
const variantDir =
|
|
3636
|
+
const packageRoot = resolvePackageRoot4(packageName);
|
|
3637
|
+
const variantDir = path21.join(packageRoot, "library", variant);
|
|
5734
3638
|
logger.debug(`Resolved variant dir: ${variantDir}`);
|
|
5735
3639
|
logger.debug(`Package root: ${packageRoot}`);
|
|
5736
3640
|
const manifest = await loadVariantManifest(variantDir);
|
|
5737
3641
|
let data = {};
|
|
5738
|
-
const dataPath =
|
|
3642
|
+
const dataPath = path21.join(variantDir, "_data.json");
|
|
5739
3643
|
try {
|
|
5740
|
-
const raw = await
|
|
3644
|
+
const raw = await fs16.readFile(dataPath, "utf-8");
|
|
5741
3645
|
data = JSON.parse(raw);
|
|
5742
3646
|
} catch (err) {
|
|
5743
3647
|
if (err.code !== "ENOENT") {
|
|
@@ -5774,7 +3678,6 @@ export {
|
|
|
5774
3678
|
runGenerateAgentsMd,
|
|
5775
3679
|
runLintInit,
|
|
5776
3680
|
runProjectInit,
|
|
5777
|
-
runProjectUpdate,
|
|
5778
3681
|
runSkillsAdd,
|
|
5779
3682
|
runSkillsInit,
|
|
5780
3683
|
runSkillsUpdate,
|