teamix-evo 0.9.0 → 0.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +35 -30
- package/dist/core/index.d.ts +79 -1
- package/dist/core/index.js +2286 -1426
- package/dist/core/index.js.map +1 -1
- package/dist/index.js +2237 -326
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
package/dist/core/index.js
CHANGED
|
@@ -369,13 +369,20 @@ function resolveSourcePath(source, variantDir, packageRoot) {
|
|
|
369
369
|
}
|
|
370
370
|
return path6.join(variantDir, source);
|
|
371
371
|
}
|
|
372
|
-
|
|
372
|
+
var DEFAULT_SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
373
|
+
"node_modules",
|
|
374
|
+
"dist",
|
|
375
|
+
"build",
|
|
376
|
+
".teamix-evo"
|
|
377
|
+
]);
|
|
378
|
+
async function walkDir(dir, skipDirs) {
|
|
373
379
|
const files = [];
|
|
374
380
|
const entries = await fs4.readdir(dir, { withFileTypes: true });
|
|
375
381
|
for (const entry of entries) {
|
|
376
382
|
const fullPath = path6.join(dir, entry.name);
|
|
377
383
|
if (entry.isDirectory()) {
|
|
378
|
-
|
|
384
|
+
if (skipDirs && skipDirs.has(entry.name)) continue;
|
|
385
|
+
files.push(...await walkDir(fullPath, skipDirs));
|
|
379
386
|
} else if (entry.isFile()) {
|
|
380
387
|
files.push(fullPath);
|
|
381
388
|
}
|
|
@@ -1251,13 +1258,16 @@ Run \`npx teamix-evo@latest tokens list-variants\` to see all options.`
|
|
|
1251
1258
|
if (!await fileExists(overridesAbs)) {
|
|
1252
1259
|
await writeFileSafe(overridesAbs, EMPTY_OVERRIDES_TEMPLATE);
|
|
1253
1260
|
}
|
|
1254
|
-
const
|
|
1255
|
-
installed.
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
+
const overridesId = `tokens:${CONSUMER_OVERRIDES_FILE}`;
|
|
1262
|
+
if (!installed.some((r) => r.id === overridesId)) {
|
|
1263
|
+
const overridesContent = await fs6.readFile(overridesAbs, "utf-8");
|
|
1264
|
+
installed.push({
|
|
1265
|
+
id: overridesId,
|
|
1266
|
+
target: path9.posix.join(CONSUMER_TOKENS_DIR, CONSUMER_OVERRIDES_FILE),
|
|
1267
|
+
hash: computeHash(overridesContent),
|
|
1268
|
+
strategy: "frozen"
|
|
1269
|
+
});
|
|
1270
|
+
}
|
|
1261
1271
|
const lock = {
|
|
1262
1272
|
schemaVersion: 1,
|
|
1263
1273
|
variant: {
|
|
@@ -2208,6 +2218,33 @@ async function runLintInit(options) {
|
|
|
2208
2218
|
wroteStylelint = true;
|
|
2209
2219
|
}
|
|
2210
2220
|
const packageJsonPatched = await patchPackageJsonScripts(projectRoot);
|
|
2221
|
+
let stylelintIgnoreFilesWarning = false;
|
|
2222
|
+
if (!stylelintNeedsWrite && stylelintTemplateExists) {
|
|
2223
|
+
try {
|
|
2224
|
+
const existingContent = fs9.readFileSync(stylelintConfigPath, "utf-8");
|
|
2225
|
+
const usesTeamixPreset = existingContent.includes("@teamix-evo/stylelint-config/presets/") || existingContent.includes("@teamix-evo/stylelint-config/preset/");
|
|
2226
|
+
const hasTokenIgnore = existingContent.includes("tokens.theme.css") && existingContent.includes("tokens.overrides.css");
|
|
2227
|
+
if (!usesTeamixPreset && !hasTokenIgnore) {
|
|
2228
|
+
stylelintIgnoreFilesWarning = true;
|
|
2229
|
+
logger.warn(
|
|
2230
|
+
[
|
|
2231
|
+
"\u68C0\u6D4B\u5230\u73B0\u6709 stylelint \u914D\u7F6E\u672A\u6392\u9664 token \u5B9A\u4E49\u6587\u4EF6\u3002",
|
|
2232
|
+
"\u5EFA\u8BAE\u5728 stylelint.config.cjs \u4E2D\u6DFB\u52A0 ignoreFiles:",
|
|
2233
|
+
"",
|
|
2234
|
+
" ignoreFiles: [",
|
|
2235
|
+
" '**/tokens.theme.css',",
|
|
2236
|
+
" '**/tokens.overrides.css',",
|
|
2237
|
+
" ]",
|
|
2238
|
+
"",
|
|
2239
|
+
"\u6216\u5207\u6362\u5230 teamix-evo \u9884\u8BBE\u4EE5\u81EA\u52A8\u83B7\u5F97\u6392\u9664\u89C4\u5219:",
|
|
2240
|
+
"",
|
|
2241
|
+
" extends: ['@teamix-evo/stylelint-config/presets/consumer']"
|
|
2242
|
+
].join("\n")
|
|
2243
|
+
);
|
|
2244
|
+
}
|
|
2245
|
+
} catch {
|
|
2246
|
+
}
|
|
2247
|
+
}
|
|
2211
2248
|
return {
|
|
2212
2249
|
status: "installed",
|
|
2213
2250
|
eslint: wroteEslint,
|
|
@@ -2216,7 +2253,8 @@ async function runLintInit(options) {
|
|
|
2216
2253
|
stylelintMergeRequested: wroteStylelint && stylelintStrategy === "merge" && stylelintExistingPaths.length > 0,
|
|
2217
2254
|
eslintSkipped: eslintSkipRequested,
|
|
2218
2255
|
stylelintSkipped: stylelintSkipRequested,
|
|
2219
|
-
packageJsonPatched
|
|
2256
|
+
packageJsonPatched,
|
|
2257
|
+
stylelintIgnoreFilesWarning
|
|
2220
2258
|
};
|
|
2221
2259
|
}
|
|
2222
2260
|
function detectPm(projectRoot) {
|
|
@@ -2364,7 +2402,7 @@ function renderManagedBlockBody(args) {
|
|
|
2364
2402
|
return `# AGENTS.md
|
|
2365
2403
|
|
|
2366
2404
|
> \u672C\u5DE5\u7A0B\u5DF2\u88C5\u914D Teamix Evo AI skills\u3002AI \u52A9\u624B\u5728\u4EE5\u4E0B\u573A\u666F\u4E0B**\u5FC5\u987B\u5148\u8BFB\u5BF9\u5E94 skill** \u518D\u52A8\u624B\u3002
|
|
2367
|
-
> \u672C\u6587\u4EF6\u7531 \`teamix-evo init\` / \`create-teamix-evo\` \u81EA\u52A8\u751F\u6210\uFF08regenerable\uFF0C
|
|
2405
|
+
> \u672C\u6587\u4EF6\u7531 \`teamix-evo init\` / \`create-teamix-evo\` \u81EA\u52A8\u751F\u6210\uFF08regenerable\uFF0C\u9075\u5FAA ADR 0038\uFF09\uFF0C\u5237\u65B0\u65B9\u5F0F\u89C1\u5E95\u90E8\u3002
|
|
2368
2406
|
|
|
2369
2407
|
## \u5DF2\u88C5 Skills\uFF08variant: ${variant}\uFF09
|
|
2370
2408
|
|
|
@@ -2377,6 +2415,13 @@ ${skillBlock}
|
|
|
2377
2415
|
- \u6A21\u7CCA\u573A\u666F\uFF1A\u5148\u6309 SKIP \u53CD\u5411\u6392\u9664\uFF0C\u5269\u4F59\u552F\u4E00 skill \u5373\u4E3A\u5165\u53E3
|
|
2378
2416
|
- \u751F\u547D\u5468\u671F\u547D\u4EE4\uFF08\`init\` / \`update\` / \`add\`\uFF09\u8D70 \`teamix-evo-manage\`\uFF08\u5168\u5C40 skill\uFF0C\u672C\u6587\u4EF6\u4E0D\u5217\uFF09
|
|
2379
2417
|
|
|
2418
|
+
## UI \u7EC4\u4EF6\u9886\u5730\uFF08init \u540E\u7EA6\u675F\uFF09
|
|
2419
|
+
|
|
2420
|
+
- \`src/components/ui/\` \u7531 teamix-evo \u63A5\u7BA1\uFF0C\u7981\u6B62\u624B\u5DE5\u6216\u901A\u8FC7 shadcn CLI \u6DFB\u52A0\u65B0\u7EC4\u4EF6
|
|
2421
|
+
- \u5982\u9700\u65B0\u589E UI \u7EC4\u4EF6\uFF0C\u4F7F\u7528 \`npx teamix-evo ui add <id>\`
|
|
2422
|
+
- \`src/components/shadcn-ui/\` \u662F init \u524D legacy \u7EC4\u4EF6\u5F52\u6863\uFF0C\u53EA\u8BFB\uFF1B\u65B0\u4EE3\u7801\u4E0D\u5E94\u518D import
|
|
2423
|
+
- \u5347\u7EA7 legacy \u7EC4\u4EF6\uFF1A\u89E6\u53D1 teamix-evo-upgrade skill
|
|
2424
|
+
|
|
2380
2425
|
> \u5237\u65B0\u672C\u6587\u4EF6\uFF1A\`npx teamix-evo skills add\` \u6216\u91CD\u8DD1 \`npm create teamix-evo\` / \`teamix-evo init\`\u3002`;
|
|
2381
2426
|
}
|
|
2382
2427
|
function wrapManagedBlock(body) {
|
|
@@ -2933,1546 +2978,2361 @@ async function migrateLegacyTokens(options) {
|
|
|
2933
2978
|
};
|
|
2934
2979
|
}
|
|
2935
2980
|
|
|
2936
|
-
// src/core/
|
|
2981
|
+
// src/core/ui-conflict-detector.ts
|
|
2937
2982
|
import * as fs14 from "fs/promises";
|
|
2938
2983
|
import * as path18 from "path";
|
|
2939
|
-
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
}
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
|
|
2960
|
-
|
|
2961
|
-
|
|
2962
|
-
|
|
2963
|
-
|
|
2964
|
-
|
|
2965
|
-
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
|
|
2969
|
-
|
|
2970
|
-
|
|
2971
|
-
|
|
2972
|
-
|
|
2973
|
-
await fs14.cp(src, dst, { recursive: true });
|
|
2984
|
+
function looksLikeShadcnOriginal(content) {
|
|
2985
|
+
if (content.includes("data-slot=")) return false;
|
|
2986
|
+
if (content.includes("@teamix-evo")) return false;
|
|
2987
|
+
if (content.includes("teamix-evo:managed")) return false;
|
|
2988
|
+
return true;
|
|
2989
|
+
}
|
|
2990
|
+
async function detectUiConflicts(options) {
|
|
2991
|
+
const { projectRoot, aliases, manifest } = options;
|
|
2992
|
+
const conflicts = [];
|
|
2993
|
+
const conflictDirSet = /* @__PURE__ */ new Set();
|
|
2994
|
+
let totalChecked = 0;
|
|
2995
|
+
for (const entry of manifest.entries) {
|
|
2996
|
+
for (const file of entry.files) {
|
|
2997
|
+
totalChecked++;
|
|
2998
|
+
const aliasDir = aliases[file.targetAlias];
|
|
2999
|
+
if (!aliasDir) continue;
|
|
3000
|
+
const targetAbs = path18.join(projectRoot, aliasDir, file.targetName);
|
|
3001
|
+
const exists = await fileExists(targetAbs);
|
|
3002
|
+
if (!exists) continue;
|
|
3003
|
+
let isShadcnOriginal = true;
|
|
3004
|
+
try {
|
|
3005
|
+
const content = await fs14.readFile(targetAbs, "utf-8");
|
|
3006
|
+
isShadcnOriginal = looksLikeShadcnOriginal(content);
|
|
3007
|
+
} catch {
|
|
3008
|
+
}
|
|
3009
|
+
const relativePath = path18.relative(projectRoot, targetAbs).replace(/\\/g, "/");
|
|
3010
|
+
conflicts.push({
|
|
3011
|
+
id: entry.id,
|
|
3012
|
+
targetPath: targetAbs,
|
|
3013
|
+
relativePath,
|
|
3014
|
+
isShadcnOriginal
|
|
3015
|
+
});
|
|
3016
|
+
conflictDirSet.add(aliasDir);
|
|
3017
|
+
}
|
|
2974
3018
|
}
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
|
|
3019
|
+
return {
|
|
3020
|
+
conflictEntries: conflicts,
|
|
3021
|
+
unconflictedTargets: totalChecked - conflicts.length,
|
|
3022
|
+
totalEntries: totalChecked,
|
|
3023
|
+
shouldBlock: conflicts.length > 0,
|
|
3024
|
+
conflictDirs: [...conflictDirSet]
|
|
2978
3025
|
};
|
|
2979
|
-
await fs14.writeFile(
|
|
2980
|
-
path18.join(target, META_FILE),
|
|
2981
|
-
JSON.stringify(meta, null, 2) + "\n",
|
|
2982
|
-
"utf-8"
|
|
2983
|
-
);
|
|
2984
|
-
logger.debug(
|
|
2985
|
-
`Snapshot created \u2192 ${path18.relative(projectRoot, target)} (${meta.reason})`
|
|
2986
|
-
);
|
|
2987
|
-
const keep = opts.keep ?? DEFAULT_KEEP;
|
|
2988
|
-
await pruneSnapshots(projectRoot, keep, { protectedTs: opts.protectedTs });
|
|
2989
|
-
return { ts, path: target };
|
|
2990
3026
|
}
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
|
|
3027
|
+
|
|
3028
|
+
// src/core/ui-isolate.ts
|
|
3029
|
+
import * as fs15 from "fs/promises";
|
|
3030
|
+
import * as path19 from "path";
|
|
3031
|
+
var IGNORE_DIR_NAMES = DEFAULT_SKIP_DIRS;
|
|
3032
|
+
async function runUiIsolate(options) {
|
|
3033
|
+
const { projectRoot, aliases } = options;
|
|
3034
|
+
const componentsDir = path19.join(projectRoot, aliases.components);
|
|
3035
|
+
const legacyDir = path19.join(
|
|
3036
|
+
projectRoot,
|
|
3037
|
+
aliases.components.replace(/\/ui\/?$/, "/shadcn-ui")
|
|
3038
|
+
);
|
|
3039
|
+
const movedFiles = [];
|
|
3040
|
+
const backedUpFiles = [];
|
|
3041
|
+
let componentsJsonRemoved = false;
|
|
3042
|
+
if (await fileExists(componentsDir)) {
|
|
3043
|
+
await ensureDir(legacyDir);
|
|
3044
|
+
const UI_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
3045
|
+
".ts",
|
|
3046
|
+
".tsx",
|
|
3047
|
+
".js",
|
|
3048
|
+
".jsx",
|
|
3049
|
+
".css",
|
|
3050
|
+
".json",
|
|
3051
|
+
".md",
|
|
3052
|
+
".mdx"
|
|
3053
|
+
]);
|
|
3054
|
+
const allUiFiles = await walkDir(componentsDir);
|
|
3055
|
+
const uiFiles = allUiFiles.filter(
|
|
3056
|
+
(f) => UI_EXTENSIONS.has(path19.extname(f))
|
|
3057
|
+
);
|
|
3058
|
+
for (const srcFile of uiFiles) {
|
|
3059
|
+
const relFromUi = path19.relative(componentsDir, srcFile);
|
|
3060
|
+
const destFile = path19.join(legacyDir, relFromUi);
|
|
3061
|
+
await ensureDir(path19.dirname(destFile));
|
|
3062
|
+
const content = await fs15.readFile(srcFile, "utf-8");
|
|
3063
|
+
await fs15.writeFile(destFile, content, "utf-8");
|
|
3064
|
+
try {
|
|
3065
|
+
await fs15.unlink(srcFile);
|
|
3066
|
+
} catch {
|
|
3067
|
+
logger.warn(`Could not remove: ${srcFile}`);
|
|
3068
|
+
}
|
|
3069
|
+
const fromRel = path19.relative(projectRoot, srcFile).replace(/\\/g, "/");
|
|
3070
|
+
const toRel = path19.relative(projectRoot, destFile).replace(/\\/g, "/");
|
|
3071
|
+
movedFiles.push({ from: fromRel, to: toRel });
|
|
3072
|
+
}
|
|
3073
|
+
await pruneEmptyDirs(componentsDir);
|
|
3074
|
+
logger.info(
|
|
3075
|
+
` moved ${movedFiles.length} files \u2192 ${path19.relative(
|
|
3076
|
+
projectRoot,
|
|
3077
|
+
legacyDir
|
|
3078
|
+
)}/`
|
|
3079
|
+
);
|
|
2999
3080
|
}
|
|
3000
|
-
const
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
const
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
const
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
|
|
3081
|
+
const importRewrites = /* @__PURE__ */ new Map();
|
|
3082
|
+
const srcDir = path19.join(projectRoot, "src");
|
|
3083
|
+
if (await fileExists(srcDir)) {
|
|
3084
|
+
const SCAN_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mdx"]);
|
|
3085
|
+
const allScanFiles = await walkDir(srcDir, DEFAULT_SKIP_DIRS);
|
|
3086
|
+
const scanFiles = allScanFiles.filter((f) => {
|
|
3087
|
+
const rel2 = path19.relative(projectRoot, f);
|
|
3088
|
+
const segments = rel2.split(path19.sep);
|
|
3089
|
+
if (segments.some((s) => IGNORE_DIR_NAMES.has(s))) return false;
|
|
3090
|
+
return SCAN_EXTENSIONS.has(path19.extname(f));
|
|
3091
|
+
});
|
|
3092
|
+
const oldAlias = aliases.components;
|
|
3093
|
+
const newAlias = oldAlias.replace(/\/ui\/?$/, "/shadcn-ui");
|
|
3094
|
+
const oldWithoutSrc = oldAlias.replace(/^src\//, "");
|
|
3095
|
+
const newWithoutSrc = newAlias.replace(/^src\//, "");
|
|
3096
|
+
const dirName = path19.basename(oldAlias);
|
|
3097
|
+
const newDirName = path19.basename(newAlias);
|
|
3098
|
+
for (const file of scanFiles) {
|
|
3099
|
+
const content = await fs15.readFile(file, "utf-8");
|
|
3100
|
+
let modified = content;
|
|
3101
|
+
let count = 0;
|
|
3102
|
+
modified = modified.replace(
|
|
3103
|
+
new RegExp(`(['"'\`])@/${escapeRegExp2(oldWithoutSrc)}/`, "g"),
|
|
3104
|
+
(match, quote) => {
|
|
3105
|
+
count++;
|
|
3106
|
+
return `${quote}@/${newWithoutSrc}/`;
|
|
3107
|
+
}
|
|
3108
|
+
);
|
|
3109
|
+
modified = modified.replace(
|
|
3110
|
+
new RegExp(`(['"'\`])~/${escapeRegExp2(oldWithoutSrc)}/`, "g"),
|
|
3111
|
+
(match, quote) => {
|
|
3112
|
+
count++;
|
|
3113
|
+
return `${quote}~/${newWithoutSrc}/`;
|
|
3114
|
+
}
|
|
3115
|
+
);
|
|
3116
|
+
modified = modified.replace(
|
|
3117
|
+
new RegExp(
|
|
3118
|
+
`(from\\s+['"'\`](?:\\.\\.?\\/)+(?:[\\w.-]+\\/)*)${escapeRegExp2(
|
|
3119
|
+
dirName
|
|
3120
|
+
)}/`,
|
|
3121
|
+
"g"
|
|
3122
|
+
),
|
|
3123
|
+
(match) => {
|
|
3124
|
+
count++;
|
|
3125
|
+
return match.slice(0, -dirName.length - 1) + `${newDirName}/`;
|
|
3126
|
+
}
|
|
3127
|
+
);
|
|
3128
|
+
if (count > 0) {
|
|
3129
|
+
await fs15.writeFile(file, modified, "utf-8");
|
|
3130
|
+
const relPath = path19.relative(projectRoot, file).replace(/\\/g, "/");
|
|
3131
|
+
importRewrites.set(relPath, count);
|
|
3014
3132
|
}
|
|
3133
|
+
}
|
|
3134
|
+
logger.info(` rewrote imports in ${importRewrites.size} files`);
|
|
3135
|
+
}
|
|
3136
|
+
const componentsJsonPath = path19.join(projectRoot, "components.json");
|
|
3137
|
+
if (await fileExists(componentsJsonPath)) {
|
|
3138
|
+
await backupFile(componentsJsonPath, projectRoot);
|
|
3139
|
+
backedUpFiles.push("components.json");
|
|
3140
|
+
try {
|
|
3141
|
+
await fs15.unlink(componentsJsonPath);
|
|
3142
|
+
componentsJsonRemoved = true;
|
|
3143
|
+
logger.info(" backed up and removed components.json");
|
|
3015
3144
|
} catch {
|
|
3016
|
-
|
|
3145
|
+
logger.warn(" could not remove components.json");
|
|
3017
3146
|
}
|
|
3018
|
-
result.push({ ts: entry.name, isoTs, reason, path: dir });
|
|
3019
3147
|
}
|
|
3020
|
-
|
|
3021
|
-
|
|
3022
|
-
|
|
3023
|
-
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
for (const snap of toRemove) {
|
|
3032
|
-
await fs14.rm(snap.path, { recursive: true, force: true });
|
|
3033
|
-
removed.push(snap.ts);
|
|
3034
|
-
logger.debug(`Pruned snapshot ${snap.ts}`);
|
|
3148
|
+
const libBackupTargets = [
|
|
3149
|
+
path19.join(projectRoot, aliases.utils + ".ts"),
|
|
3150
|
+
path19.join(projectRoot, aliases.lib, "color.ts")
|
|
3151
|
+
];
|
|
3152
|
+
const hooksDir = path19.join(projectRoot, aliases.hooks);
|
|
3153
|
+
if (await fileExists(hooksDir)) {
|
|
3154
|
+
const allHookFiles = await walkDir(hooksDir, DEFAULT_SKIP_DIRS);
|
|
3155
|
+
const hookFiles = allHookFiles.filter(
|
|
3156
|
+
(f) => path19.extname(f) === ".ts" || path19.extname(f) === ".tsx"
|
|
3157
|
+
);
|
|
3158
|
+
libBackupTargets.push(...hookFiles);
|
|
3035
3159
|
}
|
|
3036
|
-
|
|
3037
|
-
|
|
3038
|
-
|
|
3039
|
-
|
|
3040
|
-
|
|
3041
|
-
|
|
3042
|
-
|
|
3043
|
-
let rel2 = p;
|
|
3044
|
-
if (path19.isAbsolute(p)) {
|
|
3045
|
-
rel2 = path19.relative(projectRoot, p);
|
|
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
|
+
}
|
|
3046
3167
|
}
|
|
3047
|
-
return
|
|
3168
|
+
return {
|
|
3169
|
+
movedFiles,
|
|
3170
|
+
importRewrites,
|
|
3171
|
+
componentsJsonRemoved,
|
|
3172
|
+
backedUpFiles
|
|
3173
|
+
};
|
|
3048
3174
|
}
|
|
3049
|
-
|
|
3050
|
-
|
|
3051
|
-
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
const
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
} catch (err) {
|
|
3059
|
-
if (err.code === "ENOENT") continue;
|
|
3060
|
-
throw err;
|
|
3061
|
-
}
|
|
3062
|
-
for (const e of entries) {
|
|
3063
|
-
const full = path19.join(dir, e.name);
|
|
3064
|
-
if (e.isDirectory()) {
|
|
3065
|
-
stack.push(full);
|
|
3066
|
-
} else if (e.isFile() && e.name.endsWith(".bak")) {
|
|
3067
|
-
const rel2 = path19.relative(backupsDir, full);
|
|
3068
|
-
const original = stripBackupSuffix(rel2);
|
|
3069
|
-
if (original) out.add(original.split(path19.sep).join("/"));
|
|
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));
|
|
3070
3184
|
}
|
|
3071
3185
|
}
|
|
3186
|
+
const remaining = await fs15.readdir(dir);
|
|
3187
|
+
if (remaining.length === 0) {
|
|
3188
|
+
await fs15.rmdir(dir);
|
|
3189
|
+
}
|
|
3190
|
+
} catch {
|
|
3072
3191
|
}
|
|
3073
|
-
return out;
|
|
3074
3192
|
}
|
|
3075
|
-
|
|
3076
|
-
|
|
3077
|
-
|
|
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")
|
|
3078
3224
|
);
|
|
3079
|
-
|
|
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
|
+
};
|
|
3080
3247
|
}
|
|
3081
|
-
function
|
|
3082
|
-
const
|
|
3083
|
-
|
|
3084
|
-
|
|
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);
|
|
3085
3258
|
}
|
|
3086
|
-
return
|
|
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";
|
|
3087
3287
|
}
|
|
3088
3288
|
|
|
3089
|
-
// src/core/
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
|
|
3093
|
-
|
|
3094
|
-
"
|
|
3095
|
-
"
|
|
3096
|
-
"collapsible",
|
|
3097
|
-
"dialog",
|
|
3098
|
-
"dropdown-menu",
|
|
3099
|
-
"tabs",
|
|
3100
|
-
"table",
|
|
3101
|
-
"sidebar",
|
|
3102
|
-
"page-shell",
|
|
3103
|
-
"page-header"
|
|
3104
|
-
];
|
|
3105
|
-
var CRITICAL_STEPS = /* @__PURE__ */ new Set([
|
|
3106
|
-
"tokens",
|
|
3107
|
-
"skills",
|
|
3108
|
-
"ui-init"
|
|
3109
|
-
]);
|
|
3110
|
-
var IMPLEMENTED_STRATEGIES = {
|
|
3111
|
-
// 'agents-md': both 'merge-managed' (Phase 2.B — splice the
|
|
3112
|
-
// teamix-evo-skills managed region) and 'overwrite' (full rewrite) write
|
|
3113
|
-
// through `runGenerateAgentsMd`.
|
|
3114
|
-
"agents-md": ["merge-managed", "overwrite", "skip"],
|
|
3115
|
-
tokens: ["migrate", "overwrite", "skip"],
|
|
3116
|
-
"components-json": ["overwrite", "skip"],
|
|
3117
|
-
"shadcn-source": ["overwrite", "skip-existing", "skip"],
|
|
3118
|
-
"tailwind-config": ["skip"],
|
|
3119
|
-
"index-css": ["skip"],
|
|
3120
|
-
// Phase 3.E: lint conflict strategies are honored end-to-end by
|
|
3121
|
-
// `runLintInit` (backup user file + write template, optional AI-assist hint).
|
|
3122
|
-
"eslint-config": ["merge", "backup-overwrite", "skip", "overwrite"],
|
|
3123
|
-
"stylelint-config": ["merge", "backup-overwrite", "skip", "overwrite"]
|
|
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"
|
|
3124
3296
|
};
|
|
3125
|
-
function
|
|
3126
|
-
return
|
|
3127
|
-
}
|
|
3128
|
-
function strategyImplemented(key, strategy) {
|
|
3129
|
-
return IMPLEMENTED_STRATEGIES[key]?.includes(strategy) ?? false;
|
|
3297
|
+
function isoToFsSafe(iso) {
|
|
3298
|
+
return iso.replace(/[:.]/g, "-");
|
|
3130
3299
|
}
|
|
3131
|
-
async function
|
|
3132
|
-
|
|
3133
|
-
|
|
3134
|
-
|
|
3135
|
-
if (options.answers.uiSelection === "all") {
|
|
3136
|
-
const { manifest } = await loadUiData("@teamix-evo/ui");
|
|
3137
|
-
return manifest.entries.map((e) => e.id);
|
|
3300
|
+
async function buildUiUpgradeStaging(options) {
|
|
3301
|
+
const { lineageReport, category } = options;
|
|
3302
|
+
if (lineageReport.lineage !== "teamix-evo" && lineageReport.lineage !== "mixed") {
|
|
3303
|
+
return null;
|
|
3138
3304
|
}
|
|
3139
|
-
|
|
3140
|
-
|
|
3141
|
-
|
|
3142
|
-
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
|
|
3148
|
-
|
|
3149
|
-
|
|
3150
|
-
|
|
3151
|
-
|
|
3152
|
-
|
|
3153
|
-
|
|
3154
|
-
|
|
3155
|
-
|
|
3156
|
-
|
|
3157
|
-
|
|
3158
|
-
|
|
3159
|
-
|
|
3160
|
-
|
|
3161
|
-
|
|
3162
|
-
|
|
3163
|
-
|
|
3164
|
-
|
|
3165
|
-
|
|
3166
|
-
|
|
3167
|
-
|
|
3168
|
-
|
|
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
|
|
3169
3335
|
});
|
|
3170
|
-
|
|
3336
|
+
if (built) entries.push(built);
|
|
3171
3337
|
}
|
|
3172
|
-
|
|
3173
|
-
|
|
3174
|
-
|
|
3175
|
-
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
|
|
3180
|
-
path: "eslint.config.js",
|
|
3181
|
-
step: "lint",
|
|
3182
|
-
detail: "@teamix-evo/eslint-config consumer preset"
|
|
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
|
|
3183
3346
|
});
|
|
3347
|
+
if (built) entries.push(built);
|
|
3184
3348
|
}
|
|
3185
|
-
if (
|
|
3186
|
-
|
|
3187
|
-
|
|
3188
|
-
|
|
3189
|
-
|
|
3190
|
-
|
|
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"
|
|
3191
3383
|
});
|
|
3192
3384
|
}
|
|
3193
|
-
if (
|
|
3194
|
-
|
|
3195
|
-
|
|
3196
|
-
|
|
3197
|
-
|
|
3198
|
-
|
|
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"
|
|
3199
3394
|
});
|
|
3200
3395
|
}
|
|
3201
|
-
|
|
3202
|
-
|
|
3203
|
-
|
|
3204
|
-
const
|
|
3205
|
-
const
|
|
3206
|
-
|
|
3207
|
-
|
|
3208
|
-
const allChanges = [];
|
|
3209
|
-
const backupsBefore = dryRun ? /* @__PURE__ */ new Set() : await listBackupOriginals(projectRoot).catch(() => /* @__PURE__ */ new Set());
|
|
3210
|
-
let snapshot = null;
|
|
3211
|
-
let snapshotError;
|
|
3212
|
-
if (!dryRun) {
|
|
3213
|
-
try {
|
|
3214
|
-
snapshot = await createSnapshot(projectRoot, { reason: "init" });
|
|
3215
|
-
} catch (err) {
|
|
3216
|
-
snapshotError = getErrorMessage(err);
|
|
3217
|
-
}
|
|
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;
|
|
3218
3403
|
}
|
|
3219
|
-
|
|
3220
|
-
const
|
|
3221
|
-
|
|
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
|
|
3222
3439
|
};
|
|
3223
|
-
|
|
3224
|
-
|
|
3225
|
-
|
|
3226
|
-
|
|
3227
|
-
|
|
3228
|
-
|
|
3229
|
-
|
|
3230
|
-
|
|
3231
|
-
|
|
3232
|
-
|
|
3233
|
-
|
|
3234
|
-
|
|
3235
|
-
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
|
|
3242
|
-
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
|
|
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 };
|
|
3246
3495
|
}
|
|
3247
|
-
const
|
|
3248
|
-
const
|
|
3249
|
-
const
|
|
3250
|
-
|
|
3251
|
-
|
|
3252
|
-
|
|
3253
|
-
|
|
3254
|
-
|
|
3255
|
-
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
|
|
3259
|
-
|
|
3260
|
-
|
|
3261
|
-
|
|
3262
|
-
|
|
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";
|
|
3263
3515
|
} else {
|
|
3264
|
-
|
|
3265
|
-
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
|
|
3279
|
-
|
|
3280
|
-
|
|
3281
|
-
|
|
3282
|
-
|
|
3283
|
-
|
|
3284
|
-
|
|
3285
|
-
|
|
3286
|
-
|
|
3287
|
-
status: "ok",
|
|
3288
|
-
detail,
|
|
3289
|
-
changes: deriveTokensChanges(result, projectRoot)
|
|
3290
|
-
});
|
|
3291
|
-
} catch (err) {
|
|
3292
|
-
recordFailure("tokens", err);
|
|
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]);
|
|
3293
3539
|
}
|
|
3294
3540
|
}
|
|
3295
|
-
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
|
|
3300
|
-
|
|
3301
|
-
|
|
3302
|
-
|
|
3303
|
-
|
|
3304
|
-
|
|
3305
|
-
|
|
3306
|
-
|
|
3307
|
-
|
|
3308
|
-
|
|
3309
|
-
} else {
|
|
3310
|
-
try {
|
|
3311
|
-
const result = await runSkillsAdd({
|
|
3312
|
-
projectRoot,
|
|
3313
|
-
names: [codeSkillId],
|
|
3314
|
-
ides: answers.ides,
|
|
3315
|
-
scope: answers.scope,
|
|
3316
|
-
ide
|
|
3317
|
-
});
|
|
3318
|
-
record({
|
|
3319
|
-
name: "skills",
|
|
3320
|
-
status: "ok",
|
|
3321
|
-
detail: result.status === "installed" ? `added: ${result.addedSkillIds.join(", ") || "none"}; existing: ${result.skippedSkillIds.join(", ") || "none"}` : result.status,
|
|
3322
|
-
changes: deriveSkillsChanges(result, projectRoot)
|
|
3323
|
-
});
|
|
3324
|
-
} catch (err) {
|
|
3325
|
-
recordFailure("skills", err);
|
|
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);
|
|
3326
3555
|
}
|
|
3327
3556
|
}
|
|
3328
|
-
|
|
3329
|
-
|
|
3330
|
-
|
|
3331
|
-
|
|
3332
|
-
|
|
3333
|
-
|
|
3334
|
-
|
|
3335
|
-
|
|
3336
|
-
|
|
3337
|
-
|
|
3338
|
-
|
|
3339
|
-
|
|
3340
|
-
|
|
3341
|
-
|
|
3342
|
-
|
|
3343
|
-
|
|
3344
|
-
|
|
3345
|
-
|
|
3346
|
-
|
|
3347
|
-
|
|
3348
|
-
projectRoot,
|
|
3349
|
-
variant: answers.variant,
|
|
3350
|
-
skillIds: agentsMdSkillIds,
|
|
3351
|
-
// Phase 2.B: when the user picked `merge-managed` on conflict, only
|
|
3352
|
-
// rewrite the `teamix-evo-skills` managed region so hand-written
|
|
3353
|
-
// sections survive the regenerate step. `overwrite` keeps the
|
|
3354
|
-
// historical full-rewrite default.
|
|
3355
|
-
mode: agentsMdDecision === "merge-managed" ? "merge-managed" : "overwrite"
|
|
3356
|
-
});
|
|
3357
|
-
record({
|
|
3358
|
-
name: "agents-md",
|
|
3359
|
-
status: "ok",
|
|
3360
|
-
detail: `${result.skillCount} skill index${result.missingSkillIds.length > 0 ? ` (missing SKILL.md: ${result.missingSkillIds.join(", ")})` : ""}`,
|
|
3361
|
-
changes: [
|
|
3362
|
-
{
|
|
3363
|
-
kind: result.backedUp ? "modified" : "created",
|
|
3364
|
-
path: toRelativePosix(result.path, projectRoot),
|
|
3365
|
-
step: "agents-md",
|
|
3366
|
-
detail: "skill-trigger fallback (ADR 0038)"
|
|
3367
|
-
}
|
|
3368
|
-
]
|
|
3369
|
-
});
|
|
3370
|
-
} catch (err) {
|
|
3371
|
-
recordFailure("agents-md", err);
|
|
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
|
+
}
|
|
3372
3577
|
}
|
|
3373
3578
|
}
|
|
3374
|
-
|
|
3375
|
-
|
|
3376
|
-
const
|
|
3377
|
-
|
|
3378
|
-
|
|
3379
|
-
|
|
3380
|
-
|
|
3381
|
-
|
|
3382
|
-
|
|
3383
|
-
|
|
3384
|
-
|
|
3385
|
-
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
|
|
3390
|
-
|
|
3391
|
-
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
|
|
3396
|
-
|
|
3397
|
-
|
|
3398
|
-
|
|
3399
|
-
|
|
3400
|
-
|
|
3401
|
-
|
|
3402
|
-
|
|
3403
|
-
|
|
3404
|
-
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
|
|
3409
|
-
|
|
3410
|
-
|
|
3411
|
-
|
|
3412
|
-
|
|
3413
|
-
|
|
3414
|
-
|
|
3415
|
-
|
|
3416
|
-
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
|
|
3426
|
-
|
|
3427
|
-
|
|
3428
|
-
|
|
3429
|
-
|
|
3430
|
-
|
|
3431
|
-
|
|
3432
|
-
detail: "aborted: ui-init failed"
|
|
3433
|
-
});
|
|
3434
|
-
} else if (shadcnDecision === "skip") {
|
|
3435
|
-
record({
|
|
3436
|
-
name: "ui-add",
|
|
3437
|
-
status: "skip",
|
|
3438
|
-
detail: "shadcn-source conflict strategy = skip"
|
|
3439
|
-
});
|
|
3440
|
-
} else {
|
|
3441
|
-
try {
|
|
3442
|
-
const entries = await resolveUiEntries(options);
|
|
3443
|
-
const addResult = await runUiAdd({
|
|
3444
|
-
projectRoot,
|
|
3445
|
-
ids: entries,
|
|
3446
|
-
// 'overwrite' strategy → overwrite=true; everything else (incl.
|
|
3447
|
-
// 'skip-existing' which is the default) → overwrite=false.
|
|
3448
|
-
overwrite: shadcnDecision === "overwrite"
|
|
3449
|
-
});
|
|
3450
|
-
record({
|
|
3451
|
-
name: "ui-add",
|
|
3452
|
-
status: "ok",
|
|
3453
|
-
detail: `${addResult.orderedIds.length} entries (${addResult.written} written, ${addResult.skipped} skipped)`,
|
|
3454
|
-
changes: deriveUiAddChanges(addResult, projectRoot)
|
|
3455
|
-
});
|
|
3456
|
-
} catch (err) {
|
|
3457
|
-
recordFailure("ui-add", err);
|
|
3458
|
-
}
|
|
3459
|
-
}
|
|
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);
|
|
3460
3637
|
}
|
|
3461
3638
|
}
|
|
3462
|
-
|
|
3463
|
-
|
|
3464
|
-
|
|
3465
|
-
|
|
3466
|
-
|
|
3467
|
-
|
|
3468
|
-
|
|
3469
|
-
|
|
3470
|
-
|
|
3471
|
-
|
|
3472
|
-
|
|
3473
|
-
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
}
|
|
3477
|
-
|
|
3478
|
-
|
|
3479
|
-
|
|
3480
|
-
|
|
3481
|
-
|
|
3482
|
-
|
|
3483
|
-
|
|
3484
|
-
|
|
3485
|
-
|
|
3486
|
-
|
|
3487
|
-
|
|
3488
|
-
|
|
3489
|
-
|
|
3490
|
-
|
|
3491
|
-
|
|
3492
|
-
|
|
3493
|
-
|
|
3494
|
-
|
|
3495
|
-
|
|
3496
|
-
|
|
3497
|
-
|
|
3498
|
-
|
|
3499
|
-
|
|
3500
|
-
|
|
3501
|
-
|
|
3502
|
-
|
|
3503
|
-
|
|
3504
|
-
|
|
3505
|
-
|
|
3506
|
-
|
|
3507
|
-
|
|
3508
|
-
|
|
3509
|
-
|
|
3510
|
-
|
|
3511
|
-
|
|
3512
|
-
|
|
3513
|
-
|
|
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);
|
|
3514
3734
|
}
|
|
3515
3735
|
}
|
|
3516
|
-
|
|
3517
|
-
|
|
3518
|
-
|
|
3519
|
-
|
|
3520
|
-
|
|
3521
|
-
|
|
3522
|
-
|
|
3523
|
-
|
|
3524
|
-
|
|
3525
|
-
|
|
3526
|
-
|
|
3527
|
-
|
|
3528
|
-
|
|
3529
|
-
|
|
3530
|
-
|
|
3531
|
-
path: rel2,
|
|
3532
|
-
step: "backup",
|
|
3533
|
-
detail: ".teamix-evo/.backups/<\u540C\u8DEF\u5F84>.<isoTs>.bak"
|
|
3534
|
-
});
|
|
3535
|
-
}
|
|
3536
|
-
}
|
|
3537
|
-
} catch {
|
|
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--;
|
|
3538
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]);
|
|
3539
3763
|
}
|
|
3540
|
-
const
|
|
3541
|
-
|
|
3542
|
-
status,
|
|
3543
|
-
steps,
|
|
3544
|
-
pendingConflictWork: pending,
|
|
3545
|
-
changes: allChanges,
|
|
3546
|
-
snapshot
|
|
3547
|
-
};
|
|
3548
|
-
if (snapshotError) out.snapshotError = snapshotError;
|
|
3549
|
-
if (firstFailure.value) {
|
|
3550
|
-
out.resumeHint = {
|
|
3551
|
-
failedAt: firstFailure.value.step,
|
|
3552
|
-
completed: steps.filter((s) => s.status === "ok").map((s) => s.name),
|
|
3553
|
-
failed: steps.filter((s) => s.status === "fail").map((s) => s.name),
|
|
3554
|
-
error: firstFailure.value.error,
|
|
3555
|
-
// Every sub-step is idempotent (returns `already-initialized` when its
|
|
3556
|
-
// state file already exists), so a plain re-run resumes from the
|
|
3557
|
-
// failed step. A richer --resume model lands with batch 3 (lock
|
|
3558
|
-
// snapshot + restore).
|
|
3559
|
-
resumeCommand: "teamix-evo init"
|
|
3560
|
-
};
|
|
3764
|
+
for (const m of src.matchAll(/--([a-z][a-z0-9-]*)\s*:/g)) {
|
|
3765
|
+
if (m[1]) out.add(m[1]);
|
|
3561
3766
|
}
|
|
3562
3767
|
return out;
|
|
3563
3768
|
}
|
|
3564
|
-
|
|
3565
|
-
|
|
3566
|
-
|
|
3567
|
-
|
|
3568
|
-
|
|
3569
|
-
|
|
3570
|
-
} from "@teamix-evo/registry";
|
|
3571
|
-
|
|
3572
|
-
// src/core/tokens-update.ts
|
|
3573
|
-
import * as path21 from "path";
|
|
3574
|
-
import * as fs16 from "fs/promises";
|
|
3575
|
-
import {
|
|
3576
|
-
loadTokensPackageManifest as loadTokensPackageManifest2,
|
|
3577
|
-
getVariantEntry as getVariantEntry2
|
|
3578
|
-
} from "@teamix-evo/registry";
|
|
3579
|
-
|
|
3580
|
-
// src/core/upgrade-hints.ts
|
|
3581
|
-
import * as path20 from "path";
|
|
3582
|
-
var TEAMIX_DIR3 = ".teamix-evo";
|
|
3583
|
-
var HINTS_DIR = ".upgrade-hints";
|
|
3584
|
-
function isoToFsSafe2(iso) {
|
|
3585
|
-
return iso.replace(/[:.]/g, "-");
|
|
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;
|
|
3586
3775
|
}
|
|
3587
|
-
|
|
3588
|
-
|
|
3589
|
-
|
|
3590
|
-
const fsTs = isoToFsSafe2(isoTs);
|
|
3591
|
-
const filename = `tokens-${fsTs}.json`;
|
|
3592
|
-
const target = path20.join(
|
|
3593
|
-
options.projectRoot,
|
|
3594
|
-
TEAMIX_DIR3,
|
|
3595
|
-
HINTS_DIR,
|
|
3596
|
-
filename
|
|
3597
|
-
);
|
|
3598
|
-
const payload = {
|
|
3599
|
-
schemaVersion: 1,
|
|
3600
|
-
ts: isoTs,
|
|
3601
|
-
package: "tokens",
|
|
3602
|
-
trigger: options.trigger,
|
|
3603
|
-
fromVariant: options.fromVariant,
|
|
3604
|
-
toVariant: options.toVariant,
|
|
3605
|
-
fromVersion: options.fromVersion,
|
|
3606
|
-
toVersion: options.toVersion,
|
|
3607
|
-
renames: options.renames
|
|
3608
|
-
};
|
|
3609
|
-
await writeFileSafe(target, JSON.stringify(payload, null, 2) + "\n");
|
|
3610
|
-
return {
|
|
3611
|
-
path: target,
|
|
3612
|
-
ts: fsTs,
|
|
3613
|
-
renameCount: options.renames.length
|
|
3614
|
-
};
|
|
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() ?? "";
|
|
3615
3779
|
}
|
|
3616
|
-
|
|
3617
|
-
|
|
3618
|
-
|
|
3619
|
-
|
|
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);
|
|
3620
3786
|
}
|
|
3621
|
-
function
|
|
3622
|
-
const
|
|
3623
|
-
const
|
|
3624
|
-
|
|
3625
|
-
const
|
|
3626
|
-
|
|
3627
|
-
|
|
3628
|
-
const bi = bParts[i] ?? 0;
|
|
3629
|
-
if (ai !== bi) return ai - bi;
|
|
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` };
|
|
3630
3794
|
}
|
|
3631
|
-
|
|
3632
|
-
if (
|
|
3633
|
-
|
|
3634
|
-
|
|
3635
|
-
|
|
3636
|
-
|
|
3637
|
-
|
|
3638
|
-
|
|
3639
|
-
|
|
3640
|
-
|
|
3641
|
-
|
|
3642
|
-
|
|
3643
|
-
|
|
3644
|
-
|
|
3645
|
-
|
|
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) {
|
|
3646
3823
|
throw new Error(
|
|
3647
|
-
`
|
|
3824
|
+
`Unknown ${category} component id(s): ${unknown.map((s) => `"${s}"`).join(
|
|
3825
|
+
", "
|
|
3826
|
+
)}. Hint: \`teamix-evo ${category} list\` shows available ids.`
|
|
3648
3827
|
);
|
|
3649
3828
|
}
|
|
3650
|
-
updated = replaceManagedRegion3(updated, id, body);
|
|
3651
3829
|
}
|
|
3652
|
-
|
|
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
|
+
};
|
|
3653
3852
|
}
|
|
3654
|
-
|
|
3655
|
-
|
|
3656
|
-
|
|
3657
|
-
|
|
3658
|
-
|
|
3659
|
-
|
|
3660
|
-
|
|
3661
|
-
|
|
3662
|
-
|
|
3663
|
-
|
|
3664
|
-
|
|
3665
|
-
|
|
3666
|
-
|
|
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
|
+
});
|
|
3667
3868
|
}
|
|
3668
|
-
const
|
|
3669
|
-
const
|
|
3670
|
-
const
|
|
3671
|
-
const
|
|
3672
|
-
const
|
|
3673
|
-
|
|
3674
|
-
|
|
3675
|
-
|
|
3676
|
-
|
|
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);
|
|
3677
3880
|
}
|
|
3678
|
-
const
|
|
3679
|
-
|
|
3680
|
-
|
|
3681
|
-
|
|
3682
|
-
path21.join(packageRoot, fileRel)
|
|
3683
|
-
);
|
|
3881
|
+
for (const e of uiManifest.entries) {
|
|
3882
|
+
if (entryPackageRoot.has(e.id)) continue;
|
|
3883
|
+
entryPackageRoot.set(e.id, uiRoot);
|
|
3884
|
+
merged.push(e);
|
|
3684
3885
|
}
|
|
3685
|
-
const
|
|
3886
|
+
const synthetic = {
|
|
3686
3887
|
schemaVersion: 1,
|
|
3687
|
-
|
|
3888
|
+
package: "ui",
|
|
3889
|
+
version: variantManifest.version,
|
|
3890
|
+
engines: variantManifest.engines,
|
|
3891
|
+
entries: merged
|
|
3688
3892
|
};
|
|
3689
|
-
|
|
3690
|
-
|
|
3691
|
-
|
|
3692
|
-
|
|
3693
|
-
|
|
3694
|
-
|
|
3695
|
-
|
|
3696
|
-
|
|
3697
|
-
|
|
3698
|
-
|
|
3699
|
-
|
|
3700
|
-
|
|
3701
|
-
|
|
3702
|
-
|
|
3703
|
-
|
|
3704
|
-
|
|
3705
|
-
|
|
3706
|
-
|
|
3707
|
-
|
|
3708
|
-
|
|
3709
|
-
|
|
3710
|
-
|
|
3711
|
-
|
|
3712
|
-
|
|
3713
|
-
|
|
3714
|
-
|
|
3715
|
-
|
|
3716
|
-
|
|
3717
|
-
|
|
3718
|
-
|
|
3719
|
-
|
|
3720
|
-
|
|
3721
|
-
|
|
3722
|
-
|
|
3723
|
-
|
|
3724
|
-
|
|
3725
|
-
|
|
3726
|
-
|
|
3727
|
-
|
|
3728
|
-
|
|
3729
|
-
|
|
3730
|
-
|
|
3731
|
-
|
|
3732
|
-
|
|
3733
|
-
|
|
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;
|
|
3734
3954
|
}
|
|
3735
|
-
|
|
3736
|
-
|
|
3737
|
-
|
|
3738
|
-
|
|
3739
|
-
|
|
3740
|
-
frozenDrift.push({
|
|
3741
|
-
target: resource.target,
|
|
3742
|
-
reason: "upstream-changed"
|
|
3743
|
-
});
|
|
3744
|
-
}
|
|
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;
|
|
3745
3960
|
}
|
|
3746
|
-
|
|
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
|
+
);
|
|
3747
3968
|
}
|
|
3748
|
-
|
|
3749
|
-
|
|
3750
|
-
|
|
3751
|
-
|
|
3752
|
-
|
|
3753
|
-
|
|
3754
|
-
await
|
|
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");
|
|
3755
3983
|
}
|
|
3756
|
-
return {
|
|
3757
|
-
status: "up-to-date",
|
|
3758
|
-
packageName,
|
|
3759
|
-
variant: currentVariant,
|
|
3760
|
-
version: currentVersion,
|
|
3761
|
-
frozenDrift
|
|
3762
|
-
};
|
|
3763
3984
|
}
|
|
3764
|
-
|
|
3765
|
-
|
|
3766
|
-
|
|
3767
|
-
|
|
3768
|
-
|
|
3769
|
-
|
|
3770
|
-
|
|
3771
|
-
|
|
3772
|
-
|
|
3773
|
-
|
|
3774
|
-
|
|
3775
|
-
|
|
3776
|
-
|
|
3777
|
-
|
|
3778
|
-
|
|
3779
|
-
);
|
|
3780
|
-
|
|
3781
|
-
|
|
3782
|
-
|
|
3783
|
-
|
|
3784
|
-
|
|
3785
|
-
|
|
3786
|
-
|
|
3787
|
-
|
|
3788
|
-
|
|
3789
|
-
|
|
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
|
+
}
|
|
3790
4103
|
}
|
|
3791
|
-
const
|
|
3792
|
-
|
|
3793
|
-
|
|
3794
|
-
|
|
3795
|
-
|
|
3796
|
-
|
|
3797
|
-
|
|
3798
|
-
|
|
3799
|
-
|
|
3800
|
-
|
|
3801
|
-
|
|
3802
|
-
|
|
3803
|
-
|
|
3804
|
-
|
|
3805
|
-
|
|
3806
|
-
|
|
3807
|
-
|
|
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
|
+
}
|
|
3808
4134
|
}
|
|
3809
4135
|
return {
|
|
3810
|
-
|
|
3811
|
-
|
|
3812
|
-
|
|
3813
|
-
from: currentVersion,
|
|
3814
|
-
to: variantEntry.version,
|
|
3815
|
-
rewritten,
|
|
3816
|
-
managedReplaced,
|
|
3817
|
-
preserved,
|
|
3818
|
-
frozenDrift,
|
|
3819
|
-
renames,
|
|
3820
|
-
...hintPath ? { hintPath } : {}
|
|
4136
|
+
filesScanned,
|
|
4137
|
+
entries,
|
|
4138
|
+
affectedFiles: affectedFileSet.size
|
|
3821
4139
|
};
|
|
3822
4140
|
}
|
|
3823
|
-
function lookupUpstreamBasename(consumerBasename) {
|
|
3824
|
-
for (const [upstream, consumer] of Object.entries(
|
|
3825
|
-
CONSUMER_BASENAME_BY_UPSTREAM
|
|
3826
|
-
)) {
|
|
3827
|
-
if (consumer === consumerBasename) return upstream;
|
|
3828
|
-
}
|
|
3829
|
-
return consumerBasename;
|
|
3830
|
-
}
|
|
3831
4141
|
|
|
3832
|
-
// src/core/
|
|
3833
|
-
|
|
3834
|
-
|
|
3835
|
-
|
|
3836
|
-
|
|
3837
|
-
"
|
|
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"
|
|
3838
4151
|
};
|
|
3839
|
-
|
|
3840
|
-
|
|
3841
|
-
"
|
|
3842
|
-
|
|
3843
|
-
|
|
3844
|
-
|
|
3845
|
-
|
|
3846
|
-
|
|
3847
|
-
|
|
3848
|
-
|
|
3849
|
-
|
|
3850
|
-
|
|
3851
|
-
|
|
3852
|
-
|
|
3853
|
-
|
|
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"
|
|
3854
4205
|
);
|
|
3855
|
-
|
|
3856
|
-
|
|
3857
|
-
const
|
|
3858
|
-
|
|
3859
|
-
|
|
3860
|
-
|
|
3861
|
-
|
|
3862
|
-
|
|
3863
|
-
|
|
3864
|
-
|
|
3865
|
-
|
|
3866
|
-
|
|
3867
|
-
|
|
3868
|
-
|
|
3869
|
-
|
|
3870
|
-
|
|
3871
|
-
|
|
3872
|
-
|
|
3873
|
-
|
|
3874
|
-
|
|
3875
|
-
|
|
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"
|
|
3876
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 };
|
|
3877
4244
|
}
|
|
3878
|
-
function
|
|
3879
|
-
const
|
|
3880
|
-
|
|
3881
|
-
|
|
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;
|
|
3882
4276
|
}
|
|
3883
|
-
function
|
|
3884
|
-
|
|
3885
|
-
|
|
3886
|
-
|
|
3887
|
-
|
|
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}`);
|
|
3888
4289
|
}
|
|
3889
|
-
return
|
|
4290
|
+
return removed.reverse();
|
|
3890
4291
|
}
|
|
3891
|
-
|
|
3892
|
-
|
|
3893
|
-
|
|
3894
|
-
|
|
3895
|
-
|
|
3896
|
-
|
|
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);
|
|
3897
4300
|
}
|
|
4301
|
+
return rel2.split(path27.sep).join("/");
|
|
3898
4302
|
}
|
|
3899
|
-
async function
|
|
3900
|
-
const
|
|
3901
|
-
const
|
|
3902
|
-
|
|
3903
|
-
|
|
3904
|
-
|
|
3905
|
-
|
|
3906
|
-
|
|
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
|
+
}
|
|
3907
4326
|
}
|
|
3908
|
-
return
|
|
4327
|
+
return out;
|
|
3909
4328
|
}
|
|
3910
|
-
function
|
|
3911
|
-
const
|
|
3912
|
-
|
|
3913
|
-
|
|
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);
|
|
3914
4339
|
}
|
|
3915
|
-
|
|
3916
|
-
return hasComponentsJson ? "shadcn-native" : "custom-only";
|
|
4340
|
+
return out;
|
|
3917
4341
|
}
|
|
3918
4342
|
|
|
3919
|
-
// src/core/
|
|
3920
|
-
import * as
|
|
3921
|
-
import
|
|
3922
|
-
|
|
3923
|
-
|
|
3924
|
-
|
|
3925
|
-
|
|
3926
|
-
|
|
3927
|
-
|
|
3928
|
-
|
|
3929
|
-
|
|
3930
|
-
|
|
3931
|
-
|
|
3932
|
-
|
|
3933
|
-
"
|
|
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"]
|
|
3934
4380
|
};
|
|
3935
|
-
function
|
|
3936
|
-
return
|
|
4381
|
+
function pickIde(ides) {
|
|
4382
|
+
return ides[0] ?? "qoder";
|
|
3937
4383
|
}
|
|
3938
|
-
|
|
3939
|
-
|
|
3940
|
-
|
|
3941
|
-
|
|
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];
|
|
3942
4390
|
}
|
|
3943
|
-
|
|
3944
|
-
|
|
3945
|
-
|
|
3946
|
-
const isoTs = options.isoTs ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
3947
|
-
const fsTs = isoToFsSafe3(isoTs);
|
|
3948
|
-
const stagingDir = path23.join(
|
|
3949
|
-
options.projectRoot,
|
|
3950
|
-
TEAMIX_DIR4,
|
|
3951
|
-
STAGING_DIR,
|
|
3952
|
-
`${category}-${fsTs}`
|
|
3953
|
-
);
|
|
3954
|
-
const entryMap = new Map(
|
|
3955
|
-
options.manifest.entries.map((e) => [e.id, e])
|
|
3956
|
-
);
|
|
3957
|
-
const resByEntryId = collectResourcesByEntry(installedPkg.resources);
|
|
3958
|
-
const onlyIds = options.onlyIds && options.onlyIds.length > 0 ? new Set(options.onlyIds) : null;
|
|
3959
|
-
const entries = [];
|
|
3960
|
-
for (const id of lineageReport.registeredIds) {
|
|
3961
|
-
if (onlyIds && !onlyIds.has(id)) continue;
|
|
3962
|
-
const built = await processRegistered({
|
|
3963
|
-
id,
|
|
3964
|
-
entry: entryMap.get(id),
|
|
3965
|
-
resource: resByEntryId.get(id),
|
|
3966
|
-
packageRoot: options.packageRoot,
|
|
3967
|
-
entryPackageRoot: options.entryPackageRoot,
|
|
3968
|
-
aliases: options.aliases,
|
|
3969
|
-
stagingDir,
|
|
3970
|
-
projectRoot: options.projectRoot,
|
|
3971
|
-
category,
|
|
3972
|
-
sourceVersion: options.manifest.version
|
|
3973
|
-
});
|
|
3974
|
-
if (built) entries.push(built);
|
|
4391
|
+
if (options.answers.uiSelection === "all") {
|
|
4392
|
+
const { manifest } = await loadUiData("@teamix-evo/ui");
|
|
4393
|
+
return manifest.entries.map((e) => e.id);
|
|
3975
4394
|
}
|
|
3976
|
-
|
|
3977
|
-
|
|
3978
|
-
|
|
3979
|
-
|
|
3980
|
-
|
|
3981
|
-
|
|
3982
|
-
|
|
3983
|
-
|
|
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
|
|
3984
4425
|
});
|
|
3985
|
-
|
|
4426
|
+
remaining--;
|
|
3986
4427
|
}
|
|
3987
|
-
|
|
3988
|
-
const byRisk = aggregateByRisk(entries);
|
|
3989
|
-
const manifestOut = {
|
|
3990
|
-
schemaVersion: 1,
|
|
3991
|
-
ts: isoTs,
|
|
3992
|
-
package: category,
|
|
3993
|
-
trigger: options.trigger,
|
|
3994
|
-
variant: lineageReport.installedVariant ?? "_flat",
|
|
3995
|
-
fromVersion: lineageReport.installedVersion ?? "",
|
|
3996
|
-
toVersion: options.manifest.version,
|
|
3997
|
-
lineage: lineageReport.lineage,
|
|
3998
|
-
summary: { total: entries.length, byRisk },
|
|
3999
|
-
entries
|
|
4000
|
-
};
|
|
4001
|
-
await ensureDir(stagingDir);
|
|
4002
|
-
await writeFileSafe(
|
|
4003
|
-
path23.join(stagingDir, "meta.json"),
|
|
4004
|
-
JSON.stringify(manifestOut, null, 2) + "\n"
|
|
4005
|
-
);
|
|
4006
|
-
return { stagingDir, manifest: manifestOut };
|
|
4428
|
+
return out;
|
|
4007
4429
|
}
|
|
4008
|
-
|
|
4009
|
-
|
|
4010
|
-
|
|
4011
|
-
|
|
4012
|
-
|
|
4013
|
-
|
|
4014
|
-
|
|
4015
|
-
|
|
4016
|
-
|
|
4017
|
-
projectRoot,
|
|
4018
|
-
stagingDir,
|
|
4019
|
-
currentSource: "",
|
|
4020
|
-
hint: "installed file missing on disk"
|
|
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"
|
|
4021
4439
|
});
|
|
4022
4440
|
}
|
|
4023
|
-
if (
|
|
4024
|
-
|
|
4025
|
-
|
|
4026
|
-
|
|
4027
|
-
|
|
4028
|
-
|
|
4029
|
-
stagingDir,
|
|
4030
|
-
currentSource,
|
|
4031
|
-
hint: "entry removed in upstream package"
|
|
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"
|
|
4032
4447
|
});
|
|
4033
4448
|
}
|
|
4034
|
-
|
|
4035
|
-
|
|
4036
|
-
|
|
4037
|
-
|
|
4038
|
-
|
|
4039
|
-
|
|
4040
|
-
|
|
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
|
+
});
|
|
4041
4456
|
}
|
|
4042
|
-
|
|
4043
|
-
const incomingHash = computeHash(incomingTransformed);
|
|
4044
|
-
const currentExt = path23.extname(resource.target) || ".tsx";
|
|
4045
|
-
const incomingExt = path23.extname(file.targetName) || currentExt;
|
|
4046
|
-
const currentRel = `${id}/current${currentExt}`;
|
|
4047
|
-
const incomingRel = `${id}/incoming${incomingExt}`;
|
|
4048
|
-
await writeFileSafe(path23.join(stagingDir, currentRel), currentSource);
|
|
4049
|
-
await writeFileSafe(path23.join(stagingDir, incomingRel), incomingTransformed);
|
|
4050
|
-
const diff = classifyRisk({
|
|
4051
|
-
currentHash: resource.hash,
|
|
4052
|
-
incomingHash,
|
|
4053
|
-
currentSource,
|
|
4054
|
-
incomingSource: incomingTransformed,
|
|
4055
|
-
multiFile: entry.files.length > 1
|
|
4056
|
-
});
|
|
4057
|
-
const promotion = derivePromotion({
|
|
4058
|
-
currentSource,
|
|
4059
|
-
incomingSource: incomingTransformed,
|
|
4060
|
-
targetName: entry.files[0]?.targetName ?? `${id}.tsx`
|
|
4061
|
-
});
|
|
4062
|
-
return {
|
|
4063
|
-
id,
|
|
4064
|
-
category,
|
|
4065
|
-
current: {
|
|
4066
|
-
target: path23.relative(projectRoot, resource.target),
|
|
4067
|
-
hash: resource.hash,
|
|
4068
|
-
sourceLineage: "teamix-evo"
|
|
4069
|
-
},
|
|
4070
|
-
incoming: {
|
|
4071
|
-
sourceVersion: args.sourceVersion,
|
|
4072
|
-
hash: incomingHash,
|
|
4073
|
-
relPath: incomingRel
|
|
4074
|
-
},
|
|
4075
|
-
diff,
|
|
4076
|
-
promotion
|
|
4077
|
-
};
|
|
4457
|
+
return out;
|
|
4078
4458
|
}
|
|
4079
|
-
async function
|
|
4080
|
-
const {
|
|
4081
|
-
const
|
|
4082
|
-
const
|
|
4083
|
-
const
|
|
4084
|
-
|
|
4085
|
-
const
|
|
4086
|
-
|
|
4087
|
-
|
|
4088
|
-
|
|
4089
|
-
|
|
4090
|
-
|
|
4091
|
-
|
|
4092
|
-
|
|
4093
|
-
current: {
|
|
4094
|
-
target: path23.relative(projectRoot, target),
|
|
4095
|
-
hash: computeHash(raw),
|
|
4096
|
-
sourceLineage: "custom"
|
|
4097
|
-
},
|
|
4098
|
-
diff: {
|
|
4099
|
-
riskLevel: "foreign",
|
|
4100
|
-
hints: [
|
|
4101
|
-
"component is on disk but not registered in .teamix-evo/manifest.json",
|
|
4102
|
-
"AI should propose: (a) ignore, (b) re-register via teamix-evo ui add, or (c) remove"
|
|
4103
|
-
],
|
|
4104
|
-
filesChangedCount: 0
|
|
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);
|
|
4105
4473
|
}
|
|
4474
|
+
}
|
|
4475
|
+
let aborted = false;
|
|
4476
|
+
const firstFailure = {
|
|
4477
|
+
value: null
|
|
4106
4478
|
};
|
|
4107
|
-
|
|
4108
|
-
|
|
4109
|
-
|
|
4110
|
-
|
|
4111
|
-
|
|
4112
|
-
|
|
4113
|
-
|
|
4114
|
-
)
|
|
4115
|
-
|
|
4116
|
-
|
|
4117
|
-
|
|
4118
|
-
|
|
4119
|
-
|
|
4120
|
-
|
|
4121
|
-
|
|
4122
|
-
}
|
|
4123
|
-
|
|
4124
|
-
|
|
4125
|
-
|
|
4126
|
-
|
|
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);
|
|
4127
4628
|
}
|
|
4128
|
-
};
|
|
4129
|
-
}
|
|
4130
|
-
function classifyRisk(args) {
|
|
4131
|
-
if (args.currentHash === args.incomingHash) {
|
|
4132
|
-
return { riskLevel: "unchanged", hints: [], filesChangedCount: 0 };
|
|
4133
4629
|
}
|
|
4134
|
-
|
|
4135
|
-
const
|
|
4136
|
-
const
|
|
4137
|
-
const
|
|
4138
|
-
|
|
4139
|
-
|
|
4140
|
-
|
|
4141
|
-
|
|
4142
|
-
|
|
4143
|
-
|
|
4144
|
-
|
|
4145
|
-
|
|
4146
|
-
|
|
4147
|
-
|
|
4148
|
-
|
|
4149
|
-
|
|
4150
|
-
|
|
4151
|
-
|
|
4152
|
-
|
|
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
|
+
]);
|
|
4655
|
+
record({
|
|
4656
|
+
name: "ui-add",
|
|
4657
|
+
status: "planned",
|
|
4658
|
+
detail: `runUiAdd(${entries.length} entries: ${entries.slice(0, 3).join(", ")}${entries.length > 3 ? "\u2026" : ""})`
|
|
4659
|
+
});
|
|
4153
4660
|
} else {
|
|
4154
|
-
|
|
4155
|
-
|
|
4156
|
-
|
|
4157
|
-
|
|
4158
|
-
|
|
4159
|
-
|
|
4160
|
-
|
|
4161
|
-
|
|
4162
|
-
|
|
4163
|
-
|
|
4164
|
-
|
|
4165
|
-
|
|
4166
|
-
|
|
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
|
+
}
|
|
4167
4876
|
}
|
|
4168
|
-
|
|
4169
|
-
|
|
4170
|
-
|
|
4171
|
-
|
|
4172
|
-
|
|
4173
|
-
|
|
4174
|
-
|
|
4175
|
-
|
|
4176
|
-
|
|
4877
|
+
recordPending("components-json");
|
|
4878
|
+
recordPending("shadcn-source");
|
|
4879
|
+
if (!answers.withLint) {
|
|
4880
|
+
record({
|
|
4881
|
+
name: "lint",
|
|
4882
|
+
status: "skip",
|
|
4883
|
+
detail: "withLint = false"
|
|
4884
|
+
});
|
|
4885
|
+
} else if (dryRun) {
|
|
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);
|
|
4177
4932
|
}
|
|
4178
4933
|
}
|
|
4179
|
-
|
|
4180
|
-
|
|
4181
|
-
|
|
4182
|
-
const
|
|
4183
|
-
|
|
4184
|
-
|
|
4185
|
-
|
|
4186
|
-
|
|
4187
|
-
|
|
4188
|
-
|
|
4189
|
-
|
|
4190
|
-
|
|
4191
|
-
|
|
4192
|
-
|
|
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
|
+
}
|
|
4193
5002
|
}
|
|
4194
5003
|
}
|
|
4195
|
-
|
|
4196
|
-
|
|
4197
|
-
|
|
4198
|
-
|
|
4199
|
-
|
|
4200
|
-
|
|
4201
|
-
|
|
4202
|
-
|
|
4203
|
-
|
|
4204
|
-
|
|
4205
|
-
|
|
4206
|
-
|
|
4207
|
-
|
|
4208
|
-
|
|
4209
|
-
|
|
4210
|
-
|
|
4211
|
-
|
|
4212
|
-
break;
|
|
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
|
+
});
|
|
4213
5021
|
}
|
|
4214
5022
|
}
|
|
5023
|
+
} catch {
|
|
4215
5024
|
}
|
|
4216
5025
|
}
|
|
4217
|
-
|
|
4218
|
-
|
|
4219
|
-
|
|
4220
|
-
|
|
4221
|
-
|
|
4222
|
-
|
|
4223
|
-
|
|
4224
|
-
|
|
4225
|
-
|
|
4226
|
-
|
|
4227
|
-
|
|
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
|
+
};
|
|
4228
5048
|
}
|
|
4229
|
-
|
|
4230
|
-
|
|
4231
|
-
|
|
4232
|
-
|
|
4233
|
-
|
|
4234
|
-
|
|
4235
|
-
|
|
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
|
+
}
|
|
4236
5067
|
}
|
|
4237
5068
|
return out;
|
|
4238
5069
|
}
|
|
4239
|
-
|
|
4240
|
-
|
|
4241
|
-
|
|
4242
|
-
|
|
4243
|
-
|
|
4244
|
-
|
|
4245
|
-
|
|
4246
|
-
|
|
4247
|
-
|
|
4248
|
-
|
|
4249
|
-
|
|
4250
|
-
|
|
4251
|
-
|
|
4252
|
-
|
|
4253
|
-
|
|
4254
|
-
|
|
4255
|
-
|
|
4256
|
-
|
|
4257
|
-
|
|
4258
|
-
|
|
4259
|
-
|
|
4260
|
-
return "
|
|
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, "-");
|
|
4261
5092
|
}
|
|
4262
|
-
function
|
|
4263
|
-
|
|
4264
|
-
const
|
|
4265
|
-
const
|
|
4266
|
-
const
|
|
4267
|
-
const
|
|
4268
|
-
|
|
4269
|
-
|
|
4270
|
-
|
|
4271
|
-
|
|
4272
|
-
|
|
4273
|
-
|
|
4274
|
-
|
|
4275
|
-
|
|
4276
|
-
|
|
4277
|
-
|
|
4278
|
-
|
|
4279
|
-
|
|
4280
|
-
|
|
4281
|
-
|
|
4282
|
-
|
|
4283
|
-
|
|
4284
|
-
|
|
4285
|
-
const curImports = extractImportSources(current);
|
|
4286
|
-
const newImports = extractImportSources(incoming);
|
|
4287
|
-
const hasExtraImports = [...curImports].some((src) => !newImports.has(src));
|
|
4288
|
-
const tagSet = /* @__PURE__ */ new Set();
|
|
4289
|
-
for (const m of current.matchAll(/<([A-Z]\w+)[\s/>]/g)) {
|
|
4290
|
-
if (m[1]) tagSet.add(m[1]);
|
|
4291
|
-
}
|
|
4292
|
-
const atomicChildren = [...tagSet];
|
|
4293
|
-
const isComposition = atomicChildren.length > 2;
|
|
4294
|
-
const signatureChanged = extractDefaultParamList(current) !== extractDefaultParamList(incoming);
|
|
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");
|
|
4295
5116
|
return {
|
|
4296
|
-
|
|
4297
|
-
|
|
4298
|
-
|
|
4299
|
-
cvaDelta: { addedVariants: cvaAdded, modifiedVariants: cvaModified },
|
|
4300
|
-
structureDelta: { isComposition, atomicChildren }
|
|
5117
|
+
path: target,
|
|
5118
|
+
ts: fsTs,
|
|
5119
|
+
renameCount: options.renames.length
|
|
4301
5120
|
};
|
|
4302
5121
|
}
|
|
4303
|
-
function
|
|
4304
|
-
|
|
4305
|
-
|
|
4306
|
-
|
|
4307
|
-
|
|
4308
|
-
|
|
4309
|
-
|
|
4310
|
-
|
|
4311
|
-
const
|
|
4312
|
-
const
|
|
4313
|
-
|
|
4314
|
-
|
|
4315
|
-
|
|
4316
|
-
);
|
|
4317
|
-
return { recommendedModes: ["Coexist"], confidence: 0.85, reasons };
|
|
4318
|
-
}
|
|
4319
|
-
if (apiNoChange && logicMinimal && (fv.styleDelta.classNameDiff || fv.styleDelta.tokenUsageDiff) && fv.cvaDelta.addedVariants.length === 0 && fv.cvaDelta.modifiedVariants.length === 0) {
|
|
4320
|
-
reasons.push(
|
|
4321
|
-
"only style / token differences \u2014 push to tokens.overrides.css"
|
|
4322
|
-
);
|
|
4323
|
-
return { recommendedModes: ["TokenOnly"], confidence: 0.8, reasons };
|
|
4324
|
-
}
|
|
4325
|
-
const modes = [];
|
|
4326
|
-
let score = 0;
|
|
4327
|
-
if (fv.cvaDelta.addedVariants.length > 0 || fv.cvaDelta.modifiedVariants.length > 0) {
|
|
4328
|
-
modes.push("Variant");
|
|
4329
|
-
reasons.push(
|
|
4330
|
-
`cva variants delta: +${fv.cvaDelta.addedVariants.length} ~${fv.cvaDelta.modifiedVariants.length}`
|
|
4331
|
-
);
|
|
4332
|
-
score = Math.max(score, 0.7);
|
|
4333
|
-
}
|
|
4334
|
-
if (fv.logicDelta.hasState || fv.logicDelta.hasEffect || fv.logicDelta.hasExtraImports || fv.apiDelta.added.length > 0) {
|
|
4335
|
-
modes.push("Wrapper");
|
|
4336
|
-
reasons.push("user added state / effect / imports / props");
|
|
4337
|
-
score = Math.max(score, 0.75);
|
|
4338
|
-
}
|
|
4339
|
-
if (apiNoChange && logicMinimal && !fv.styleDelta.classNameDiff && !fv.styleDelta.tokenUsageDiff && fv.cvaDelta.addedVariants.length === 0 && fv.cvaDelta.modifiedVariants.length === 0) {
|
|
4340
|
-
modes.push("Preset");
|
|
4341
|
-
reasons.push("no API/logic delta \u2014 Preset captures default-prop tweaks");
|
|
4342
|
-
score = Math.max(score, 0.6);
|
|
4343
|
-
}
|
|
4344
|
-
if (fv.structureDelta.isComposition) {
|
|
4345
|
-
modes.push("Compose");
|
|
4346
|
-
reasons.push(
|
|
4347
|
-
`composition of ${fv.structureDelta.atomicChildren.length} atomic children`
|
|
4348
|
-
);
|
|
4349
|
-
score = Math.max(score, 0.65);
|
|
4350
|
-
}
|
|
4351
|
-
if (modes.length === 0) {
|
|
4352
|
-
reasons.push("no axis crossed the 0.6 threshold");
|
|
4353
|
-
return { recommendedModes: ["ManualReview"], confidence: 0.4, reasons };
|
|
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;
|
|
4354
5136
|
}
|
|
4355
|
-
|
|
5137
|
+
if (aRest === "" && bRest !== "") return 1;
|
|
5138
|
+
if (aRest !== "" && bRest === "") return -1;
|
|
5139
|
+
return aRest.localeCompare(bRest, void 0, { numeric: true });
|
|
4356
5140
|
}
|
|
4357
|
-
|
|
4358
|
-
|
|
4359
|
-
|
|
4360
|
-
|
|
4361
|
-
|
|
4362
|
-
|
|
4363
|
-
|
|
4364
|
-
|
|
4365
|
-
|
|
4366
|
-
|
|
4367
|
-
|
|
4368
|
-
|
|
4369
|
-
|
|
4370
|
-
|
|
4371
|
-
if (depth === 0) return block.slice(open + 1, i);
|
|
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
|
+
);
|
|
4372
5155
|
}
|
|
5156
|
+
updated = replaceManagedRegion3(updated, id, body);
|
|
4373
5157
|
}
|
|
4374
|
-
return
|
|
5158
|
+
return updated;
|
|
4375
5159
|
}
|
|
4376
|
-
|
|
4377
|
-
|
|
4378
|
-
|
|
4379
|
-
|
|
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" };
|
|
4380
5173
|
}
|
|
4381
|
-
|
|
4382
|
-
|
|
4383
|
-
|
|
4384
|
-
|
|
4385
|
-
|
|
4386
|
-
|
|
4387
|
-
|
|
4388
|
-
|
|
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;
|
|
4389
5222
|
}
|
|
4390
|
-
|
|
4391
|
-
|
|
4392
|
-
|
|
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;
|
|
4393
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);
|
|
4394
5253
|
}
|
|
4395
|
-
|
|
4396
|
-
|
|
4397
|
-
|
|
4398
|
-
|
|
4399
|
-
|
|
4400
|
-
|
|
4401
|
-
|
|
4402
|
-
|
|
4403
|
-
|
|
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
|
+
};
|
|
4404
5269
|
}
|
|
4405
|
-
|
|
4406
|
-
|
|
4407
|
-
|
|
4408
|
-
|
|
4409
|
-
|
|
4410
|
-
|
|
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);
|
|
4411
5296
|
}
|
|
4412
|
-
|
|
4413
|
-
|
|
4414
|
-
|
|
4415
|
-
|
|
4416
|
-
|
|
4417
|
-
|
|
4418
|
-
|
|
4419
|
-
|
|
4420
|
-
var nodeRequire = createRequire5(import.meta.url);
|
|
4421
|
-
function resolvePackageRoot4(packageName) {
|
|
4422
|
-
const pkgJsonPath = nodeRequire.resolve(`${packageName}/package.json`);
|
|
4423
|
-
return path24.dirname(pkgJsonPath);
|
|
4424
|
-
}
|
|
4425
|
-
async function buildStaging(args) {
|
|
4426
|
-
const { category, projectRoot, aliases, lineageReport, trigger, onlyIds } = args;
|
|
4427
|
-
if (category === "ui") {
|
|
4428
|
-
const root = args.uiPackageRoot ?? resolvePackageRoot4("@teamix-evo/ui");
|
|
4429
|
-
const manifest = await loadUiPackageManifest3(root);
|
|
4430
|
-
return buildUiUpgradeStaging({
|
|
5297
|
+
const renames = selectApplicableRenames(
|
|
5298
|
+
variantEntry.renames ?? [],
|
|
5299
|
+
currentVersion,
|
|
5300
|
+
variantEntry.version
|
|
5301
|
+
);
|
|
5302
|
+
let hintPath;
|
|
5303
|
+
if (renames.length > 0) {
|
|
5304
|
+
const hint = await writeTokensUpgradeHint({
|
|
4431
5305
|
projectRoot,
|
|
4432
|
-
|
|
4433
|
-
|
|
4434
|
-
|
|
4435
|
-
|
|
4436
|
-
|
|
4437
|
-
|
|
4438
|
-
onlyIds
|
|
5306
|
+
trigger: "update",
|
|
5307
|
+
fromVariant: currentVariant,
|
|
5308
|
+
toVariant: currentVariant,
|
|
5309
|
+
fromVersion: currentVersion,
|
|
5310
|
+
toVersion: variantEntry.version,
|
|
5311
|
+
renames
|
|
4439
5312
|
});
|
|
5313
|
+
if (hint) hintPath = hint.path;
|
|
4440
5314
|
}
|
|
4441
|
-
|
|
4442
|
-
|
|
4443
|
-
|
|
4444
|
-
|
|
4445
|
-
|
|
4446
|
-
|
|
4447
|
-
|
|
4448
|
-
|
|
4449
|
-
|
|
4450
|
-
|
|
4451
|
-
|
|
4452
|
-
|
|
4453
|
-
for (const e of uiManifest.entries) {
|
|
4454
|
-
if (entryPackageRoot.has(e.id)) continue;
|
|
4455
|
-
entryPackageRoot.set(e.id, uiRoot);
|
|
4456
|
-
merged.push(e);
|
|
4457
|
-
}
|
|
4458
|
-
const synthetic = {
|
|
4459
|
-
schemaVersion: 1,
|
|
4460
|
-
package: "ui",
|
|
4461
|
-
version: variantManifest.version,
|
|
4462
|
-
engines: variantManifest.engines,
|
|
4463
|
-
entries: merged
|
|
5315
|
+
return {
|
|
5316
|
+
status: "updated",
|
|
5317
|
+
packageName,
|
|
5318
|
+
variant: currentVariant,
|
|
5319
|
+
from: currentVersion,
|
|
5320
|
+
to: variantEntry.version,
|
|
5321
|
+
rewritten,
|
|
5322
|
+
managedReplaced,
|
|
5323
|
+
preserved,
|
|
5324
|
+
frozenDrift,
|
|
5325
|
+
renames,
|
|
5326
|
+
...hintPath ? { hintPath } : {}
|
|
4464
5327
|
};
|
|
4465
|
-
|
|
4466
|
-
|
|
4467
|
-
|
|
4468
|
-
|
|
4469
|
-
|
|
4470
|
-
|
|
4471
|
-
|
|
4472
|
-
|
|
4473
|
-
trigger,
|
|
4474
|
-
onlyIds
|
|
4475
|
-
});
|
|
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;
|
|
4476
5336
|
}
|
|
4477
5337
|
|
|
4478
5338
|
// src/core/project-update.ts
|
|
@@ -4722,7 +5582,7 @@ async function runComponentSourceStep(category, args) {
|
|
|
4722
5582
|
});
|
|
4723
5583
|
return;
|
|
4724
5584
|
}
|
|
4725
|
-
const stagingRel =
|
|
5585
|
+
const stagingRel = path31.relative(projectRoot, built.stagingDir);
|
|
4726
5586
|
const summary = summarizeStagingRisk(built.manifest.summary.byRisk);
|
|
4727
5587
|
record({
|
|
4728
5588
|
name: category,
|
|
@@ -4765,8 +5625,8 @@ async function planTokensUpdate(tokensPackage, config) {
|
|
|
4765
5625
|
}
|
|
4766
5626
|
|
|
4767
5627
|
// src/core/installer.ts
|
|
4768
|
-
import * as
|
|
4769
|
-
import * as
|
|
5628
|
+
import * as path32 from "path";
|
|
5629
|
+
import * as fs23 from "fs/promises";
|
|
4770
5630
|
async function installResources(options) {
|
|
4771
5631
|
const { projectRoot, manifest, data, variantDir, packageRoot } = options;
|
|
4772
5632
|
const installedResources = [];
|
|
@@ -4803,13 +5663,13 @@ async function installSingleResource(resource, projectRoot, data, variantDir, pa
|
|
|
4803
5663
|
variantDir,
|
|
4804
5664
|
packageRoot
|
|
4805
5665
|
);
|
|
4806
|
-
const targetPath =
|
|
5666
|
+
const targetPath = path32.join(projectRoot, resource.target);
|
|
4807
5667
|
let content;
|
|
4808
5668
|
if (resource.template) {
|
|
4809
5669
|
const templateContent = await loadTemplateFile(sourcePath);
|
|
4810
5670
|
content = renderTemplate(templateContent, data);
|
|
4811
5671
|
} else {
|
|
4812
|
-
content = await
|
|
5672
|
+
content = await fs23.readFile(sourcePath, "utf-8");
|
|
4813
5673
|
}
|
|
4814
5674
|
await writeFileSafe(targetPath, content);
|
|
4815
5675
|
const hash = computeHash(content);
|
|
@@ -4827,13 +5687,13 @@ async function installRecursiveResource(resource, projectRoot, data, variantDir,
|
|
|
4827
5687
|
variantDir,
|
|
4828
5688
|
packageRoot
|
|
4829
5689
|
);
|
|
4830
|
-
const targetDir =
|
|
5690
|
+
const targetDir = path32.join(projectRoot, resource.target);
|
|
4831
5691
|
const results = [];
|
|
4832
5692
|
await ensureDir(targetDir);
|
|
4833
5693
|
const entries = await walkDir(sourcePath);
|
|
4834
5694
|
for (const entry of entries) {
|
|
4835
|
-
const relPath =
|
|
4836
|
-
let targetFile =
|
|
5695
|
+
const relPath = path32.relative(sourcePath, entry);
|
|
5696
|
+
let targetFile = path32.join(targetDir, relPath);
|
|
4837
5697
|
if (resource.template && targetFile.endsWith(".hbs")) {
|
|
4838
5698
|
targetFile = targetFile.slice(0, -4);
|
|
4839
5699
|
}
|
|
@@ -4842,11 +5702,11 @@ async function installRecursiveResource(resource, projectRoot, data, variantDir,
|
|
|
4842
5702
|
const templateContent = await loadTemplateFile(entry);
|
|
4843
5703
|
content = renderTemplate(templateContent, data);
|
|
4844
5704
|
} else {
|
|
4845
|
-
content = await
|
|
5705
|
+
content = await fs23.readFile(entry, "utf-8");
|
|
4846
5706
|
}
|
|
4847
5707
|
await writeFileSafe(targetFile, content);
|
|
4848
5708
|
const hash = computeHash(content);
|
|
4849
|
-
const targetRel =
|
|
5709
|
+
const targetRel = path32.relative(projectRoot, targetFile);
|
|
4850
5710
|
results.push({
|
|
4851
5711
|
id: `${resource.id}:${relPath}`,
|
|
4852
5712
|
target: targetRel,
|
|
@@ -4859,25 +5719,25 @@ async function installRecursiveResource(resource, projectRoot, data, variantDir,
|
|
|
4859
5719
|
}
|
|
4860
5720
|
|
|
4861
5721
|
// src/core/registry-client.ts
|
|
4862
|
-
import * as
|
|
4863
|
-
import * as
|
|
5722
|
+
import * as path33 from "path";
|
|
5723
|
+
import * as fs24 from "fs/promises";
|
|
4864
5724
|
import { createRequire as createRequire6 } from "module";
|
|
4865
5725
|
import { loadVariantManifest } from "@teamix-evo/registry";
|
|
4866
5726
|
var require6 = createRequire6(import.meta.url);
|
|
4867
5727
|
function resolvePackageRoot5(packageName) {
|
|
4868
5728
|
const pkgJsonPath = require6.resolve(`${packageName}/package.json`);
|
|
4869
|
-
return
|
|
5729
|
+
return path33.dirname(pkgJsonPath);
|
|
4870
5730
|
}
|
|
4871
5731
|
async function loadVariantData(packageName, variant) {
|
|
4872
5732
|
const packageRoot = resolvePackageRoot5(packageName);
|
|
4873
|
-
const variantDir =
|
|
5733
|
+
const variantDir = path33.join(packageRoot, "library", variant);
|
|
4874
5734
|
logger.debug(`Resolved variant dir: ${variantDir}`);
|
|
4875
5735
|
logger.debug(`Package root: ${packageRoot}`);
|
|
4876
5736
|
const manifest = await loadVariantManifest(variantDir);
|
|
4877
5737
|
let data = {};
|
|
4878
|
-
const dataPath =
|
|
5738
|
+
const dataPath = path33.join(variantDir, "_data.json");
|
|
4879
5739
|
try {
|
|
4880
|
-
const raw = await
|
|
5740
|
+
const raw = await fs24.readFile(dataPath, "utf-8");
|
|
4881
5741
|
data = JSON.parse(raw);
|
|
4882
5742
|
} catch (err) {
|
|
4883
5743
|
if (err.code !== "ENOENT") {
|