@decantr/cli 2.8.1 → 2.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -15
- package/dist/bin.js +4 -3
- package/dist/{chunk-KT2ROK2D.js → chunk-34TZXWIF.js} +1858 -1542
- package/dist/{chunk-RKZMHS2K.js → chunk-N7A3WUZ2.js} +2972 -2004
- package/dist/{chunk-FV6DGYD7.js → chunk-T5INVSOP.js} +92 -18
- package/dist/{chunk-PAF4PBD3.js → chunk-TMOCTDYY.js} +28 -8
- package/dist/{content-health-QQHBR6XG.js → content-health-4KP2EGTI.js} +27 -10
- package/dist/{heal-ZYD6NVGE.js → heal-2BDT7TR5.js} +1 -2
- package/dist/{health-ETZXWGTW.js → health-Q7XF3I5Z.js} +2 -3
- package/dist/index.js +4 -3
- package/dist/{studio-G3YOU5YF.js → studio-B2Y4TKZ5.js} +3 -5
- package/dist/{workspace-U7J3CJY3.js → workspace-H5QQJCCI.js} +3 -5
- package/package.json +3 -3
- package/dist/chunk-3TH5PLFO.js +0 -331
- package/dist/chunk-VE6N3XWG.js +0 -78
package/dist/chunk-3TH5PLFO.js
DELETED
|
@@ -1,331 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
buildGuardRegistryContext,
|
|
3
|
-
collectMetrics,
|
|
4
|
-
createDoctrineMap,
|
|
5
|
-
isOptedIn,
|
|
6
|
-
optIn,
|
|
7
|
-
readDoctrineMap,
|
|
8
|
-
scanAmbientContext,
|
|
9
|
-
scanProjectInteractions,
|
|
10
|
-
scanRoutes,
|
|
11
|
-
scanStyling,
|
|
12
|
-
sendGuardMetrics
|
|
13
|
-
} from "./chunk-KT2ROK2D.js";
|
|
14
|
-
|
|
15
|
-
// src/commands/heal.ts
|
|
16
|
-
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
|
|
17
|
-
import { join as join2 } from "path";
|
|
18
|
-
import { evaluateGuard, isV4, validateEssence } from "@decantr/essence-spec";
|
|
19
|
-
|
|
20
|
-
// src/brownfield-check.ts
|
|
21
|
-
import { existsSync, readFileSync } from "fs";
|
|
22
|
-
import { join } from "path";
|
|
23
|
-
function readProjectJson(projectRoot) {
|
|
24
|
-
const path = join(projectRoot, ".decantr", "project.json");
|
|
25
|
-
if (!existsSync(path)) return {};
|
|
26
|
-
try {
|
|
27
|
-
return JSON.parse(readFileSync(path, "utf-8"));
|
|
28
|
-
} catch {
|
|
29
|
-
return {};
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
function essenceRoutes(essence) {
|
|
33
|
-
const fromRouteMap = Object.keys(essence.blueprint.routes ?? {});
|
|
34
|
-
const fromPages = essence.blueprint.sections?.flatMap(
|
|
35
|
-
(section) => section.pages.map((page) => page.route).filter((route) => Boolean(route))
|
|
36
|
-
) ?? essence.blueprint.pages?.map((page) => page.route).filter((route) => Boolean(route)) ?? [];
|
|
37
|
-
return /* @__PURE__ */ new Set([...fromRouteMap, ...fromPages]);
|
|
38
|
-
}
|
|
39
|
-
function routeLabel(routes) {
|
|
40
|
-
if (routes.length <= 6) return routes.join(", ");
|
|
41
|
-
return `${routes.slice(0, 6).join(", ")} (+${routes.length - 6} more)`;
|
|
42
|
-
}
|
|
43
|
-
function hasDoctrineEffect(essence, key) {
|
|
44
|
-
const effects = essence.dna.constraints?.effects;
|
|
45
|
-
return Boolean(effects && effects[key]);
|
|
46
|
-
}
|
|
47
|
-
function hasActionableDoctrineSource(doctrine, area) {
|
|
48
|
-
return doctrine.sources.some(
|
|
49
|
-
(source) => source.area === area && source.currency === "current" && source.safeToCite && source.confidence >= 0.72 && source.precedence >= 75
|
|
50
|
-
);
|
|
51
|
-
}
|
|
52
|
-
function hasAssistantBridge(projectRoot) {
|
|
53
|
-
const previewPath = join(projectRoot, ".decantr", "context", "assistant-bridge.md");
|
|
54
|
-
if (existsSync(previewPath)) return true;
|
|
55
|
-
const candidateFiles = [
|
|
56
|
-
"CLAUDE.md",
|
|
57
|
-
"AGENTS.md",
|
|
58
|
-
"GEMINI.md",
|
|
59
|
-
"copilot-instructions.md",
|
|
60
|
-
".github/copilot-instructions.md",
|
|
61
|
-
".cursorrules",
|
|
62
|
-
".windsurfrules",
|
|
63
|
-
".claude/rules/decantr.md",
|
|
64
|
-
".cursor/rules/decantr.mdc"
|
|
65
|
-
];
|
|
66
|
-
return candidateFiles.some((rel) => {
|
|
67
|
-
const path = join(projectRoot, rel);
|
|
68
|
-
if (!existsSync(path)) return false;
|
|
69
|
-
try {
|
|
70
|
-
return readFileSync(path, "utf-8").includes("decantr:assistant-bridge:start");
|
|
71
|
-
} catch {
|
|
72
|
-
return false;
|
|
73
|
-
}
|
|
74
|
-
});
|
|
75
|
-
}
|
|
76
|
-
function scanBrownfieldIssues(projectRoot, essence) {
|
|
77
|
-
const projectJson = readProjectJson(projectRoot);
|
|
78
|
-
const routes = scanRoutes(projectRoot);
|
|
79
|
-
const styling = scanStyling(projectRoot);
|
|
80
|
-
const ambient = scanAmbientContext(projectRoot);
|
|
81
|
-
const doctrine = readDoctrineMap(projectRoot) ?? createDoctrineMap(ambient);
|
|
82
|
-
const issues = [];
|
|
83
|
-
const declaredRoutes = essenceRoutes(essence);
|
|
84
|
-
const observedRoutes = new Set(routes.routes.map((route) => route.path));
|
|
85
|
-
const missingFromEssence = [...observedRoutes].filter((route) => !declaredRoutes.has(route));
|
|
86
|
-
const missingFromSource = [...declaredRoutes].filter((route) => !observedRoutes.has(route));
|
|
87
|
-
if (routes.routes.length > 0 && declaredRoutes.size === 0) {
|
|
88
|
-
issues.push({
|
|
89
|
-
type: "error",
|
|
90
|
-
rule: "brownfield-route-coverage",
|
|
91
|
-
message: `The app has ${routes.routes.length} observed route(s), but the Decantr essence declares no routes.`,
|
|
92
|
-
suggestion: "Run `decantr analyze`, review the proposal, then `decantr init --existing --accept-proposal` or `--merge-proposal`."
|
|
93
|
-
});
|
|
94
|
-
} else if (missingFromEssence.length > 0) {
|
|
95
|
-
issues.push({
|
|
96
|
-
type: "error",
|
|
97
|
-
rule: "brownfield-route-drift",
|
|
98
|
-
message: `Observed routes are missing from the Decantr contract: ${routeLabel(missingFromEssence)}.`,
|
|
99
|
-
suggestion: "Regenerate a brownfield proposal and merge the missing routes into the essence."
|
|
100
|
-
});
|
|
101
|
-
}
|
|
102
|
-
if (routes.routes.length > 0 && declaredRoutes.size === 1 && declaredRoutes.has("/") && routes.routes.length > 1) {
|
|
103
|
-
issues.push({
|
|
104
|
-
type: "error",
|
|
105
|
-
rule: "brownfield-generic-contract",
|
|
106
|
-
message: "The essence only declares `/` while the app has multiple observed routes.",
|
|
107
|
-
suggestion: "Accept or merge an observed brownfield proposal instead of using a generic scaffold contract."
|
|
108
|
-
});
|
|
109
|
-
}
|
|
110
|
-
if (missingFromSource.length > 0 && routes.routes.length > 0) {
|
|
111
|
-
issues.push({
|
|
112
|
-
type: "warning",
|
|
113
|
-
rule: "brownfield-stale-route",
|
|
114
|
-
message: `Essence routes were not observed in source: ${routeLabel(missingFromSource)}.`,
|
|
115
|
-
suggestion: "Confirm whether these are generated/dynamic routes or stale contract entries."
|
|
116
|
-
});
|
|
117
|
-
}
|
|
118
|
-
const adoptionMode = projectJson.initialized?.adoptionMode;
|
|
119
|
-
const themeId = essence.dna.theme.id;
|
|
120
|
-
if (adoptionMode === "contract-only" && themeId === "luminarum" && (styling.approach !== "unknown" || styling.cssVariables.length > 0)) {
|
|
121
|
-
issues.push({
|
|
122
|
-
type: "warning",
|
|
123
|
-
rule: "brownfield-theme-default",
|
|
124
|
-
message: "Contract-only brownfield essence still uses Decantr theme `luminarum` while the app has an existing styling system.",
|
|
125
|
-
suggestion: 'Use an observed proposal with `theme.id = "existing"` unless the user explicitly opts into a Decantr theme.'
|
|
126
|
-
});
|
|
127
|
-
}
|
|
128
|
-
for (const conflict of ambient.conflicts) {
|
|
129
|
-
issues.push({
|
|
130
|
-
type: "warning",
|
|
131
|
-
rule: "brownfield-doctrine-conflict",
|
|
132
|
-
message: conflict,
|
|
133
|
-
suggestion: "Resolve or document precedence before treating these rules as enforceable contract."
|
|
134
|
-
});
|
|
135
|
-
}
|
|
136
|
-
if (ambient.items.length === 0) {
|
|
137
|
-
issues.push({
|
|
138
|
-
type: "warning",
|
|
139
|
-
rule: "brownfield-context-missing",
|
|
140
|
-
message: "No ambient project context was detected for this brownfield check.",
|
|
141
|
-
suggestion: "Run `decantr analyze` to create `.decantr/ambient-context.json` and a proposal-backed report."
|
|
142
|
-
});
|
|
143
|
-
}
|
|
144
|
-
const hasBrownfieldArtifacts = Boolean(
|
|
145
|
-
projectJson.initialized?.workflowMode === "brownfield-attach"
|
|
146
|
-
);
|
|
147
|
-
if (hasBrownfieldArtifacts && !existsSync(join(projectRoot, ".decantr", "doctrine-map.json"))) {
|
|
148
|
-
issues.push({
|
|
149
|
-
type: "warning",
|
|
150
|
-
rule: "brownfield-doctrine-map-missing",
|
|
151
|
-
message: "Brownfield attach metadata exists, but `.decantr/doctrine-map.json` is missing.",
|
|
152
|
-
suggestion: "Run `decantr analyze` to regenerate ranked doctrine evidence."
|
|
153
|
-
});
|
|
154
|
-
}
|
|
155
|
-
if (hasActionableDoctrineSource(doctrine, "security-data") && !hasDoctrineEffect(essence, "doctrine-security-data")) {
|
|
156
|
-
issues.push({
|
|
157
|
-
type: "warning",
|
|
158
|
-
rule: "brownfield-doctrine-coverage",
|
|
159
|
-
message: "Security/data doctrine was detected, but the essence does not record a security/data preservation constraint.",
|
|
160
|
-
suggestion: "Regenerate and merge a brownfield proposal so security/data doctrine is represented in `dna.constraints.effects`."
|
|
161
|
-
});
|
|
162
|
-
}
|
|
163
|
-
if (hasActionableDoctrineSource(doctrine, "design-system") && !hasDoctrineEffect(essence, "doctrine-design-system")) {
|
|
164
|
-
issues.push({
|
|
165
|
-
type: "warning",
|
|
166
|
-
rule: "brownfield-doctrine-coverage",
|
|
167
|
-
message: "Design-system doctrine was detected, but the essence does not record a design-system preservation constraint.",
|
|
168
|
-
suggestion: "Regenerate and merge a brownfield proposal so design-system doctrine is represented in `dna.constraints.effects`."
|
|
169
|
-
});
|
|
170
|
-
}
|
|
171
|
-
if (styling.approach !== "unknown") {
|
|
172
|
-
const palette = String(essence.dna.color.palette ?? "");
|
|
173
|
-
const observedPalette = palette === "observed" || palette === styling.approach;
|
|
174
|
-
if (themeId === "existing" && !observedPalette) {
|
|
175
|
-
issues.push({
|
|
176
|
-
type: "warning",
|
|
177
|
-
rule: "brownfield-style-drift",
|
|
178
|
-
message: `Observed styling approach is ${styling.approach}, but the essence color palette is ${palette || "unset"}.`,
|
|
179
|
-
suggestion: "Regenerate and merge a brownfield proposal so the contract reflects the existing styling system."
|
|
180
|
-
});
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
if (ambient.items.some((item) => item.role === "assistant-specific") && hasBrownfieldArtifacts && !hasAssistantBridge(projectRoot)) {
|
|
184
|
-
issues.push({
|
|
185
|
-
type: "warning",
|
|
186
|
-
rule: "brownfield-assistant-bridge-missing",
|
|
187
|
-
message: "Assistant-specific rule files were detected, but no Decantr assistant bridge preview or applied bridge block was found.",
|
|
188
|
-
suggestion: "Run `decantr rules preview` first, then `decantr rules apply` if the user explicitly approves rule-file mutation."
|
|
189
|
-
});
|
|
190
|
-
}
|
|
191
|
-
const unsafeSources = doctrine.sources.filter((source) => !source.safeToCite);
|
|
192
|
-
if (unsafeSources.length > 0) {
|
|
193
|
-
issues.push({
|
|
194
|
-
type: "warning",
|
|
195
|
-
rule: "brownfield-unsafe-context",
|
|
196
|
-
message: `Some ambient context should not be cited directly: ${unsafeSources.slice(0, 4).map((source) => source.path).join(", ")}${unsafeSources.length > 4 ? ` (+${unsafeSources.length - 4} more)` : ""}.`,
|
|
197
|
-
suggestion: "Keep unsafe source paths in the inventory, but do not paste their contents into assistant context."
|
|
198
|
-
});
|
|
199
|
-
}
|
|
200
|
-
return issues;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
// src/commands/heal.ts
|
|
204
|
-
var GREEN = "\x1B[32m";
|
|
205
|
-
var RED = "\x1B[31m";
|
|
206
|
-
var YELLOW = "\x1B[33m";
|
|
207
|
-
var CYAN = "\x1B[36m";
|
|
208
|
-
var RESET = "\x1B[0m";
|
|
209
|
-
var DIM = "\x1B[2m";
|
|
210
|
-
function collectCheckIssues(projectRoot = process.cwd(), options = {}) {
|
|
211
|
-
const essencePath = join2(projectRoot, "decantr.essence.json");
|
|
212
|
-
if (!existsSync2(essencePath)) {
|
|
213
|
-
return {
|
|
214
|
-
essence: null,
|
|
215
|
-
issues: [
|
|
216
|
-
{
|
|
217
|
-
type: "error",
|
|
218
|
-
rule: "essence-missing",
|
|
219
|
-
message: "No decantr.essence.json found. Run `decantr init` first."
|
|
220
|
-
}
|
|
221
|
-
],
|
|
222
|
-
missingEssence: true
|
|
223
|
-
};
|
|
224
|
-
}
|
|
225
|
-
const essence = JSON.parse(readFileSync2(essencePath, "utf-8"));
|
|
226
|
-
const issues = [];
|
|
227
|
-
const validation = validateEssence(essence);
|
|
228
|
-
if (!validation.valid) {
|
|
229
|
-
for (const err of validation.errors) {
|
|
230
|
-
issues.push({
|
|
231
|
-
type: "error",
|
|
232
|
-
rule: "schema",
|
|
233
|
-
message: err
|
|
234
|
-
});
|
|
235
|
-
}
|
|
236
|
-
return { essence, issues, missingEssence: false };
|
|
237
|
-
}
|
|
238
|
-
let interactionIssues = [];
|
|
239
|
-
try {
|
|
240
|
-
interactionIssues = scanProjectInteractions(projectRoot);
|
|
241
|
-
} catch {
|
|
242
|
-
}
|
|
243
|
-
try {
|
|
244
|
-
const guardContext = buildGuardRegistryContext(projectRoot);
|
|
245
|
-
const violations = evaluateGuard(essence, {
|
|
246
|
-
...guardContext,
|
|
247
|
-
interaction_issues: interactionIssues
|
|
248
|
-
});
|
|
249
|
-
for (const v of violations) {
|
|
250
|
-
issues.push({
|
|
251
|
-
type: v.severity === "error" ? "error" : "warning",
|
|
252
|
-
rule: v.rule,
|
|
253
|
-
message: v.message,
|
|
254
|
-
suggestion: v.suggestion
|
|
255
|
-
});
|
|
256
|
-
}
|
|
257
|
-
} catch {
|
|
258
|
-
}
|
|
259
|
-
if (options.brownfield) {
|
|
260
|
-
try {
|
|
261
|
-
if (isV4(essence)) {
|
|
262
|
-
const brownfieldIssues = scanBrownfieldIssues(projectRoot, essence);
|
|
263
|
-
issues.push(...brownfieldIssues);
|
|
264
|
-
} else {
|
|
265
|
-
issues.push({
|
|
266
|
-
type: "warning",
|
|
267
|
-
rule: "brownfield-check",
|
|
268
|
-
message: "Brownfield checks require an Essence v4.0.0 Decantr contract."
|
|
269
|
-
});
|
|
270
|
-
}
|
|
271
|
-
} catch (e) {
|
|
272
|
-
issues.push({
|
|
273
|
-
type: "warning",
|
|
274
|
-
rule: "brownfield-check",
|
|
275
|
-
message: `Brownfield check could not complete: ${e.message}`
|
|
276
|
-
});
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
return { essence, issues, missingEssence: false };
|
|
280
|
-
}
|
|
281
|
-
async function cmdHeal(projectRoot = process.cwd(), options = {}) {
|
|
282
|
-
const result = collectCheckIssues(projectRoot, options);
|
|
283
|
-
console.log("Scanning for issues...\n");
|
|
284
|
-
if (result.missingEssence) {
|
|
285
|
-
console.error(result.issues[0]?.message ?? "No decantr.essence.json found.");
|
|
286
|
-
process.exitCode = 1;
|
|
287
|
-
return;
|
|
288
|
-
}
|
|
289
|
-
const issues = result.issues;
|
|
290
|
-
const essence = result.essence ?? {};
|
|
291
|
-
if (issues.length === 0) {
|
|
292
|
-
console.log(`${GREEN}No issues found. Project is healthy.${RESET}`);
|
|
293
|
-
await maybeSendTelemetry(projectRoot, essence, issues, options);
|
|
294
|
-
return;
|
|
295
|
-
}
|
|
296
|
-
console.log(`Found ${issues.length} issue(s):
|
|
297
|
-
`);
|
|
298
|
-
for (const issue of issues) {
|
|
299
|
-
const icon = issue.type === "error" ? `${RED}x${RESET}` : `${YELLOW}!${RESET}`;
|
|
300
|
-
console.log(`${icon} [${issue.rule}] ${issue.message}`);
|
|
301
|
-
if (issue.suggestion) {
|
|
302
|
-
console.log(` ${DIM}Suggestion: ${issue.suggestion}${RESET}`);
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
console.log(`
|
|
306
|
-
${YELLOW}Manual fixes required. Review the issues above.${RESET}`);
|
|
307
|
-
const hasError = issues.some((i) => i.type === "error");
|
|
308
|
-
if (hasError) {
|
|
309
|
-
process.exitCode = 1;
|
|
310
|
-
}
|
|
311
|
-
await maybeSendTelemetry(projectRoot, essence, issues, options);
|
|
312
|
-
}
|
|
313
|
-
async function maybeSendTelemetry(projectRoot, essence, issues, options) {
|
|
314
|
-
if (options.telemetry && !isOptedIn(projectRoot)) {
|
|
315
|
-
optIn(projectRoot);
|
|
316
|
-
console.log(
|
|
317
|
-
`
|
|
318
|
-
${CYAN}Telemetry enabled.${RESET} Decantr will send privacy-filtered CLI product telemetry for this project.`
|
|
319
|
-
);
|
|
320
|
-
console.log(`${DIM}Set "telemetry": false in .decantr/project.json to opt out.${RESET}`);
|
|
321
|
-
}
|
|
322
|
-
if (isOptedIn(projectRoot)) {
|
|
323
|
-
const metrics = collectMetrics(essence, issues);
|
|
324
|
-
sendGuardMetrics(metrics);
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
export {
|
|
329
|
-
collectCheckIssues,
|
|
330
|
-
cmdHeal
|
|
331
|
-
};
|
package/dist/chunk-VE6N3XWG.js
DELETED
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
// src/workspace.ts
|
|
2
|
-
import { existsSync, readdirSync, readFileSync } from "fs";
|
|
3
|
-
import { dirname, join, resolve } from "path";
|
|
4
|
-
function readPackageJson(dir) {
|
|
5
|
-
const path = join(dir, "package.json");
|
|
6
|
-
if (!existsSync(path)) return null;
|
|
7
|
-
try {
|
|
8
|
-
return JSON.parse(readFileSync(path, "utf-8"));
|
|
9
|
-
} catch {
|
|
10
|
-
return null;
|
|
11
|
-
}
|
|
12
|
-
}
|
|
13
|
-
function hasWorkspaceMarker(dir) {
|
|
14
|
-
if (existsSync(join(dir, "pnpm-workspace.yaml")) || existsSync(join(dir, "turbo.json")) || existsSync(join(dir, "nx.json"))) {
|
|
15
|
-
return true;
|
|
16
|
-
}
|
|
17
|
-
const pkg = readPackageJson(dir);
|
|
18
|
-
return Boolean(pkg?.workspaces);
|
|
19
|
-
}
|
|
20
|
-
function findWorkspaceRoot(startDir) {
|
|
21
|
-
let current = resolve(startDir);
|
|
22
|
-
while (true) {
|
|
23
|
-
if (hasWorkspaceMarker(current)) return current;
|
|
24
|
-
const parent = dirname(current);
|
|
25
|
-
if (parent === current) return null;
|
|
26
|
-
current = parent;
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
function looksLikeApp(dir, options = {}) {
|
|
30
|
-
const allowSourceDirs = options.allowSourceDirs ?? true;
|
|
31
|
-
if (existsSync(join(dir, "next.config.js")) || existsSync(join(dir, "next.config.ts")) || existsSync(join(dir, "next.config.mjs")) || existsSync(join(dir, "vite.config.ts")) || existsSync(join(dir, "vite.config.js")) || existsSync(join(dir, "angular.json")) || existsSync(join(dir, "svelte.config.js")) || existsSync(join(dir, "svelte.config.ts")) || existsSync(join(dir, "astro.config.mjs")) || allowSourceDirs && (existsSync(join(dir, "src")) || existsSync(join(dir, "app")) || existsSync(join(dir, "pages")))) {
|
|
32
|
-
return true;
|
|
33
|
-
}
|
|
34
|
-
const pkg = readPackageJson(dir);
|
|
35
|
-
const deps = { ...pkg?.dependencies, ...pkg?.devDependencies };
|
|
36
|
-
return Boolean(
|
|
37
|
-
deps.react || deps.next || deps.vue || deps.svelte || deps["@angular/core"] || deps.astro || deps.nuxt
|
|
38
|
-
);
|
|
39
|
-
}
|
|
40
|
-
function listWorkspaceApps(workspaceRoot) {
|
|
41
|
-
const candidates = [];
|
|
42
|
-
for (const base of ["apps", "packages"]) {
|
|
43
|
-
const baseDir = join(workspaceRoot, base);
|
|
44
|
-
if (!existsSync(baseDir)) continue;
|
|
45
|
-
for (const entry of readdirSync(baseDir, { withFileTypes: true })) {
|
|
46
|
-
if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
|
|
47
|
-
const candidate = join(baseDir, entry.name);
|
|
48
|
-
if (looksLikeApp(candidate, { allowSourceDirs: base === "apps" })) {
|
|
49
|
-
candidates.push(`${base}/${entry.name}`);
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
return candidates.sort();
|
|
54
|
-
}
|
|
55
|
-
function listWorkspaceAppCandidates(workspaceRoot) {
|
|
56
|
-
return listWorkspaceApps(resolve(workspaceRoot));
|
|
57
|
-
}
|
|
58
|
-
function resolveWorkspaceInfo(cwd, projectArg) {
|
|
59
|
-
const absoluteCwd = resolve(cwd);
|
|
60
|
-
const workspaceRoot = findWorkspaceRoot(absoluteCwd) ?? absoluteCwd;
|
|
61
|
-
const appRoot = projectArg ? resolve(absoluteCwd, projectArg) : absoluteCwd;
|
|
62
|
-
const appCandidates = listWorkspaceApps(workspaceRoot);
|
|
63
|
-
const projectScope = workspaceRoot !== appRoot || appCandidates.length > 0 ? "workspace-app" : "single-app";
|
|
64
|
-
const requiresProjectSelection = !projectArg && workspaceRoot === absoluteCwd && appCandidates.length > 0;
|
|
65
|
-
return {
|
|
66
|
-
cwd: absoluteCwd,
|
|
67
|
-
workspaceRoot,
|
|
68
|
-
appRoot,
|
|
69
|
-
projectScope,
|
|
70
|
-
appCandidates,
|
|
71
|
-
requiresProjectSelection
|
|
72
|
-
};
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
export {
|
|
76
|
-
listWorkspaceAppCandidates,
|
|
77
|
-
resolveWorkspaceInfo
|
|
78
|
-
};
|