@deepstorm/cli 0.8.0 → 0.9.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/dist/cli.js +177 -60
- package/dist/skills/reef-commit/SKILL.md +1 -1
- package/dist/skills/reef-scope/SKILL.md +9 -5
- package/dist/skills/sweep-init/SKILL.md +132 -35
- package/dist/skills/sweep-plan/SKILL.md.tmpl +51 -5
- package/dist/skills/sweep-run/SKILL.md +52 -8
- package/dist/skills/sweep-run/scripts/env-manager.mjs +98 -22
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -1187,13 +1187,13 @@ var require_ast = __commonJS({
|
|
|
1187
1187
|
helperExpression: function helperExpression(node) {
|
|
1188
1188
|
return node.type === "SubExpression" || (node.type === "MustacheStatement" || node.type === "BlockStatement") && !!(node.params && node.params.length || node.hash);
|
|
1189
1189
|
},
|
|
1190
|
-
scopedId: function scopedId(
|
|
1191
|
-
return /^\.|this\b/.test(
|
|
1190
|
+
scopedId: function scopedId(path24) {
|
|
1191
|
+
return /^\.|this\b/.test(path24.original);
|
|
1192
1192
|
},
|
|
1193
1193
|
// an ID is simple if it only has one part, and that part is not
|
|
1194
1194
|
// `..` or `this`.
|
|
1195
|
-
simpleId: function simpleId(
|
|
1196
|
-
return
|
|
1195
|
+
simpleId: function simpleId(path24) {
|
|
1196
|
+
return path24.parts.length === 1 && !AST.helpers.scopedId(path24) && !path24.depth;
|
|
1197
1197
|
}
|
|
1198
1198
|
}
|
|
1199
1199
|
};
|
|
@@ -2263,12 +2263,12 @@ var require_helpers2 = __commonJS({
|
|
|
2263
2263
|
loc
|
|
2264
2264
|
};
|
|
2265
2265
|
}
|
|
2266
|
-
function prepareMustache(
|
|
2266
|
+
function prepareMustache(path24, params, hash, open, strip, locInfo) {
|
|
2267
2267
|
var escapeFlag = open.charAt(3) || open.charAt(2), escaped = escapeFlag !== "{" && escapeFlag !== "&";
|
|
2268
2268
|
var decorator = /\*/.test(open);
|
|
2269
2269
|
return {
|
|
2270
2270
|
type: decorator ? "Decorator" : "MustacheStatement",
|
|
2271
|
-
path:
|
|
2271
|
+
path: path24,
|
|
2272
2272
|
params,
|
|
2273
2273
|
hash,
|
|
2274
2274
|
escaped,
|
|
@@ -2586,9 +2586,9 @@ var require_compiler = __commonJS({
|
|
|
2586
2586
|
},
|
|
2587
2587
|
DecoratorBlock: function DecoratorBlock(decorator) {
|
|
2588
2588
|
var program2 = decorator.program && this.compileProgram(decorator.program);
|
|
2589
|
-
var params = this.setupFullMustacheParams(decorator, program2, void 0),
|
|
2589
|
+
var params = this.setupFullMustacheParams(decorator, program2, void 0), path24 = decorator.path;
|
|
2590
2590
|
this.useDecorators = true;
|
|
2591
|
-
this.opcode("registerDecorator", params.length,
|
|
2591
|
+
this.opcode("registerDecorator", params.length, path24.original);
|
|
2592
2592
|
},
|
|
2593
2593
|
PartialStatement: function PartialStatement(partial) {
|
|
2594
2594
|
this.usePartial = true;
|
|
@@ -2652,46 +2652,46 @@ var require_compiler = __commonJS({
|
|
|
2652
2652
|
}
|
|
2653
2653
|
},
|
|
2654
2654
|
ambiguousSexpr: function ambiguousSexpr(sexpr, program2, inverse) {
|
|
2655
|
-
var
|
|
2656
|
-
this.opcode("getContext",
|
|
2655
|
+
var path24 = sexpr.path, name = path24.parts[0], isBlock = program2 != null || inverse != null;
|
|
2656
|
+
this.opcode("getContext", path24.depth);
|
|
2657
2657
|
this.opcode("pushProgram", program2);
|
|
2658
2658
|
this.opcode("pushProgram", inverse);
|
|
2659
|
-
|
|
2660
|
-
this.accept(
|
|
2659
|
+
path24.strict = true;
|
|
2660
|
+
this.accept(path24);
|
|
2661
2661
|
this.opcode("invokeAmbiguous", name, isBlock);
|
|
2662
2662
|
},
|
|
2663
2663
|
simpleSexpr: function simpleSexpr(sexpr) {
|
|
2664
|
-
var
|
|
2665
|
-
|
|
2666
|
-
this.accept(
|
|
2664
|
+
var path24 = sexpr.path;
|
|
2665
|
+
path24.strict = true;
|
|
2666
|
+
this.accept(path24);
|
|
2667
2667
|
this.opcode("resolvePossibleLambda");
|
|
2668
2668
|
},
|
|
2669
2669
|
helperSexpr: function helperSexpr(sexpr, program2, inverse) {
|
|
2670
|
-
var params = this.setupFullMustacheParams(sexpr, program2, inverse),
|
|
2670
|
+
var params = this.setupFullMustacheParams(sexpr, program2, inverse), path24 = sexpr.path, name = path24.parts[0];
|
|
2671
2671
|
if (this.options.knownHelpers[name]) {
|
|
2672
2672
|
this.opcode("invokeKnownHelper", params.length, name);
|
|
2673
2673
|
} else if (this.options.knownHelpersOnly) {
|
|
2674
2674
|
throw new _exception2["default"]("You specified knownHelpersOnly, but used the unknown helper " + name, sexpr);
|
|
2675
2675
|
} else {
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
this.accept(
|
|
2679
|
-
this.opcode("invokeHelper", params.length,
|
|
2676
|
+
path24.strict = true;
|
|
2677
|
+
path24.falsy = true;
|
|
2678
|
+
this.accept(path24);
|
|
2679
|
+
this.opcode("invokeHelper", params.length, path24.original, _ast2["default"].helpers.simpleId(path24));
|
|
2680
2680
|
}
|
|
2681
2681
|
},
|
|
2682
|
-
PathExpression: function PathExpression(
|
|
2683
|
-
this.addDepth(
|
|
2684
|
-
this.opcode("getContext",
|
|
2685
|
-
var name =
|
|
2682
|
+
PathExpression: function PathExpression(path24) {
|
|
2683
|
+
this.addDepth(path24.depth);
|
|
2684
|
+
this.opcode("getContext", path24.depth);
|
|
2685
|
+
var name = path24.parts[0], scoped = _ast2["default"].helpers.scopedId(path24), blockParamId = !path24.depth && !scoped && this.blockParamIndex(name);
|
|
2686
2686
|
if (blockParamId) {
|
|
2687
|
-
this.opcode("lookupBlockParam", blockParamId,
|
|
2687
|
+
this.opcode("lookupBlockParam", blockParamId, path24.parts);
|
|
2688
2688
|
} else if (!name) {
|
|
2689
2689
|
this.opcode("pushContext");
|
|
2690
|
-
} else if (
|
|
2690
|
+
} else if (path24.data) {
|
|
2691
2691
|
this.options.data = true;
|
|
2692
|
-
this.opcode("lookupData",
|
|
2692
|
+
this.opcode("lookupData", path24.depth, path24.parts, path24.strict);
|
|
2693
2693
|
} else {
|
|
2694
|
-
this.opcode("lookupOnContext",
|
|
2694
|
+
this.opcode("lookupOnContext", path24.parts, path24.falsy, path24.strict, scoped);
|
|
2695
2695
|
}
|
|
2696
2696
|
},
|
|
2697
2697
|
StringLiteral: function StringLiteral(string) {
|
|
@@ -3041,16 +3041,16 @@ var require_util = __commonJS({
|
|
|
3041
3041
|
}
|
|
3042
3042
|
exports2.urlGenerate = urlGenerate;
|
|
3043
3043
|
function normalize(aPath) {
|
|
3044
|
-
var
|
|
3044
|
+
var path24 = aPath;
|
|
3045
3045
|
var url = urlParse(aPath);
|
|
3046
3046
|
if (url) {
|
|
3047
3047
|
if (!url.path) {
|
|
3048
3048
|
return aPath;
|
|
3049
3049
|
}
|
|
3050
|
-
|
|
3050
|
+
path24 = url.path;
|
|
3051
3051
|
}
|
|
3052
|
-
var isAbsolute = exports2.isAbsolute(
|
|
3053
|
-
var parts =
|
|
3052
|
+
var isAbsolute = exports2.isAbsolute(path24);
|
|
3053
|
+
var parts = path24.split(/\/+/);
|
|
3054
3054
|
for (var part, up = 0, i = parts.length - 1; i >= 0; i--) {
|
|
3055
3055
|
part = parts[i];
|
|
3056
3056
|
if (part === ".") {
|
|
@@ -3067,18 +3067,18 @@ var require_util = __commonJS({
|
|
|
3067
3067
|
}
|
|
3068
3068
|
}
|
|
3069
3069
|
}
|
|
3070
|
-
|
|
3071
|
-
if (
|
|
3072
|
-
|
|
3070
|
+
path24 = parts.join("/");
|
|
3071
|
+
if (path24 === "") {
|
|
3072
|
+
path24 = isAbsolute ? "/" : ".";
|
|
3073
3073
|
}
|
|
3074
3074
|
if (url) {
|
|
3075
|
-
url.path =
|
|
3075
|
+
url.path = path24;
|
|
3076
3076
|
return urlGenerate(url);
|
|
3077
3077
|
}
|
|
3078
|
-
return
|
|
3078
|
+
return path24;
|
|
3079
3079
|
}
|
|
3080
3080
|
exports2.normalize = normalize;
|
|
3081
|
-
function
|
|
3081
|
+
function join22(aRoot, aPath) {
|
|
3082
3082
|
if (aRoot === "") {
|
|
3083
3083
|
aRoot = ".";
|
|
3084
3084
|
}
|
|
@@ -3110,7 +3110,7 @@ var require_util = __commonJS({
|
|
|
3110
3110
|
}
|
|
3111
3111
|
return joined;
|
|
3112
3112
|
}
|
|
3113
|
-
exports2.join =
|
|
3113
|
+
exports2.join = join22;
|
|
3114
3114
|
exports2.isAbsolute = function(aPath) {
|
|
3115
3115
|
return aPath.charAt(0) === "/" || urlRegexp.test(aPath);
|
|
3116
3116
|
};
|
|
@@ -3283,7 +3283,7 @@ var require_util = __commonJS({
|
|
|
3283
3283
|
parsed.path = parsed.path.substring(0, index + 1);
|
|
3284
3284
|
}
|
|
3285
3285
|
}
|
|
3286
|
-
sourceURL =
|
|
3286
|
+
sourceURL = join22(urlGenerate(parsed), sourceURL);
|
|
3287
3287
|
}
|
|
3288
3288
|
return normalize(sourceURL);
|
|
3289
3289
|
}
|
|
@@ -5858,8 +5858,8 @@ var require_printer = __commonJS({
|
|
|
5858
5858
|
return this.accept(sexpr.path) + " " + params + hash;
|
|
5859
5859
|
};
|
|
5860
5860
|
PrintVisitor.prototype.PathExpression = function(id) {
|
|
5861
|
-
var
|
|
5862
|
-
return (id.data ? "@" : "") + "PATH:" +
|
|
5861
|
+
var path24 = id.parts.join("/");
|
|
5862
|
+
return (id.data ? "@" : "") + "PATH:" + path24;
|
|
5863
5863
|
};
|
|
5864
5864
|
PrintVisitor.prototype.StringLiteral = function(string) {
|
|
5865
5865
|
return '"' + string.value + '"';
|
|
@@ -5912,7 +5912,7 @@ var require_lib = __commonJS({
|
|
|
5912
5912
|
// src/index.ts
|
|
5913
5913
|
var import_commander = require("commander");
|
|
5914
5914
|
var fs27 = __toESM(require("node:fs"));
|
|
5915
|
-
var
|
|
5915
|
+
var path23 = __toESM(require("node:path"));
|
|
5916
5916
|
|
|
5917
5917
|
// src/commands/setup.ts
|
|
5918
5918
|
var path9 = __toESM(require("node:path"));
|
|
@@ -9383,6 +9383,7 @@ function registerTemplateCommand(program2, registry2) {
|
|
|
9383
9383
|
|
|
9384
9384
|
// src/commands/update.ts
|
|
9385
9385
|
var fs24 = __toESM(require("node:fs"));
|
|
9386
|
+
var path20 = __toESM(require("node:path"));
|
|
9386
9387
|
var import_node_child_process2 = require("node:child_process");
|
|
9387
9388
|
|
|
9388
9389
|
// src/utils/version.ts
|
|
@@ -9814,23 +9815,139 @@ async function updateCLI(fetchFn) {
|
|
|
9814
9815
|
return result;
|
|
9815
9816
|
}
|
|
9816
9817
|
function getInstalledSkillIds(targetDir) {
|
|
9817
|
-
const
|
|
9818
|
+
const config = readDeepStormConfig(targetDir);
|
|
9819
|
+
if (!config) return [];
|
|
9820
|
+
const ids = config.installedSkills;
|
|
9821
|
+
return Array.isArray(ids) ? ids : [];
|
|
9822
|
+
}
|
|
9823
|
+
function migrateOldDataSources(targetDir) {
|
|
9824
|
+
const migrated = [];
|
|
9818
9825
|
try {
|
|
9819
|
-
const
|
|
9820
|
-
|
|
9821
|
-
|
|
9822
|
-
|
|
9823
|
-
|
|
9826
|
+
const claudeSettingsPath = path20.join(targetDir, ".claude", "settings.json");
|
|
9827
|
+
if (fs24.existsSync(claudeSettingsPath)) {
|
|
9828
|
+
const raw = fs24.readFileSync(claudeSettingsPath, "utf-8");
|
|
9829
|
+
const claudeConfig = JSON.parse(raw);
|
|
9830
|
+
if (claudeConfig.deepstorm && typeof claudeConfig.deepstorm === "object" && Object.keys(claudeConfig.deepstorm).length > 0) {
|
|
9831
|
+
writeDeepStormConfig(targetDir, claudeConfig.deepstorm);
|
|
9832
|
+
delete claudeConfig.deepstorm;
|
|
9833
|
+
fs24.writeFileSync(claudeSettingsPath, JSON.stringify(claudeConfig, null, 2) + "\n", "utf-8");
|
|
9834
|
+
migrated.push(".claude/settings.json \u2192 .deepstorm/settings.json");
|
|
9835
|
+
console.log(" \u2714 \u5DF2\u8FC1\u79FB .claude/settings.json \u4E2D\u7684 DeepStorm \u914D\u7F6E");
|
|
9836
|
+
}
|
|
9837
|
+
}
|
|
9838
|
+
} catch (err) {
|
|
9839
|
+
console.log(` \u26A0 deepstorm \u914D\u7F6E\u8FC1\u79FB\u5931\u8D25: ${err instanceof Error ? err.message : String(err)}`);
|
|
9840
|
+
}
|
|
9841
|
+
try {
|
|
9842
|
+
const sweepInitPath = path20.join(targetDir, ".sweep-init");
|
|
9843
|
+
if (fs24.existsSync(sweepInitPath)) {
|
|
9844
|
+
const existing = readDeepStormConfig(targetDir);
|
|
9845
|
+
const hasE2ePath = existing && existing.sweep?.e2eProjectPath;
|
|
9846
|
+
if (!hasE2ePath) {
|
|
9847
|
+
writeDeepStormConfig(targetDir, { sweep: { e2eProjectPath: "." } });
|
|
9848
|
+
migrated.push(".sweep-init \u2192 sweep.e2eProjectPath");
|
|
9849
|
+
console.log(' \u2714 \u5DF2\u8FC1\u79FB .sweep-init \u2192 sweep.e2eProjectPath = "."');
|
|
9850
|
+
}
|
|
9851
|
+
fs24.rmSync(sweepInitPath, { force: true });
|
|
9852
|
+
if (hasE2ePath) {
|
|
9853
|
+
console.log(" \u2139 .sweep-init \u5DF2\u5220\u9664\uFF08sweep.e2eProjectPath \u5DF2\u5B58\u5728\uFF0C\u672A\u8986\u76D6\uFF09");
|
|
9854
|
+
}
|
|
9855
|
+
}
|
|
9856
|
+
} catch (err) {
|
|
9857
|
+
console.log(` \u26A0 .sweep-init \u8FC1\u79FB\u5931\u8D25: ${err instanceof Error ? err.message : String(err)}`);
|
|
9858
|
+
}
|
|
9859
|
+
try {
|
|
9860
|
+
const envPath = path20.join(targetDir, ".env");
|
|
9861
|
+
if (fs24.existsSync(envPath)) {
|
|
9862
|
+
const raw = fs24.readFileSync(envPath, "utf-8");
|
|
9863
|
+
const lines = raw.split("\n");
|
|
9864
|
+
const baseUrlVars = {};
|
|
9865
|
+
let defaultEnv = "test";
|
|
9866
|
+
let hasDefaultEnv = false;
|
|
9867
|
+
const remainingLines = [];
|
|
9868
|
+
for (const line of lines) {
|
|
9869
|
+
const trimmed = line.trim();
|
|
9870
|
+
const baseMatch = trimmed.match(/^BASE_URL_(\w+)=(.+)$/);
|
|
9871
|
+
if (baseMatch) {
|
|
9872
|
+
const envName = baseMatch[1].toLowerCase();
|
|
9873
|
+
let url = baseMatch[2].trim();
|
|
9874
|
+
if (url.startsWith('"') && url.endsWith('"') || url.startsWith("'") && url.endsWith("'")) {
|
|
9875
|
+
url = url.slice(1, -1);
|
|
9876
|
+
}
|
|
9877
|
+
baseUrlVars[envName] = url;
|
|
9878
|
+
continue;
|
|
9879
|
+
}
|
|
9880
|
+
const defaultMatch = trimmed.match(/^DEFAULT_ENV=(.+)$/);
|
|
9881
|
+
if (defaultMatch) {
|
|
9882
|
+
hasDefaultEnv = true;
|
|
9883
|
+
defaultEnv = defaultMatch[1].trim();
|
|
9884
|
+
continue;
|
|
9885
|
+
}
|
|
9886
|
+
remainingLines.push(line);
|
|
9887
|
+
}
|
|
9888
|
+
if (Object.keys(baseUrlVars).length > 0) {
|
|
9889
|
+
const environments = {};
|
|
9890
|
+
for (const [envName, url] of Object.entries(baseUrlVars)) {
|
|
9891
|
+
environments[envName] = { baseUrl: url };
|
|
9892
|
+
}
|
|
9893
|
+
environments.default = defaultEnv;
|
|
9894
|
+
const existing = readDeepStormConfig(targetDir);
|
|
9895
|
+
const hasEnvs = existing && existing.sweep?.environments;
|
|
9896
|
+
if (!hasEnvs) {
|
|
9897
|
+
writeDeepStormConfig(targetDir, { sweep: { environments } });
|
|
9898
|
+
migrated.push(".env BASE_URL \u2192 sweep.environments");
|
|
9899
|
+
console.log(` \u2714 \u5DF2\u8FC1\u79FB .env \u4E2D ${Object.keys(baseUrlVars).length} \u4E2A\u73AF\u5883\u914D\u7F6E`);
|
|
9900
|
+
}
|
|
9901
|
+
const cleaned = remainingLines.join("\n").trimEnd();
|
|
9902
|
+
if (cleaned === "") {
|
|
9903
|
+
fs24.writeFileSync(envPath, "# \u73AF\u5883 baseURL \u914D\u7F6E\u5DF2\u8FC1\u79FB\u81F3 .deepstorm/settings.json\n# \u8BF7\u52FF\u5728\u6B64\u5904\u91CD\u590D\u5B9A\u4E49 BASE_URL_* \u914D\u7F6E\n", "utf-8");
|
|
9904
|
+
} else if (hasDefaultEnv || Object.keys(baseUrlVars).length > 0) {
|
|
9905
|
+
fs24.writeFileSync(envPath, cleaned + "\n", "utf-8");
|
|
9906
|
+
}
|
|
9907
|
+
}
|
|
9908
|
+
}
|
|
9909
|
+
} catch (err) {
|
|
9910
|
+
console.log(` \u26A0 .env baseURL \u8FC1\u79FB\u5931\u8D25: ${err instanceof Error ? err.message : String(err)}`);
|
|
9824
9911
|
}
|
|
9912
|
+
try {
|
|
9913
|
+
const scopeConfigPath = path20.join(targetDir, ".deepstorm", "scope-config.json");
|
|
9914
|
+
if (fs24.existsSync(scopeConfigPath)) {
|
|
9915
|
+
const raw = fs24.readFileSync(scopeConfigPath, "utf-8");
|
|
9916
|
+
const scopeData = JSON.parse(raw);
|
|
9917
|
+
const scope = {};
|
|
9918
|
+
if ("enabled" in scopeData) scope.enabled = scopeData.enabled;
|
|
9919
|
+
if ("ciEnabled" in scopeData) scope.ciEnabled = scopeData.ciEnabled;
|
|
9920
|
+
if ("domains" in scopeData) scope.domains = scopeData.domains;
|
|
9921
|
+
const existing = readDeepStormConfig(targetDir);
|
|
9922
|
+
const hasReefScope = existing && existing.reef?.scope;
|
|
9923
|
+
if (!hasReefScope) {
|
|
9924
|
+
writeDeepStormConfig(targetDir, { reef: { scope } });
|
|
9925
|
+
migrated.push("scope-config.json \u2192 reef.scope");
|
|
9926
|
+
console.log(" \u2714 \u5DF2\u8FC1\u79FB .deepstorm/scope-config.json \u2192 reef.scope");
|
|
9927
|
+
}
|
|
9928
|
+
fs24.rmSync(scopeConfigPath, { force: true });
|
|
9929
|
+
if (hasReefScope) {
|
|
9930
|
+
console.log(" \u2139 scope-config.json \u5DF2\u5220\u9664\uFF08reef.scope \u5DF2\u5B58\u5728\uFF0C\u672A\u8986\u76D6\uFF09");
|
|
9931
|
+
}
|
|
9932
|
+
}
|
|
9933
|
+
} catch (err) {
|
|
9934
|
+
console.log(` \u26A0 scope-config.json \u8FC1\u79FB\u5931\u8D25: ${err instanceof Error ? err.message : String(err)}`);
|
|
9935
|
+
}
|
|
9936
|
+
return { migrated };
|
|
9825
9937
|
}
|
|
9826
9938
|
function registerUpdateCommand(program2) {
|
|
9827
9939
|
const cliDir2 = __dirname;
|
|
9828
9940
|
program2.command("update").description("\u68C0\u67E5 CLI \u66F4\u65B0\u5E76\u540C\u6B65\u5DF2\u5B89\u88C5 skill \u7684\u5B98\u65B9\u6700\u65B0\u6A21\u677F").action(async () => {
|
|
9829
|
-
const
|
|
9941
|
+
const targetDir = process.cwd();
|
|
9942
|
+
const { migrated } = migrateOldDataSources(targetDir);
|
|
9943
|
+
if (migrated.length > 0) {
|
|
9944
|
+
console.log(`\u2714 \u5DF2\u5B8C\u6210 ${migrated.length} \u9879\u65E7\u6570\u636E\u6E90\u8FC1\u79FB`);
|
|
9945
|
+
}
|
|
9946
|
+
const installedIds = getInstalledSkillIds(targetDir);
|
|
9830
9947
|
if (installedIds.length === 0) {
|
|
9831
9948
|
console.log("\u672A\u68C0\u6D4B\u5230\u5DF2\u5B89\u88C5\u7684 skill\uFF0C\u8DF3\u8FC7\u540C\u6B65");
|
|
9832
9949
|
} else {
|
|
9833
|
-
upgradeTemplates(cliDir2,
|
|
9950
|
+
upgradeTemplates(cliDir2, targetDir, installedIds);
|
|
9834
9951
|
}
|
|
9835
9952
|
await updateCLI();
|
|
9836
9953
|
});
|
|
@@ -9844,7 +9961,7 @@ function registerPilotCommands(program2) {
|
|
|
9844
9961
|
|
|
9845
9962
|
// src/commands/doctor.ts
|
|
9846
9963
|
var fs25 = __toESM(require("node:fs"));
|
|
9847
|
-
var
|
|
9964
|
+
var path21 = __toESM(require("node:path"));
|
|
9848
9965
|
function getInstalledSkills(targetDir) {
|
|
9849
9966
|
const settingsPath = getDeepStormConfigPath(targetDir);
|
|
9850
9967
|
try {
|
|
@@ -9896,15 +10013,15 @@ function runDoctor(targetDir) {
|
|
|
9896
10013
|
});
|
|
9897
10014
|
}
|
|
9898
10015
|
}
|
|
9899
|
-
const skillsDir =
|
|
10016
|
+
const skillsDir = path21.join(targetDir, ".claude", "skills");
|
|
9900
10017
|
if (fs25.existsSync(skillsDir)) {
|
|
9901
10018
|
const skillDirs = fs25.readdirSync(skillsDir).filter(
|
|
9902
|
-
(n) => fs25.statSync(
|
|
10019
|
+
(n) => fs25.statSync(path21.join(skillsDir, n)).isDirectory()
|
|
9903
10020
|
);
|
|
9904
10021
|
let validCount = 0;
|
|
9905
10022
|
let invalidCount = 0;
|
|
9906
10023
|
for (const skillName of skillDirs) {
|
|
9907
|
-
const skillMd =
|
|
10024
|
+
const skillMd = path21.join(skillsDir, skillName, "SKILL.md");
|
|
9908
10025
|
if (!fs25.existsSync(skillMd)) {
|
|
9909
10026
|
invalidCount++;
|
|
9910
10027
|
continue;
|
|
@@ -9939,7 +10056,7 @@ function runDoctor(targetDir) {
|
|
|
9939
10056
|
const installedSkills = getInstalledSkills(targetDir);
|
|
9940
10057
|
const missingSkills = [];
|
|
9941
10058
|
for (const skillId of installedSkills) {
|
|
9942
|
-
if (!fs25.existsSync(
|
|
10059
|
+
if (!fs25.existsSync(path21.join(skillsDir, skillId))) {
|
|
9943
10060
|
missingSkills.push(skillId);
|
|
9944
10061
|
}
|
|
9945
10062
|
}
|
|
@@ -9963,7 +10080,7 @@ function runDoctor(targetDir) {
|
|
|
9963
10080
|
message: ".claude/skills/ \u4E0D\u5B58\u5728"
|
|
9964
10081
|
});
|
|
9965
10082
|
}
|
|
9966
|
-
const mcpPath =
|
|
10083
|
+
const mcpPath = path21.join(targetDir, ".mcp.json");
|
|
9967
10084
|
if (fs25.existsSync(mcpPath)) {
|
|
9968
10085
|
try {
|
|
9969
10086
|
const mcp = JSON.parse(fs25.readFileSync(mcpPath, "utf-8"));
|
|
@@ -10085,7 +10202,7 @@ function printDoctorReport(report) {
|
|
|
10085
10202
|
|
|
10086
10203
|
// src/commands/uninstall.ts
|
|
10087
10204
|
var fs26 = __toESM(require("node:fs"));
|
|
10088
|
-
var
|
|
10205
|
+
var path22 = __toESM(require("node:path"));
|
|
10089
10206
|
var import_prompts4 = require("@clack/prompts");
|
|
10090
10207
|
async function uninstallDeepStorm(targetDir) {
|
|
10091
10208
|
const settingsPath = getDeepStormConfigPath(targetDir);
|
|
@@ -10105,7 +10222,7 @@ async function uninstallDeepStorm(targetDir) {
|
|
|
10105
10222
|
}
|
|
10106
10223
|
cleanInstalled(targetDir);
|
|
10107
10224
|
resetConfig(targetDir);
|
|
10108
|
-
const templatesDir =
|
|
10225
|
+
const templatesDir = path22.join(targetDir, ".deepstorm", "templates");
|
|
10109
10226
|
if (fs26.existsSync(templatesDir)) {
|
|
10110
10227
|
const shouldDelete = await (0, import_prompts4.confirm)({
|
|
10111
10228
|
message: "\u662F\u5426\u5220\u9664 .deepstorm/templates/ \u76EE\u5F55\uFF1F",
|
|
@@ -10125,7 +10242,7 @@ async function uninstallDeepStorm(targetDir) {
|
|
|
10125
10242
|
var program = new import_commander.Command();
|
|
10126
10243
|
program.name("deepstorm").description("DeepStorm CLI \u2014 \u4E00\u952E\u914D\u7F6E\u9879\u76EE\u5F00\u53D1\u73AF\u5883").version(getCliVersion(), "-v, --version", "\u8F93\u51FA\u7248\u672C\u53F7");
|
|
10127
10244
|
function loadRegistry() {
|
|
10128
|
-
const registryPath =
|
|
10245
|
+
const registryPath = path23.join(__dirname, "registry.json");
|
|
10129
10246
|
try {
|
|
10130
10247
|
const raw = fs27.readFileSync(registryPath, "utf-8");
|
|
10131
10248
|
return JSON.parse(raw);
|
|
@@ -137,7 +137,7 @@ if [ -f "$SCOPE_HOOK" ]; then
|
|
|
137
137
|
bash "$SCOPE_HOOK" || {
|
|
138
138
|
echo ""
|
|
139
139
|
echo "提示:如需继续提交,请使用 'reef-scope-split.sh' 拆分分支"
|
|
140
|
-
echo "或临时禁用 scope 检查(不推荐):
|
|
140
|
+
echo "或临时禁用 scope 检查(不推荐):settings.json → reef.scope.enabled: false"
|
|
141
141
|
exit 1
|
|
142
142
|
}
|
|
143
143
|
fi
|
|
@@ -71,13 +71,17 @@ bash packages/reef/hooks/reef-scope-split.sh
|
|
|
71
71
|
|
|
72
72
|
## 配置文件
|
|
73
73
|
|
|
74
|
-
|
|
74
|
+
安装后配置写入 `.deepstorm/settings.json` 的 `reef.scope` 字段:
|
|
75
75
|
|
|
76
76
|
```json
|
|
77
77
|
{
|
|
78
|
-
"
|
|
79
|
-
|
|
80
|
-
|
|
78
|
+
"reef": {
|
|
79
|
+
"scope": {
|
|
80
|
+
"enabled": true,
|
|
81
|
+
"ciEnabled": true,
|
|
82
|
+
"domains": []
|
|
83
|
+
}
|
|
84
|
+
}
|
|
81
85
|
}
|
|
82
86
|
```
|
|
83
87
|
|
|
@@ -122,7 +126,7 @@ scope-check:
|
|
|
122
126
|
## 常见问题
|
|
123
127
|
|
|
124
128
|
**Q: 我不想在所有项目上都启用这个检查?**
|
|
125
|
-
A:
|
|
129
|
+
A: 每个项目独立安装和配置,settings.json 中 `reef.scope.enabled: false` 可关闭。
|
|
126
130
|
|
|
127
131
|
**Q: 我改了文档也要被阻断吗?**
|
|
128
132
|
A: 不会。文档(markdown、README 等)变更会被归类为 `documentation` 领域,不计入多领域阻断判断。
|
|
@@ -8,7 +8,7 @@ deepstorm:
|
|
|
8
8
|
|
|
9
9
|
# Sweep Setup — E2E 测试项目初始化
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
初始化当前目录(或指定子目录)为 Sweep E2E 测试项目。根据 DeepStorm setup 阶段选择的 E2E 框架(如 Playwright),创建对应的项目骨架。
|
|
12
12
|
|
|
13
13
|
> **注意:** setup 是 flow-create 和 flow-run 的硬性前置条件。如果当前目录未初始化,其他 skill 会报错引导执行 `/sweep-init`。
|
|
14
14
|
|
|
@@ -41,27 +41,73 @@ cat .deepstorm/settings.json 2>/dev/null | grep -o '"e2eFramework"[^,]*' | head
|
|
|
41
41
|
|
|
42
42
|
---
|
|
43
43
|
|
|
44
|
+
### 步骤 0A:选择生成位置
|
|
45
|
+
|
|
46
|
+
在执行初始化前,先询问用户的 E2E 测试项目类型,确定文件生成的目标目录。
|
|
47
|
+
|
|
48
|
+
> **注意:** TARGET_DIR 是后续所有文件操作的前缀,在整个初始化流程中使用。
|
|
49
|
+
|
|
50
|
+
#### 0A.1 询问项目类型
|
|
51
|
+
|
|
52
|
+
```
|
|
53
|
+
📂 请问你的 E2E 测试是哪种类型?
|
|
54
|
+
|
|
55
|
+
a) 独立项目 → 生成在当前目录
|
|
56
|
+
b) 与其他项目混放 → 生成到子目录
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
**如果选择 a(独立项目):**
|
|
60
|
+
- 设置 TARGET_DIR 为当前目录(`.`)
|
|
61
|
+
- 继续到步骤 1
|
|
62
|
+
|
|
63
|
+
**如果选择 b(混放项目):**
|
|
64
|
+
- 进一步询问子目录选择:
|
|
65
|
+
|
|
66
|
+
```
|
|
67
|
+
请选择目标子目录:
|
|
68
|
+
1) e2e/ 👈 推荐
|
|
69
|
+
2) tests/e2e/
|
|
70
|
+
3) 自定义路径
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
- 如果选择 3(自定义),AI 询问用户输入路径
|
|
74
|
+
- 验证用户输入的路径:不能为空、不能是绝对路径、不能包含 `..`
|
|
75
|
+
- 设置 TARGET_DIR 为所选路径(如 `"e2e"`、`"tests/e2e"` 等)
|
|
76
|
+
- 如果子目录已存在且非空,输出警告并询问用户是否确认继续
|
|
77
|
+
|
|
78
|
+
#### 0A.2 记录 TARGET_DIR
|
|
79
|
+
|
|
80
|
+
`TARGET_DIR` 变量在后续所有步骤中使用。写入 `.deepstorm/settings.json` 中的 `sweep.e2eProjectPath` 字段将在步骤 8 中完成。
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
44
84
|
### 步骤 1:检查初始化状态
|
|
45
85
|
|
|
46
|
-
|
|
86
|
+
检查当前项目是否已初始化为 E2E 测试项目。从 `.deepstorm/settings.json` 读取 `sweep.e2eProjectPath` 判断。
|
|
87
|
+
|
|
88
|
+
#### 1.1 检查 settings.json
|
|
47
89
|
|
|
48
|
-
|
|
90
|
+
```bash
|
|
91
|
+
E2E_PATH=$(cat .deepstorm/settings.json 2>/dev/null | grep -o '"e2eProjectPath"[^,]*' | head -1 | cut -d'"' -f4)
|
|
92
|
+
```
|
|
49
93
|
|
|
50
|
-
|
|
51
|
-
- **THEN** 提示"当前目录已初始化为 Sweep 测试项目。如需重新初始化,请删除 .sweep-init 文件后重试。"并退出
|
|
94
|
+
#### 1.2 已初始化 → 退出
|
|
52
95
|
|
|
53
|
-
|
|
96
|
+
- **WHEN** `sweep.e2eProjectPath` 已设置
|
|
97
|
+
- **THEN** 提示"当前项目已初始化为 Sweep 测试项目。如需重新初始化,请删除 `sweep.e2eProjectPath` 配置后重试。"并退出
|
|
54
98
|
|
|
55
|
-
|
|
99
|
+
#### 1.3 未初始化 → 继续
|
|
100
|
+
|
|
101
|
+
- **WHEN** `sweep.e2eProjectPath` 未设置
|
|
56
102
|
- **THEN** 继续执行初始化流程
|
|
57
103
|
|
|
58
104
|
---
|
|
59
105
|
|
|
60
106
|
### 步骤 2:创建项目目录结构
|
|
61
107
|
|
|
62
|
-
|
|
108
|
+
在 `{TARGET_DIR}` 下创建 E2E 测试项目的目录骨架。
|
|
63
109
|
|
|
64
|
-
####
|
|
110
|
+
#### 目录结构(当 TARGET_DIR 为 `"."` 时)
|
|
65
111
|
|
|
66
112
|
```
|
|
67
113
|
.
|
|
@@ -75,19 +121,27 @@ cat .deepstorm/settings.json 2>/dev/null | grep -o '"e2eFramework"[^,]*' | head
|
|
|
75
121
|
└── tsconfig.json # TypeScript 配置
|
|
76
122
|
```
|
|
77
123
|
|
|
78
|
-
>
|
|
124
|
+
> 如果 TARGET_DIR 为 `"e2e"`,以上所有文件在 `e2e/` 下生成。如果框架为 `playwright`,还会额外生成 `playwright.config.ts`。
|
|
79
125
|
|
|
80
126
|
#### 执行
|
|
81
127
|
|
|
82
128
|
```bash
|
|
83
|
-
|
|
129
|
+
# 计算目标目录:TARGET_DIR 为 "." 时用当前目录,否则用 TARGET_DIR
|
|
130
|
+
if [ "$TARGET_DIR" != "." ]; then
|
|
131
|
+
mkdir -p "$TARGET_DIR"/flows "$TARGET_DIR"/flows/reports "$TARGET_DIR"/scripts
|
|
132
|
+
echo "📂 将在 $TARGET_DIR/ 目录下生成 E2E 测试项目"
|
|
133
|
+
else
|
|
134
|
+
mkdir -p flows flows/reports scripts
|
|
135
|
+
fi
|
|
84
136
|
```
|
|
85
137
|
|
|
86
138
|
---
|
|
87
139
|
|
|
88
140
|
### 步骤 3:生成项目配置文件(框架感知)
|
|
89
141
|
|
|
90
|
-
根据步骤 0
|
|
142
|
+
根据步骤 0 读取的框架配置,在 `{TARGET_DIR}` 下生成对应的配置文件。
|
|
143
|
+
|
|
144
|
+
> 所有文件写入路径以 `{TARGET_DIR}` 为前缀。当 `TARGET_DIR` 为 `"."` 时,路径不变。
|
|
91
145
|
|
|
92
146
|
#### 3.1 package.json
|
|
93
147
|
|
|
@@ -115,7 +169,7 @@ mkdir -p flows flows/reports scripts
|
|
|
115
169
|
|
|
116
170
|
#### 3.2 框架配置文件
|
|
117
171
|
|
|
118
|
-
**当框架为 `playwright` 时**,写入 `playwright.config.ts`,包含多环境 baseURL 设置:
|
|
172
|
+
**当框架为 `playwright` 时**,写入 `{TARGET_DIR}/playwright.config.ts`,包含多环境 baseURL 设置:
|
|
119
173
|
|
|
120
174
|
```ts
|
|
121
175
|
import { defineConfig } from '@playwright/test';
|
|
@@ -164,23 +218,43 @@ export default defineConfig({
|
|
|
164
218
|
3. 生产环境(prod)baseURL
|
|
165
219
|
4. 默认目标环境(test / staging / prod)
|
|
166
220
|
|
|
167
|
-
#### 4.2 写入 .
|
|
221
|
+
#### 4.2 写入 settings.json
|
|
222
|
+
|
|
223
|
+
将多环境配置写入 `.deepstorm/settings.json` 的 `sweep.environments` 字段。**不再生成 `.env` 文件。**
|
|
168
224
|
|
|
169
|
-
|
|
225
|
+
使用 Bash 和 `jq` 合并写入(或 AI 读取-修改-写回):
|
|
170
226
|
|
|
171
227
|
```bash
|
|
172
|
-
# .
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
228
|
+
# 先读取现有 settings.json
|
|
229
|
+
SETTINGS=$(cat .deepstorm/settings.json 2>/dev/null || echo "{}")
|
|
230
|
+
|
|
231
|
+
# 构造 environments 配置并写入
|
|
232
|
+
# (使用 jq 或 AI 逐字段修改)
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
写入后的 settings.json 结构示例:
|
|
236
|
+
|
|
237
|
+
```json
|
|
238
|
+
{
|
|
239
|
+
"sweep": {
|
|
240
|
+
"e2eFramework": "playwright",
|
|
241
|
+
"environments": {
|
|
242
|
+
"test": { "baseUrl": "http://localhost:3000" },
|
|
243
|
+
"staging": { "baseUrl": "https://staging.example.com" },
|
|
244
|
+
"prod": { "baseUrl": "https://prod.example.com" },
|
|
245
|
+
"default": "test"
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
177
249
|
```
|
|
178
250
|
|
|
251
|
+
> **注意:** API Key 等敏感信息仍然放在 `.env` 文件(gitignored)中。此处仅迁移多环境 baseURL 配置。
|
|
252
|
+
|
|
179
253
|
---
|
|
180
254
|
|
|
181
255
|
### 步骤 5:创建 topology.yaml 初始模板
|
|
182
256
|
|
|
183
|
-
|
|
257
|
+
写入功能模块拓扑文件基础结构到 `{TARGET_DIR}/flows/topology.yaml`:
|
|
184
258
|
|
|
185
259
|
```yaml
|
|
186
260
|
# flows/topology.yaml
|
|
@@ -202,7 +276,7 @@ modules:
|
|
|
202
276
|
|
|
203
277
|
### 步骤 6:创建 flow-selector.mjs
|
|
204
278
|
|
|
205
|
-
使用 Read 读取本 skill 目录下的 `scripts/flow-selector.mjs` 文件,用 Write 写入目标项目的 `scripts/flow-selector.mjs`。
|
|
279
|
+
使用 Read 读取本 skill 目录下的 `scripts/flow-selector.mjs` 文件,用 Write 写入目标项目的 `{TARGET_DIR}/scripts/flow-selector.mjs`。
|
|
206
280
|
|
|
207
281
|
> **说明:** `flow-selector.mjs` 是基于 `@inquirer/checkbox` 的层级选择工具脚本(供 flow-run skill 调用)。
|
|
208
282
|
> 它与本 SKILL.md 同属一个 skill,作为 skill 的子文件分开维护,避免内联代码块导致的重复。
|
|
@@ -236,16 +310,37 @@ cat .mcp.json 2>/dev/null | grep -c "deepstorm-playwright"
|
|
|
236
310
|
|
|
237
311
|
### 步骤 8:完成初始化
|
|
238
312
|
|
|
239
|
-
#### 8.1
|
|
313
|
+
#### 8.1 写入 e2eProjectPath 到 settings.json
|
|
314
|
+
|
|
315
|
+
将 `sweep.e2eProjectPath` 写入 `.deepstorm/settings.json`。**不再创建 `.sweep-init` 标记文件。**
|
|
240
316
|
|
|
241
317
|
```bash
|
|
242
|
-
|
|
318
|
+
# 值由步骤 0A 确定:
|
|
319
|
+
# 独立项目 → "."
|
|
320
|
+
# 混放项目 → 用户选择的路径(如 "e2e")
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
写入后的 settings.json 结构示例:
|
|
324
|
+
|
|
325
|
+
```json
|
|
326
|
+
{
|
|
327
|
+
"sweep": {
|
|
328
|
+
"e2eFramework": "playwright",
|
|
329
|
+
"e2eProjectPath": "e2e",
|
|
330
|
+
"environments": { ... }
|
|
331
|
+
}
|
|
332
|
+
}
|
|
243
333
|
```
|
|
244
334
|
|
|
245
335
|
#### 8.2 安装依赖
|
|
246
336
|
|
|
247
337
|
```bash
|
|
248
|
-
npm install
|
|
338
|
+
# 在目标目录中执行 npm install
|
|
339
|
+
if [ "$TARGET_DIR" != "." ]; then
|
|
340
|
+
cd "$TARGET_DIR" && npm install
|
|
341
|
+
else
|
|
342
|
+
npm install
|
|
343
|
+
fi
|
|
249
344
|
```
|
|
250
345
|
|
|
251
346
|
#### 8.3 输出完成信息
|
|
@@ -254,15 +349,16 @@ npm install
|
|
|
254
349
|
✅ Sweep E2E 测试项目初始化完成!
|
|
255
350
|
|
|
256
351
|
框架:{所选框架}
|
|
352
|
+
位置:{TARGET_DIR}
|
|
257
353
|
|
|
258
354
|
项目结构:
|
|
259
|
-
flows/ — 测试意图文档目录
|
|
260
|
-
flows/reports/ — 执行报告目录
|
|
261
|
-
scripts/ — 辅助脚本
|
|
262
|
-
(框架配置文件)
|
|
355
|
+
{TARGET_DIR}/flows/ — 测试意图文档目录
|
|
356
|
+
{TARGET_DIR}/flows/reports/ — 执行报告目录
|
|
357
|
+
{TARGET_DIR}/scripts/ — 辅助脚本
|
|
358
|
+
(框架配置文件) — {框架名} 配置文件
|
|
263
359
|
|
|
264
360
|
下一步:
|
|
265
|
-
1. 编辑 flows/topology.yaml 添加你的功能模块
|
|
361
|
+
1. 编辑 {TARGET_DIR}/flows/topology.yaml 添加你的功能模块
|
|
266
362
|
2. 运行 /sweep-plan 创建测试意图文档
|
|
267
363
|
3. 运行 /sweep-run 执行测试流程
|
|
268
364
|
```
|
|
@@ -272,15 +368,16 @@ npm install
|
|
|
272
368
|
## 检查清单
|
|
273
369
|
|
|
274
370
|
- [ ] 已读取框架配置(`deepstorm.sweep.e2eFramework`)
|
|
275
|
-
- [ ]
|
|
276
|
-
- [ ]
|
|
371
|
+
- [ ] 步骤 0A:已选择目标目录(`TARGET_DIR` 已确定)
|
|
372
|
+
- [ ] 步骤 1:检查通过(`e2eProjectPath` 未设置)
|
|
373
|
+
- [ ] 项目目录结构已创建(`{TARGET_DIR}/flows/`、`{TARGET_DIR}/flows/reports/`、`{TARGET_DIR}/scripts/`)
|
|
277
374
|
- [ ] package.json 已写入(含对应框架的依赖)
|
|
278
375
|
- [ ] 框架配置文件已生成(如 playwright.config.ts)
|
|
279
376
|
- [ ] tsconfig.json 已写入
|
|
280
377
|
- [ ] 用户已输入三个环境的 baseURL
|
|
281
|
-
- [ ] .
|
|
378
|
+
- [ ] environments 已写入 `.deepstorm/settings.json` 的 `sweep.environments`
|
|
282
379
|
- [ ] topology.yaml 初始模板已创建
|
|
283
380
|
- [ ] flow-selector.mjs 已写入
|
|
284
381
|
- [ ] 已确认 MCP 配置状态(从 `.mcp.json` 读取)
|
|
285
|
-
- [ ] .
|
|
286
|
-
- [ ] npm install
|
|
382
|
+
- [ ] `sweep.e2eProjectPath` 已写入 `.deepstorm/settings.json`
|
|
383
|
+
- [ ] npm install 已在目标目录执行
|
|
@@ -17,7 +17,7 @@ mcpCapabilities:
|
|
|
17
17
|
|
|
18
18
|
## 前置条件
|
|
19
19
|
|
|
20
|
-
运行 `/sweep-plan`
|
|
20
|
+
运行 `/sweep-plan` 前,请确保项目已通过 `/sweep-init` 初始化(`.deepstorm/settings.json` 中存在 `sweep.e2eProjectPath` 配置)。
|
|
21
21
|
|
|
22
22
|
---
|
|
23
23
|
|
|
@@ -44,10 +44,56 @@ mcpCapabilities:
|
|
|
44
44
|
|
|
45
45
|
## 工作流
|
|
46
46
|
|
|
47
|
-
### 步骤 1
|
|
47
|
+
### 步骤 1:检查初始化状态与路径导航
|
|
48
48
|
|
|
49
|
-
|
|
50
|
-
|
|
49
|
+
从 `.deepstorm/settings.json` 读取 `sweep.e2eProjectPath`,确定 E2E 测试项目的位置,如非根目录则自动切换。支持从子目录向上查找以兼容用户在 E2E 项目子目录中执行的情况。
|
|
50
|
+
|
|
51
|
+
#### 1.1 读取配置(支持向上查找)
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
# 向上查找 .deepstorm/settings.json
|
|
55
|
+
DEEPSTORM_DIR=""
|
|
56
|
+
CUR="$PWD"
|
|
57
|
+
while [ "$CUR" != "/" ]; do
|
|
58
|
+
if [ -f "$CUR/.deepstorm/settings.json" ]; then
|
|
59
|
+
DEEPSTORM_DIR="$CUR"
|
|
60
|
+
break
|
|
61
|
+
fi
|
|
62
|
+
CUR=$(dirname "$CUR")
|
|
63
|
+
done
|
|
64
|
+
|
|
65
|
+
if [ -n "$DEEPSTORM_DIR" ]; then
|
|
66
|
+
E2E_PATH=$(grep -o '"e2eProjectPath"[^,]*' "$DEEPSTORM_DIR/.deepstorm/settings.json" | head -1 | cut -d'"' -f4)
|
|
67
|
+
else
|
|
68
|
+
E2E_PATH=""
|
|
69
|
+
fi
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
#### 1.2 配置存在 → 路径导航
|
|
73
|
+
|
|
74
|
+
- **WHEN** `E2E_PATH` 不为空
|
|
75
|
+
- **THEN** 判断路径值。注意 `E2E_PATH` 是基于 `DEEPSTORM_DIR`(settings.json 所在目录)的相对路径,若当前在子目录则需拼接:
|
|
76
|
+
```bash
|
|
77
|
+
if [ "$E2E_PATH" != "." ]; then
|
|
78
|
+
# 若从子目录找到的 settings.json,E2E_PATH 相对于 DEEPSTORM_DIR
|
|
79
|
+
TARGET_DIR="$E2E_PATH"
|
|
80
|
+
if [ -n "$DEEPSTORM_DIR" ] && [ "$DEEPSTORM_DIR" != "$PWD" ]; then
|
|
81
|
+
TARGET_DIR="$DEEPSTORM_DIR/$E2E_PATH"
|
|
82
|
+
fi
|
|
83
|
+
if [ -d "$TARGET_DIR" ]; then
|
|
84
|
+
echo "📂 切换到 E2E 项目目录: $E2E_PATH"
|
|
85
|
+
cd "$TARGET_DIR"
|
|
86
|
+
else
|
|
87
|
+
echo "❌ E2E 项目目录不存在: $E2E_PATH,请重新运行 /sweep-init"
|
|
88
|
+
exit 1
|
|
89
|
+
fi
|
|
90
|
+
fi
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
#### 1.3 配置不存在 → 报错退出
|
|
94
|
+
|
|
95
|
+
- **WHEN** `E2E_PATH` 为空
|
|
96
|
+
- **THEN** 提示"❌ 未检测到 E2E 项目。请先运行 /sweep-init 初始化。"并退出
|
|
51
97
|
|
|
52
98
|
### 步骤 2:获取测试需求上下文(MCP 动态适配)
|
|
53
99
|
|
|
@@ -303,7 +349,7 @@ node scripts/spec-compiler.mjs flows/user-system/login/login.flow.md
|
|
|
303
349
|
|
|
304
350
|
## 检查清单
|
|
305
351
|
|
|
306
|
-
- [ ]
|
|
352
|
+
- [ ] 项目已初始化(读取 settings.json → sweep.e2eProjectPath)
|
|
307
353
|
- [ ] 获取到测试需求上下文(Issue / PRD / 描述)
|
|
308
354
|
- [ ] 确定放置模块位置(topology.yaml)
|
|
309
355
|
- [ ] 确定文件名
|
|
@@ -58,10 +58,54 @@ node scripts/env-manager.mjs --framework
|
|
|
58
58
|
|
|
59
59
|
---
|
|
60
60
|
|
|
61
|
-
### 步骤 1
|
|
61
|
+
### 步骤 1:检查初始化状态与路径导航
|
|
62
62
|
|
|
63
|
-
|
|
64
|
-
|
|
63
|
+
从 `.deepstorm/settings.json` 读取 `sweep.e2eProjectPath`,确定 E2E 测试项目的位置,如非根目录则自动切换。支持从子目录向上查找以兼容用户在 E2E 项目子目录中执行的情况。
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
# 向上查找 .deepstorm/settings.json
|
|
67
|
+
DEEPSTORM_DIR=""
|
|
68
|
+
CUR="$PWD"
|
|
69
|
+
while [ "$CUR" != "/" ]; do
|
|
70
|
+
if [ -f "$CUR/.deepstorm/settings.json" ]; then
|
|
71
|
+
DEEPSTORM_DIR="$CUR"
|
|
72
|
+
break
|
|
73
|
+
fi
|
|
74
|
+
CUR=$(dirname "$CUR")
|
|
75
|
+
done
|
|
76
|
+
|
|
77
|
+
if [ -n "$DEEPSTORM_DIR" ]; then
|
|
78
|
+
E2E_PATH=$(grep -o '"e2eProjectPath"[^,]*' "$DEEPSTORM_DIR/.deepstorm/settings.json" | head -1 | cut -d'"' -f4)
|
|
79
|
+
else
|
|
80
|
+
E2E_PATH=""
|
|
81
|
+
fi
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
#### 1.1 配置存在 → 路径导航
|
|
85
|
+
|
|
86
|
+
- **WHEN** `E2E_PATH` 不为空
|
|
87
|
+
- **THEN** 判断路径值。注意 `E2E_PATH` 是基于 `DEEPSTORM_DIR`(settings.json 所在目录)的相对路径,若当前在子目录则需拼接:
|
|
88
|
+
```bash
|
|
89
|
+
if [ "$E2E_PATH" != "." ]; then
|
|
90
|
+
# 若从子目录找到的 settings.json,E2E_PATH 相对于 DEEPSTORM_DIR
|
|
91
|
+
TARGET_DIR="$E2E_PATH"
|
|
92
|
+
if [ -n "$DEEPSTORM_DIR" ] && [ "$DEEPSTORM_DIR" != "$PWD" ]; then
|
|
93
|
+
TARGET_DIR="$DEEPSTORM_DIR/$E2E_PATH"
|
|
94
|
+
fi
|
|
95
|
+
if [ -d "$TARGET_DIR" ]; then
|
|
96
|
+
echo "📂 切换到 E2E 项目目录: $E2E_PATH"
|
|
97
|
+
cd "$TARGET_DIR"
|
|
98
|
+
else
|
|
99
|
+
echo "❌ E2E 项目目录不存在: $E2E_PATH,请重新运行 /sweep-init"
|
|
100
|
+
exit 1
|
|
101
|
+
fi
|
|
102
|
+
fi
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
#### 1.2 配置不存在 → 报错退出
|
|
106
|
+
|
|
107
|
+
- **WHEN** `E2E_PATH` 为空
|
|
108
|
+
- **THEN** 提示"❌ 未检测到 E2E 项目。请先运行 /sweep-init 初始化。"并退出
|
|
65
109
|
|
|
66
110
|
---
|
|
67
111
|
|
|
@@ -162,9 +206,9 @@ node scripts/env-manager.mjs --env staging
|
|
|
162
206
|
]}
|
|
163
207
|
```
|
|
164
208
|
|
|
165
|
-
- `--env staging` → 从
|
|
166
|
-
- `--env test` → 从
|
|
167
|
-
- 不传参 → 使用
|
|
209
|
+
- `--env staging` → 从 `settings.json` 的 `sweep.environments.staging` 读取
|
|
210
|
+
- `--env test` → 从 `settings.json` 的 `sweep.environments.test` 读取
|
|
211
|
+
- 不传参 → 使用 `settings.json` 的 `sweep.environments.default`,未设置则默认 `test`
|
|
168
212
|
|
|
169
213
|
#### 4.2 设置环境变量
|
|
170
214
|
|
|
@@ -179,7 +223,7 @@ BASE_URL=$(node scripts/env-manager.mjs --env test | node -pe "JSON.parse(requir
|
|
|
179
223
|
|
|
180
224
|
#### 4.3 环境不存在处理
|
|
181
225
|
|
|
182
|
-
如果 `baseUrl` 为 `null`(指定的环境在
|
|
226
|
+
如果 `baseUrl` 为 `null`(指定的环境在 settings.json 中找不到对应配置):
|
|
183
227
|
|
|
184
228
|
- 提示"未找到 [{env}] 环境的 baseURL 配置"
|
|
185
229
|
- 列出 `availableEnvs` 中已有的环境
|
|
@@ -426,7 +470,7 @@ Flow: L01 - 正常登录成功
|
|
|
426
470
|
## 检查清单
|
|
427
471
|
|
|
428
472
|
- [ ] 前置检查:框架配置已读取(`.deepstorm/settings.json` → `sweep.e2eFramework`)
|
|
429
|
-
- [ ]
|
|
473
|
+
- [ ] 项目已初始化(读取 settings.json → sweep.e2eProjectPath)
|
|
430
474
|
- [ ] 执行范围已确定(--all / --path / 交互选择)
|
|
431
475
|
- [ ] 执行模式已选择(hybrid / native / no-parallel / browser)
|
|
432
476
|
- [ ] MCP 服务按模式正确启动(available → headless / headed / 不启动)
|
|
@@ -3,9 +3,13 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* Env Manager — 测试环境配置管理
|
|
5
5
|
*
|
|
6
|
-
*
|
|
6
|
+
* 从 .deepstorm/settings.json 读取目标环境配置,输出结构化结果。
|
|
7
7
|
* 支持检查 E2E 框架配置和 MCP 可用性。
|
|
8
8
|
*
|
|
9
|
+
* 所有旧数据源(.env BASE_URL、.sweep-init、scope-config.json 等)
|
|
10
|
+
* 已由 `deepstorm update` 统一迁移到 .deepstorm/settings.json,
|
|
11
|
+
* 本文件无需再做兼容处理。
|
|
12
|
+
*
|
|
9
13
|
* Usage:
|
|
10
14
|
* node env-manager.mjs --env test # 解析指定环境
|
|
11
15
|
* node env-manager.mjs --env test --print # 输出 export 语句
|
|
@@ -27,6 +31,65 @@ function getEnvPath() {
|
|
|
27
31
|
function getMcpPath() {
|
|
28
32
|
return resolve(process.cwd(), '.mcp.json');
|
|
29
33
|
}
|
|
34
|
+
function getDeepstormSettingsPath() {
|
|
35
|
+
return resolve(process.cwd(), '.deepstorm', 'settings.json');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// ── settings.json reading ─────────────────────────────────────────
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Read .deepstorm/settings.json and extract environments config.
|
|
42
|
+
* Returns the sweep.environments object or null if not configured.
|
|
43
|
+
*/
|
|
44
|
+
export function readDeepstormEnvironments() {
|
|
45
|
+
const settingsPath = getDeepstormSettingsPath();
|
|
46
|
+
if (!existsSync(settingsPath)) return null;
|
|
47
|
+
try {
|
|
48
|
+
const content = readFileSync(settingsPath, 'utf-8');
|
|
49
|
+
const config = JSON.parse(content);
|
|
50
|
+
return config.sweep?.environments || null;
|
|
51
|
+
} catch {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Read .deepstorm/settings.json and extract e2eProjectPath.
|
|
58
|
+
* Returns the project path string or null if not configured.
|
|
59
|
+
*/
|
|
60
|
+
export function readE2eProjectPath() {
|
|
61
|
+
const settingsPath = getDeepstormSettingsPath();
|
|
62
|
+
if (!existsSync(settingsPath)) return null;
|
|
63
|
+
try {
|
|
64
|
+
const content = readFileSync(settingsPath, 'utf-8');
|
|
65
|
+
const config = JSON.parse(content);
|
|
66
|
+
return config.sweep?.e2eProjectPath || null;
|
|
67
|
+
} catch {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Convert settings.json environments format to the same structure
|
|
74
|
+
* that listEnvs() returns from .env, for a unified return format.
|
|
75
|
+
*
|
|
76
|
+
* Input: { test: { baseUrl: "..." }, staging: { baseUrl: "..." }, default: "test" }
|
|
77
|
+
* Output: { envMap: { BASE_URL_TEST: "...", BASE_URL_STAGING: "...", DEFAULT_ENV: "test" },
|
|
78
|
+
* defaultEnv: "test" }
|
|
79
|
+
*/
|
|
80
|
+
function deepstormEnvsToEnvMap(envs) {
|
|
81
|
+
if (!envs) return { envMap: {}, defaultEnv: 'test' };
|
|
82
|
+
const envMap = {};
|
|
83
|
+
const defaultEnv = envs.default || 'test';
|
|
84
|
+
for (const [name, config] of Object.entries(envs)) {
|
|
85
|
+
if (name === 'default') continue;
|
|
86
|
+
if (config && config.baseUrl) {
|
|
87
|
+
envMap[`BASE_URL_${name.toUpperCase()}`] = config.baseUrl;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
envMap.DEFAULT_ENV = defaultEnv;
|
|
91
|
+
return { envMap, defaultEnv };
|
|
92
|
+
}
|
|
30
93
|
|
|
31
94
|
// ── .env parsing ──────────────────────────────────────────────────
|
|
32
95
|
|
|
@@ -67,26 +130,35 @@ export function readDotEnv() {
|
|
|
67
130
|
}
|
|
68
131
|
|
|
69
132
|
/**
|
|
70
|
-
* Get the default environment name.
|
|
133
|
+
* Get the default environment name from .deepstorm/settings.json.
|
|
71
134
|
* Falls back to 'test' if not configured.
|
|
72
135
|
*/
|
|
73
|
-
export function getDefaultEnv(
|
|
74
|
-
|
|
136
|
+
export function getDefaultEnv() {
|
|
137
|
+
const dsEnvs = readDeepstormEnvironments();
|
|
138
|
+
if (dsEnvs && dsEnvs.default) {
|
|
139
|
+
return dsEnvs.default;
|
|
140
|
+
}
|
|
141
|
+
return 'test';
|
|
75
142
|
}
|
|
76
143
|
|
|
77
144
|
/**
|
|
78
|
-
* List all available environments from .
|
|
79
|
-
*
|
|
145
|
+
* List all available environments from .deepstorm/settings.json.
|
|
146
|
+
*
|
|
147
|
+
* Reads sweep.environments and returns environment name + URL pairs.
|
|
148
|
+
* Returns empty array if not configured.
|
|
80
149
|
*/
|
|
81
|
-
export function listEnvs(
|
|
150
|
+
export function listEnvs() {
|
|
151
|
+
const dsEnvs = readDeepstormEnvironments();
|
|
152
|
+
if (!dsEnvs) return [];
|
|
153
|
+
|
|
82
154
|
const envs = [];
|
|
83
|
-
for (const
|
|
84
|
-
|
|
85
|
-
if (
|
|
155
|
+
for (const [name, config] of Object.entries(dsEnvs)) {
|
|
156
|
+
if (name === 'default') continue;
|
|
157
|
+
if (config && config.baseUrl) {
|
|
86
158
|
envs.push({
|
|
87
|
-
name:
|
|
88
|
-
key:
|
|
89
|
-
url:
|
|
159
|
+
name: name.toLowerCase(),
|
|
160
|
+
key: `BASE_URL_${name.toUpperCase()}`,
|
|
161
|
+
url: config.baseUrl,
|
|
90
162
|
});
|
|
91
163
|
}
|
|
92
164
|
}
|
|
@@ -94,22 +166,27 @@ export function listEnvs(envMap) {
|
|
|
94
166
|
}
|
|
95
167
|
|
|
96
168
|
/**
|
|
97
|
-
* Resolve target environment config.
|
|
169
|
+
* Resolve target environment config from .deepstorm/settings.json.
|
|
98
170
|
*
|
|
99
171
|
* @param {string} [envName] - Target environment name (e.g. 'staging', 'test')
|
|
100
172
|
* @returns {{ env: string, baseUrl: string|null, availableEnvs: Array<{name:string,url:string}> }}
|
|
101
173
|
*
|
|
102
|
-
* If envName is omitted, uses DEFAULT_ENV
|
|
174
|
+
* If envName is omitted, uses DEFAULT_ENV (or 'test').
|
|
175
|
+
* If settings.json has no environments config, baseUrl is null and availableEnvs is empty.
|
|
103
176
|
* If the environment is not found, baseUrl is null and availableEnvs lists alternatives.
|
|
104
177
|
*/
|
|
105
178
|
export function resolveEnv(envName) {
|
|
106
|
-
const
|
|
107
|
-
|
|
108
|
-
|
|
179
|
+
const dsEnvs = readDeepstormEnvironments();
|
|
180
|
+
if (!dsEnvs) {
|
|
181
|
+
const name = envName || 'test';
|
|
182
|
+
return { env: name, baseUrl: null, availableEnvs: [] };
|
|
183
|
+
}
|
|
109
184
|
|
|
185
|
+
const { envMap, defaultEnv } = deepstormEnvsToEnvMap(dsEnvs);
|
|
186
|
+
const name = envName || defaultEnv;
|
|
187
|
+
const availableEnvs = listEnvs();
|
|
110
188
|
const baseUrlKey = `BASE_URL_${name.toUpperCase()}`;
|
|
111
189
|
const baseUrl = envMap[baseUrlKey] || null;
|
|
112
|
-
|
|
113
190
|
return { env: name, baseUrl, availableEnvs };
|
|
114
191
|
}
|
|
115
192
|
|
|
@@ -167,9 +244,8 @@ if (process.argv[1] === import.meta.filename) {
|
|
|
167
244
|
const args = process.argv.slice(2);
|
|
168
245
|
|
|
169
246
|
if (args.includes('--list')) {
|
|
170
|
-
const
|
|
171
|
-
const
|
|
172
|
-
const defaultEnv = getDefaultEnv(envMap);
|
|
247
|
+
const envs = listEnvs();
|
|
248
|
+
const defaultEnv = getDefaultEnv();
|
|
173
249
|
console.log(JSON.stringify({ default: defaultEnv, environments: envs }));
|
|
174
250
|
process.exit(0);
|
|
175
251
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deepstorm/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.1",
|
|
4
4
|
"description": "DeepStorm CLI — 一键配置项目开发环境",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "billkang",
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"dotenv": "^17.4.2",
|
|
17
17
|
"handlebars": "^4.7.8",
|
|
18
18
|
"js-yaml": "^4.1.0",
|
|
19
|
-
"@deepstorm/pilot": "^0.
|
|
19
|
+
"@deepstorm/pilot": "^0.9.1"
|
|
20
20
|
},
|
|
21
21
|
"devDependencies": {
|
|
22
22
|
"@types/node": "^22.0.0",
|