@decantr/cli 1.7.25 → 1.7.27
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/bin.js +3 -3
- package/dist/{chunk-7FXMRAC3.js → chunk-DSVBYVG5.js} +152 -40
- package/dist/{chunk-3K6HWLD5.js → chunk-GCDFX7UE.js} +30 -4
- package/dist/chunk-RSIOCKZF.js +391 -0
- package/dist/heal-JPW3BXS3.js +99 -0
- package/dist/index.js +3 -3
- package/dist/{upgrade-KG42WK5C.js → upgrade-XMY6LIPS.js} +1 -1
- package/package.json +9 -5
- package/dist/chunk-QRQCPD3C.js +0 -135
- package/dist/heal-EMT5LYVZ.js +0 -175
package/dist/bin.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import "./chunk-
|
|
3
|
-
import "./chunk-
|
|
4
|
-
import "./chunk-
|
|
2
|
+
import "./chunk-DSVBYVG5.js";
|
|
3
|
+
import "./chunk-GCDFX7UE.js";
|
|
4
|
+
import "./chunk-RSIOCKZF.js";
|
|
@@ -14,11 +14,12 @@ import {
|
|
|
14
14
|
scaffoldProject,
|
|
15
15
|
syncRegistry,
|
|
16
16
|
writeExecutionPackBundleArtifacts
|
|
17
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-GCDFX7UE.js";
|
|
18
18
|
import {
|
|
19
19
|
buildGuardRegistryContext,
|
|
20
|
-
scanProjectInteractions
|
|
21
|
-
|
|
20
|
+
scanProjectInteractions,
|
|
21
|
+
sendCliCommandTelemetry
|
|
22
|
+
} from "./chunk-RSIOCKZF.js";
|
|
22
23
|
|
|
23
24
|
// src/index.ts
|
|
24
25
|
import { existsSync as existsSync27, mkdirSync as mkdirSync11, readdirSync as readdirSync7, readFileSync as readFileSync20, writeFileSync as writeFileSync14 } from "fs";
|
|
@@ -40,29 +41,63 @@ import {
|
|
|
40
41
|
} from "@decantr/verifier";
|
|
41
42
|
|
|
42
43
|
// src/auth.ts
|
|
43
|
-
import {
|
|
44
|
+
import {
|
|
45
|
+
chmodSync,
|
|
46
|
+
existsSync,
|
|
47
|
+
mkdirSync,
|
|
48
|
+
readFileSync,
|
|
49
|
+
rmSync,
|
|
50
|
+
statSync,
|
|
51
|
+
writeFileSync
|
|
52
|
+
} from "fs";
|
|
44
53
|
import { homedir } from "os";
|
|
45
54
|
import { join } from "path";
|
|
46
|
-
var
|
|
47
|
-
var
|
|
55
|
+
var CONFIG_DIR_MODE = 448;
|
|
56
|
+
var AUTH_FILE_MODE = 384;
|
|
57
|
+
function getConfigDir() {
|
|
58
|
+
return process.env.DECANTR_CONFIG_DIR || join(homedir(), ".config", "decantr");
|
|
59
|
+
}
|
|
60
|
+
function getAuthFile() {
|
|
61
|
+
return join(getConfigDir(), "auth.json");
|
|
62
|
+
}
|
|
63
|
+
function chmodIfNeeded(path, mode) {
|
|
64
|
+
try {
|
|
65
|
+
if ((statSync(path).mode & 511) !== mode) {
|
|
66
|
+
chmodSync(path, mode);
|
|
67
|
+
}
|
|
68
|
+
} catch {
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
function ensureConfigDir() {
|
|
72
|
+
const configDir = getConfigDir();
|
|
73
|
+
mkdirSync(configDir, { recursive: true, mode: CONFIG_DIR_MODE });
|
|
74
|
+
chmodIfNeeded(configDir, CONFIG_DIR_MODE);
|
|
75
|
+
}
|
|
48
76
|
function getCredentials() {
|
|
49
|
-
|
|
77
|
+
const authFile = getAuthFile();
|
|
78
|
+
if (!existsSync(authFile)) return null;
|
|
50
79
|
try {
|
|
51
|
-
|
|
80
|
+
chmodIfNeeded(authFile, AUTH_FILE_MODE);
|
|
81
|
+
return JSON.parse(readFileSync(authFile, "utf-8"));
|
|
52
82
|
} catch {
|
|
53
83
|
return null;
|
|
54
84
|
}
|
|
55
85
|
}
|
|
56
86
|
function saveCredentials(creds) {
|
|
57
|
-
|
|
58
|
-
|
|
87
|
+
ensureConfigDir();
|
|
88
|
+
const authFile = getAuthFile();
|
|
89
|
+
writeFileSync(authFile, JSON.stringify(creds, null, 2), { mode: AUTH_FILE_MODE });
|
|
90
|
+
chmodIfNeeded(authFile, AUTH_FILE_MODE);
|
|
59
91
|
}
|
|
60
92
|
function clearCredentials() {
|
|
61
|
-
|
|
62
|
-
|
|
93
|
+
const authFile = getAuthFile();
|
|
94
|
+
if (existsSync(authFile)) {
|
|
95
|
+
rmSync(authFile);
|
|
63
96
|
}
|
|
64
97
|
}
|
|
65
98
|
function getApiKeyOrToken() {
|
|
99
|
+
const envKey = process.env.DECANTR_API_KEY?.trim();
|
|
100
|
+
if (envKey) return envKey;
|
|
66
101
|
const creds = getCredentials();
|
|
67
102
|
if (!creds) return null;
|
|
68
103
|
return creds.api_key || creds.access_token || null;
|
|
@@ -341,7 +376,7 @@ import { existsSync as existsSync12, mkdirSync as mkdirSync3, writeFileSync as w
|
|
|
341
376
|
import { join as join12 } from "path";
|
|
342
377
|
|
|
343
378
|
// src/analyzers/components.ts
|
|
344
|
-
import { existsSync as existsSync4, readdirSync, statSync } from "fs";
|
|
379
|
+
import { existsSync as existsSync4, readdirSync, statSync as statSync2 } from "fs";
|
|
345
380
|
import { join as join4 } from "path";
|
|
346
381
|
var PAGE_EXTENSIONS = /* @__PURE__ */ new Set([".tsx", ".ts", ".jsx", ".js"]);
|
|
347
382
|
var ROOT_COMPONENT_CANDIDATES = [
|
|
@@ -366,7 +401,7 @@ function countFilesRecursive(dir, extensions) {
|
|
|
366
401
|
if (entry.startsWith(".") || entry === "node_modules") continue;
|
|
367
402
|
const fullPath = join4(dir, entry);
|
|
368
403
|
try {
|
|
369
|
-
const stat =
|
|
404
|
+
const stat = statSync2(fullPath);
|
|
370
405
|
if (stat.isDirectory()) {
|
|
371
406
|
count += countFilesRecursive(fullPath, extensions);
|
|
372
407
|
} else if (stat.isFile()) {
|
|
@@ -392,7 +427,7 @@ function countPageFiles(dir) {
|
|
|
392
427
|
if (entry.startsWith(".") || entry === "node_modules") continue;
|
|
393
428
|
const fullPath = join4(dir, entry);
|
|
394
429
|
try {
|
|
395
|
-
const stat =
|
|
430
|
+
const stat = statSync2(fullPath);
|
|
396
431
|
if (stat.isDirectory()) {
|
|
397
432
|
count += countPageFiles(fullPath);
|
|
398
433
|
} else if (stat.isFile()) {
|
|
@@ -632,7 +667,7 @@ function scanDependencies(projectRoot) {
|
|
|
632
667
|
}
|
|
633
668
|
|
|
634
669
|
// src/analyzers/features.ts
|
|
635
|
-
import { existsSync as existsSync6, readdirSync as readdirSync2, statSync as
|
|
670
|
+
import { existsSync as existsSync6, readdirSync as readdirSync2, statSync as statSync3 } from "fs";
|
|
636
671
|
import { join as join6 } from "path";
|
|
637
672
|
var FEATURE_PATTERNS = {
|
|
638
673
|
auth: [
|
|
@@ -690,7 +725,7 @@ function collectPaths(dir, baseDir, depth = 0) {
|
|
|
690
725
|
const relPath = fullPath.slice(baseDir.length + 1);
|
|
691
726
|
paths.push(relPath);
|
|
692
727
|
try {
|
|
693
|
-
if (
|
|
728
|
+
if (statSync3(fullPath).isDirectory()) {
|
|
694
729
|
paths.push(...collectPaths(fullPath, baseDir, depth + 1));
|
|
695
730
|
}
|
|
696
731
|
} catch {
|
|
@@ -861,7 +896,7 @@ function scanLayout(projectRoot) {
|
|
|
861
896
|
}
|
|
862
897
|
|
|
863
898
|
// src/analyzers/routes.ts
|
|
864
|
-
import { existsSync as existsSync8, readdirSync as readdirSync4, readFileSync as readFileSync6, statSync as
|
|
899
|
+
import { existsSync as existsSync8, readdirSync as readdirSync4, readFileSync as readFileSync6, statSync as statSync4 } from "fs";
|
|
865
900
|
import { join as join8, relative } from "path";
|
|
866
901
|
var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".next", ".git", "api", "_app", "_document"]);
|
|
867
902
|
function shouldSkipDir(name) {
|
|
@@ -910,7 +945,7 @@ function walkAppDir(dir, baseDir, segments) {
|
|
|
910
945
|
if (shouldSkipDir(entry)) continue;
|
|
911
946
|
const fullPath = join8(dir, entry);
|
|
912
947
|
try {
|
|
913
|
-
if (!
|
|
948
|
+
if (!statSync4(fullPath).isDirectory()) continue;
|
|
914
949
|
} catch {
|
|
915
950
|
continue;
|
|
916
951
|
}
|
|
@@ -932,7 +967,7 @@ function walkPagesDir(dir, baseDir, segments) {
|
|
|
932
967
|
if (shouldSkipDir(entry)) continue;
|
|
933
968
|
const fullPath = join8(dir, entry);
|
|
934
969
|
try {
|
|
935
|
-
const stat =
|
|
970
|
+
const stat = statSync4(fullPath);
|
|
936
971
|
if (stat.isDirectory()) {
|
|
937
972
|
const routeSegment = segmentToRoute(entry);
|
|
938
973
|
const nextSegments = routeSegment === null ? [...segments] : [...segments, routeSegment];
|
|
@@ -968,7 +1003,7 @@ function collectRouteCandidateFiles(dir, files, depth = 0) {
|
|
|
968
1003
|
if (entry.startsWith(".") || entry === "node_modules") continue;
|
|
969
1004
|
const fullPath = join8(dir, entry);
|
|
970
1005
|
try {
|
|
971
|
-
const stat =
|
|
1006
|
+
const stat = statSync4(fullPath);
|
|
972
1007
|
if (stat.isDirectory()) {
|
|
973
1008
|
collectRouteCandidateFiles(fullPath, files, depth + 1);
|
|
974
1009
|
} else if (stat.isFile()) {
|
|
@@ -2668,7 +2703,7 @@ async function cmdMigrate(projectRoot = process.cwd()) {
|
|
|
2668
2703
|
}
|
|
2669
2704
|
|
|
2670
2705
|
// src/commands/new-project.ts
|
|
2671
|
-
import {
|
|
2706
|
+
import { spawnSync } from "child_process";
|
|
2672
2707
|
import { existsSync as existsSync18, mkdirSync as mkdirSync8 } from "fs";
|
|
2673
2708
|
import { join as join19, resolve as resolve2 } from "path";
|
|
2674
2709
|
import { fileURLToPath } from "url";
|
|
@@ -3118,6 +3153,72 @@ function dim2(text) {
|
|
|
3118
3153
|
function cyan2(text) {
|
|
3119
3154
|
return `${CYAN3}${text}${RESET6}`;
|
|
3120
3155
|
}
|
|
3156
|
+
function validatePassThroughFlagValue(flag, value) {
|
|
3157
|
+
if (value.length === 0) {
|
|
3158
|
+
throw new Error(`--${flag} cannot be empty.`);
|
|
3159
|
+
}
|
|
3160
|
+
if (value.length > 512) {
|
|
3161
|
+
throw new Error(`--${flag} is too long.`);
|
|
3162
|
+
}
|
|
3163
|
+
if (/[\u0000-\u001f\u007f]/.test(value)) {
|
|
3164
|
+
throw new Error(`--${flag} contains unsupported control characters.`);
|
|
3165
|
+
}
|
|
3166
|
+
return value;
|
|
3167
|
+
}
|
|
3168
|
+
function pushPassThroughFlag(flags, flag, value) {
|
|
3169
|
+
if (value == null) return;
|
|
3170
|
+
flags.push(`--${flag}=${validatePassThroughFlagValue(flag, value)}`);
|
|
3171
|
+
}
|
|
3172
|
+
function buildNewProjectInitArgs(options, inferredAdoption) {
|
|
3173
|
+
const initFlags = [
|
|
3174
|
+
"--yes",
|
|
3175
|
+
"--workflow=greenfield",
|
|
3176
|
+
`--adoption=${validatePassThroughFlagValue("mode", inferredAdoption)}`
|
|
3177
|
+
];
|
|
3178
|
+
pushPassThroughFlag(initFlags, "blueprint", options.blueprint);
|
|
3179
|
+
pushPassThroughFlag(initFlags, "archetype", options.archetype);
|
|
3180
|
+
pushPassThroughFlag(initFlags, "theme", options.theme);
|
|
3181
|
+
pushPassThroughFlag(initFlags, "mode", options.mode);
|
|
3182
|
+
pushPassThroughFlag(initFlags, "shape", options.shape);
|
|
3183
|
+
pushPassThroughFlag(initFlags, "target", options.target);
|
|
3184
|
+
if (options.offline) initFlags.push("--offline");
|
|
3185
|
+
pushPassThroughFlag(initFlags, "registry", options.registry);
|
|
3186
|
+
pushPassThroughFlag(initFlags, "assistant-bridge", options.assistantBridge);
|
|
3187
|
+
return initFlags;
|
|
3188
|
+
}
|
|
3189
|
+
function commandForPlatform(command) {
|
|
3190
|
+
if (process.platform !== "win32") {
|
|
3191
|
+
return command;
|
|
3192
|
+
}
|
|
3193
|
+
return /^(?:npm|pnpm|yarn|bun|npx)$/.test(command) ? `${command}.cmd` : command;
|
|
3194
|
+
}
|
|
3195
|
+
function runArgvCommand(command, args, cwd) {
|
|
3196
|
+
const result = spawnSync(commandForPlatform(command), args, {
|
|
3197
|
+
cwd,
|
|
3198
|
+
stdio: "inherit",
|
|
3199
|
+
shell: false
|
|
3200
|
+
});
|
|
3201
|
+
if (result.error) {
|
|
3202
|
+
throw result.error;
|
|
3203
|
+
}
|
|
3204
|
+
if (result.status !== 0) {
|
|
3205
|
+
throw new Error(`Command failed: ${command} ${args.join(" ")}`);
|
|
3206
|
+
}
|
|
3207
|
+
}
|
|
3208
|
+
function resolveInitCommand(initFlags) {
|
|
3209
|
+
const bundledCliEntrypoint = fileURLToPath(new URL("./bin.js", import.meta.url));
|
|
3210
|
+
const cliEntrypoint = existsSync18(bundledCliEntrypoint) ? bundledCliEntrypoint : process.argv[1] && existsSync18(process.argv[1]) ? process.argv[1] : null;
|
|
3211
|
+
if (cliEntrypoint) {
|
|
3212
|
+
return {
|
|
3213
|
+
command: process.execPath,
|
|
3214
|
+
args: [cliEntrypoint, "init", ...initFlags]
|
|
3215
|
+
};
|
|
3216
|
+
}
|
|
3217
|
+
return {
|
|
3218
|
+
command: "npx",
|
|
3219
|
+
args: ["decantr", "init", ...initFlags]
|
|
3220
|
+
};
|
|
3221
|
+
}
|
|
3121
3222
|
async function cmdNewProject(projectName, options) {
|
|
3122
3223
|
const workspaceRoot = process.cwd();
|
|
3123
3224
|
const projectDir = resolve2(workspaceRoot, projectName);
|
|
@@ -3138,6 +3239,14 @@ async function cmdNewProject(projectName, options) {
|
|
|
3138
3239
|
process.exitCode = 1;
|
|
3139
3240
|
return;
|
|
3140
3241
|
}
|
|
3242
|
+
let initFlags;
|
|
3243
|
+
try {
|
|
3244
|
+
initFlags = buildNewProjectInitArgs(options, inferredAdoption);
|
|
3245
|
+
} catch (err) {
|
|
3246
|
+
console.error(error2(err.message));
|
|
3247
|
+
process.exitCode = 1;
|
|
3248
|
+
return;
|
|
3249
|
+
}
|
|
3141
3250
|
console.log(heading(`Creating ${projectName}...`));
|
|
3142
3251
|
mkdirSync8(projectDir, { recursive: true });
|
|
3143
3252
|
console.log(dim2(` Created ${projectName}/`));
|
|
@@ -3165,7 +3274,7 @@ async function cmdNewProject(projectName, options) {
|
|
|
3165
3274
|
if (shouldBootstrapRuntime) {
|
|
3166
3275
|
console.log(heading("Installing dependencies..."));
|
|
3167
3276
|
try {
|
|
3168
|
-
|
|
3277
|
+
runArgvCommand(packageManager, ["install"], projectDir);
|
|
3169
3278
|
} catch {
|
|
3170
3279
|
console.log(
|
|
3171
3280
|
`
|
|
@@ -3202,21 +3311,9 @@ ${YELLOW4}Dependency install failed. Run \`${packageManager} install\` manually.
|
|
|
3202
3311
|
return;
|
|
3203
3312
|
}
|
|
3204
3313
|
console.log(heading("Initializing Decantr..."));
|
|
3205
|
-
const initFlags = ["--yes", "--workflow=greenfield", `--adoption=${inferredAdoption}`];
|
|
3206
|
-
if (options.blueprint) initFlags.push(`--blueprint=${options.blueprint}`);
|
|
3207
|
-
if (options.archetype) initFlags.push(`--archetype=${options.archetype}`);
|
|
3208
|
-
if (options.theme) initFlags.push(`--theme=${options.theme}`);
|
|
3209
|
-
if (options.mode) initFlags.push(`--mode=${options.mode}`);
|
|
3210
|
-
if (options.shape) initFlags.push(`--shape=${options.shape}`);
|
|
3211
|
-
if (options.target) initFlags.push(`--target=${options.target}`);
|
|
3212
|
-
if (options.offline) initFlags.push("--offline");
|
|
3213
|
-
if (options.registry) initFlags.push(`--registry=${options.registry}`);
|
|
3214
|
-
if (options.assistantBridge) initFlags.push(`--assistant-bridge=${options.assistantBridge}`);
|
|
3215
3314
|
try {
|
|
3216
|
-
const
|
|
3217
|
-
|
|
3218
|
-
const cliPath = cliEntrypoint ? `"${process.execPath}" "${cliEntrypoint}"` : "npx decantr";
|
|
3219
|
-
execSync(`${cliPath} init ${initFlags.join(" ")}`, { cwd: projectDir, stdio: "inherit" });
|
|
3315
|
+
const initCommand = resolveInitCommand(initFlags);
|
|
3316
|
+
runArgvCommand(initCommand.command, initCommand.args, projectDir);
|
|
3220
3317
|
if (shouldBootstrapRuntime && bootstrapAdapter) {
|
|
3221
3318
|
bootstrapAdapter.writeProjectFiles(projectDir, title, detectRoutingMode(projectDir));
|
|
3222
3319
|
}
|
|
@@ -6578,7 +6675,7 @@ async function main() {
|
|
|
6578
6675
|
break;
|
|
6579
6676
|
}
|
|
6580
6677
|
case "upgrade": {
|
|
6581
|
-
const { cmdUpgrade } = await import("./upgrade-
|
|
6678
|
+
const { cmdUpgrade } = await import("./upgrade-XMY6LIPS.js");
|
|
6582
6679
|
const applyFlag = args.includes("--apply");
|
|
6583
6680
|
await cmdUpgrade(process.cwd(), { apply: applyFlag });
|
|
6584
6681
|
break;
|
|
@@ -6590,7 +6687,7 @@ async function main() {
|
|
|
6590
6687
|
`${YELLOW9}Note: \`decantr heal\` is deprecated. Use \`decantr check\` instead.${RESET13}`
|
|
6591
6688
|
);
|
|
6592
6689
|
}
|
|
6593
|
-
const { cmdHeal } = await import("./heal-
|
|
6690
|
+
const { cmdHeal } = await import("./heal-JPW3BXS3.js");
|
|
6594
6691
|
const telemetryFlag = args.includes("--telemetry");
|
|
6595
6692
|
await cmdHeal(process.cwd(), { telemetry: telemetryFlag });
|
|
6596
6693
|
break;
|
|
@@ -7076,8 +7173,23 @@ async function main() {
|
|
|
7076
7173
|
process.exitCode = 1;
|
|
7077
7174
|
}
|
|
7078
7175
|
}
|
|
7079
|
-
|
|
7176
|
+
var cliStartedAt = Date.now();
|
|
7177
|
+
var cliArgs = process.argv.slice(2);
|
|
7178
|
+
main().then(async () => {
|
|
7179
|
+
await sendCliCommandTelemetry({
|
|
7180
|
+
args: cliArgs,
|
|
7181
|
+
durationMs: Date.now() - cliStartedAt,
|
|
7182
|
+
projectRoot: process.cwd(),
|
|
7183
|
+
success: !process.exitCode || process.exitCode === 0
|
|
7184
|
+
});
|
|
7185
|
+
}).catch(async (e) => {
|
|
7080
7186
|
console.error(error3(e.message));
|
|
7081
7187
|
if (e.stack) console.error(e.stack);
|
|
7082
7188
|
process.exitCode = 1;
|
|
7189
|
+
await sendCliCommandTelemetry({
|
|
7190
|
+
args: cliArgs,
|
|
7191
|
+
durationMs: Date.now() - cliStartedAt,
|
|
7192
|
+
projectRoot: process.cwd(),
|
|
7193
|
+
success: false
|
|
7194
|
+
});
|
|
7083
7195
|
});
|
|
@@ -2422,6 +2422,18 @@ function generateTokensCSS(themeData, mode, spatialTokens, options) {
|
|
|
2422
2422
|
}
|
|
2423
2423
|
const seed = themeData.seed || {};
|
|
2424
2424
|
const palette = themeData.palette || {};
|
|
2425
|
+
const CORE_PALETTE_KEYS = /* @__PURE__ */ new Set([
|
|
2426
|
+
"background",
|
|
2427
|
+
"surface",
|
|
2428
|
+
"surface-raised",
|
|
2429
|
+
"border",
|
|
2430
|
+
"text",
|
|
2431
|
+
"text-muted",
|
|
2432
|
+
"primary",
|
|
2433
|
+
"primary-hover",
|
|
2434
|
+
"secondary",
|
|
2435
|
+
"accent"
|
|
2436
|
+
]);
|
|
2425
2437
|
const resolvedMode = mode === "auto" ? "dark" : mode;
|
|
2426
2438
|
const FALLBACKS = {
|
|
2427
2439
|
bg: { light: "#ffffff", dark: "#18181b" },
|
|
@@ -2439,11 +2451,19 @@ function generateTokensCSS(themeData, mode, spatialTokens, options) {
|
|
|
2439
2451
|
if (!fallbacks) return tokenModeKey === "light" ? "#ffffff" : "#18181b";
|
|
2440
2452
|
return fallbacks[tokenModeKey];
|
|
2441
2453
|
};
|
|
2454
|
+
const pickPalette = (key) => {
|
|
2455
|
+
const entry = palette[key];
|
|
2456
|
+
return entry?.[tokenMode] || entry?.[tokenModeKey] || entry?.dark || entry?.light;
|
|
2457
|
+
};
|
|
2458
|
+
const extendedPaletteTokens = Object.fromEntries(
|
|
2459
|
+
Object.keys(palette).filter((key) => !CORE_PALETTE_KEYS.has(key)).map((key) => [`--d-${key.replace(/[^a-zA-Z0-9-]/g, "-")}`, pickPalette(key)]).filter((entry) => typeof entry[1] === "string" && entry[1].length > 0)
|
|
2460
|
+
);
|
|
2442
2461
|
return {
|
|
2443
2462
|
// Seed colors
|
|
2444
|
-
"--d-primary": seed.primary || "#6366f1",
|
|
2445
|
-
"--d-secondary":
|
|
2446
|
-
"--d-accent": seed.accent || "#f59e0b",
|
|
2463
|
+
"--d-primary": pickPalette("primary") || seed.primary || "#6366f1",
|
|
2464
|
+
"--d-secondary": pickPalette("secondary") || seed.secondary || pickFb("secondary"),
|
|
2465
|
+
"--d-accent": pickPalette("accent") || seed.accent || "#f59e0b",
|
|
2466
|
+
...extendedPaletteTokens,
|
|
2447
2467
|
// Palette colors (mode-aware with mode-aware fallbacks)
|
|
2448
2468
|
"--d-bg": palette.background?.[tokenMode] || pickFb("bg"),
|
|
2449
2469
|
"--d-surface": palette.surface?.[tokenMode] || pickFb("surface"),
|
|
@@ -2513,12 +2533,17 @@ function generateTokensCSS(themeData, mode, spatialTokens, options) {
|
|
|
2513
2533
|
"--d-motion-ease-out": "cubic-bezier(0, 0, 0.2, 1)",
|
|
2514
2534
|
"--d-motion-ease-in": "cubic-bezier(0.4, 0, 1, 1)",
|
|
2515
2535
|
"--d-motion-ease-spring": "cubic-bezier(0.34, 1.56, 0.64, 1)",
|
|
2536
|
+
"--d-duration-hover": "var(--d-motion-fast)",
|
|
2537
|
+
"--d-duration-entrance": "var(--d-motion-base)",
|
|
2538
|
+
"--d-easing": "var(--d-motion-ease-out)",
|
|
2539
|
+
"--d-accent-glow": "color-mix(in srgb, var(--d-accent) 24%, transparent)",
|
|
2516
2540
|
// Typography scale (v2.1 Tier B2). Canonical sizes + weights +
|
|
2517
2541
|
// tracking + leading. d-display, d-headline, d-title, d-prose,
|
|
2518
2542
|
// d-caption, d-eyebrow read these. Themes override via
|
|
2519
2543
|
// theme.typography.* below.
|
|
2520
2544
|
"--d-font-body": "ui-sans-serif, system-ui, -apple-system, sans-serif",
|
|
2521
2545
|
"--d-font-display": "ui-sans-serif, system-ui, -apple-system, sans-serif",
|
|
2546
|
+
"--d-font-mono": "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace",
|
|
2522
2547
|
"--d-weight-regular": "400",
|
|
2523
2548
|
"--d-weight-medium": "500",
|
|
2524
2549
|
"--d-weight-semibold": "600",
|
|
@@ -2631,7 +2656,8 @@ ${lines}${spatialLines}
|
|
|
2631
2656
|
"--d-elevation-2",
|
|
2632
2657
|
"--d-elevation-3",
|
|
2633
2658
|
"--d-elevation-4",
|
|
2634
|
-
"--d-elevation-5"
|
|
2659
|
+
"--d-elevation-5",
|
|
2660
|
+
...Object.keys(palette).filter((key) => !CORE_PALETTE_KEYS.has(key)).map((key) => `--d-${key.replace(/[^a-zA-Z0-9-]/g, "-")}`)
|
|
2635
2661
|
];
|
|
2636
2662
|
if (mode === "auto") {
|
|
2637
2663
|
const lightTokens = buildTokens("light");
|
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
// src/lib/scan-interactions.ts
|
|
2
|
+
import { existsSync, readdirSync, readFileSync, statSync } from "fs";
|
|
3
|
+
import { extname, join } from "path";
|
|
4
|
+
import { verifyInteractionsInSource } from "@decantr/verifier";
|
|
5
|
+
var SCAN_EXTENSIONS = /* @__PURE__ */ new Set([".tsx", ".jsx", ".ts", ".js", ".html", ".mdx"]);
|
|
6
|
+
var SKIP_DIRECTORIES = /* @__PURE__ */ new Set([
|
|
7
|
+
"node_modules",
|
|
8
|
+
".decantr",
|
|
9
|
+
".git",
|
|
10
|
+
"dist",
|
|
11
|
+
"build",
|
|
12
|
+
".next",
|
|
13
|
+
".turbo",
|
|
14
|
+
"coverage",
|
|
15
|
+
".cache"
|
|
16
|
+
]);
|
|
17
|
+
var MAX_FILE_SIZE = 1024 * 1024;
|
|
18
|
+
function walkSourceTree(rootDir) {
|
|
19
|
+
const sources = /* @__PURE__ */ new Map();
|
|
20
|
+
function walk(dir) {
|
|
21
|
+
let entries;
|
|
22
|
+
try {
|
|
23
|
+
entries = readdirSync(dir);
|
|
24
|
+
} catch {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
for (const entry of entries) {
|
|
28
|
+
if (SKIP_DIRECTORIES.has(entry)) continue;
|
|
29
|
+
const fullPath = join(dir, entry);
|
|
30
|
+
let s;
|
|
31
|
+
try {
|
|
32
|
+
s = statSync(fullPath);
|
|
33
|
+
} catch {
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
if (s.isDirectory()) {
|
|
37
|
+
walk(fullPath);
|
|
38
|
+
} else if (s.isFile() && SCAN_EXTENSIONS.has(extname(entry))) {
|
|
39
|
+
if (s.size > MAX_FILE_SIZE) continue;
|
|
40
|
+
try {
|
|
41
|
+
sources.set(fullPath, readFileSync(fullPath, "utf8"));
|
|
42
|
+
} catch {
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
walk(rootDir);
|
|
48
|
+
return sources;
|
|
49
|
+
}
|
|
50
|
+
function collectDeclaredInteractions(projectRoot) {
|
|
51
|
+
const manifestPath = join(projectRoot, ".decantr", "context", "pack-manifest.json");
|
|
52
|
+
if (!existsSync(manifestPath)) return [];
|
|
53
|
+
let manifest;
|
|
54
|
+
try {
|
|
55
|
+
manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
|
|
56
|
+
} catch {
|
|
57
|
+
return [];
|
|
58
|
+
}
|
|
59
|
+
const all = [];
|
|
60
|
+
const pages = manifest.pages ?? [];
|
|
61
|
+
const contextDir = join(projectRoot, ".decantr", "context");
|
|
62
|
+
for (const page of pages) {
|
|
63
|
+
const packPath = join(contextDir, page.json);
|
|
64
|
+
if (!existsSync(packPath)) continue;
|
|
65
|
+
let pack;
|
|
66
|
+
try {
|
|
67
|
+
pack = JSON.parse(readFileSync(packPath, "utf8"));
|
|
68
|
+
} catch {
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
const patterns = pack.data?.patterns ?? [];
|
|
72
|
+
for (const pat of patterns) {
|
|
73
|
+
if (Array.isArray(pat.interactions)) {
|
|
74
|
+
all.push(...pat.interactions);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return all;
|
|
79
|
+
}
|
|
80
|
+
function scanProjectInteractions(projectRoot) {
|
|
81
|
+
const declared = collectDeclaredInteractions(projectRoot);
|
|
82
|
+
if (declared.length === 0) return [];
|
|
83
|
+
const sources = walkSourceTree(projectRoot);
|
|
84
|
+
if (sources.size === 0) return [];
|
|
85
|
+
const missing = verifyInteractionsInSource(declared, sources);
|
|
86
|
+
return missing.map(({ interaction, suggestion }) => `${interaction} \u2192 ${suggestion}`);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// src/guard-context.ts
|
|
90
|
+
import { existsSync as existsSync2, readdirSync as readdirSync2, readFileSync as readFileSync2 } from "fs";
|
|
91
|
+
import { join as join2 } from "path";
|
|
92
|
+
function loadJsonEntries(dir) {
|
|
93
|
+
if (!existsSync2(dir)) return [];
|
|
94
|
+
try {
|
|
95
|
+
return readdirSync2(dir).filter((file) => file.endsWith(".json") && file !== "index.json").map((file) => JSON.parse(readFileSync2(join2(dir, file), "utf-8")));
|
|
96
|
+
} catch {
|
|
97
|
+
return [];
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
function buildGuardRegistryContext(projectRoot = process.cwd()) {
|
|
101
|
+
const themeRegistry = /* @__PURE__ */ new Map();
|
|
102
|
+
const patternRegistry = /* @__PURE__ */ new Map();
|
|
103
|
+
const cacheDir = join2(projectRoot, ".decantr", "cache");
|
|
104
|
+
const customDir = join2(projectRoot, ".decantr", "custom");
|
|
105
|
+
for (const data of loadJsonEntries(join2(cacheDir, "@official", "themes"))) {
|
|
106
|
+
if (typeof data.id === "string" && !themeRegistry.has(data.id)) {
|
|
107
|
+
themeRegistry.set(data.id, {
|
|
108
|
+
modes: Array.isArray(data.modes) ? data.modes.filter((mode) => typeof mode === "string") : ["light", "dark"]
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
for (const data of loadJsonEntries(join2(customDir, "themes"))) {
|
|
113
|
+
if (typeof data.id === "string") {
|
|
114
|
+
themeRegistry.set(`custom:${data.id}`, {
|
|
115
|
+
modes: Array.isArray(data.modes) ? data.modes.filter((mode) => typeof mode === "string") : ["light", "dark"]
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
for (const data of loadJsonEntries(join2(cacheDir, "@official", "patterns"))) {
|
|
120
|
+
if (typeof data.id === "string" && !patternRegistry.has(data.id)) {
|
|
121
|
+
patternRegistry.set(data.id, data);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
for (const data of loadJsonEntries(join2(customDir, "patterns"))) {
|
|
125
|
+
if (typeof data.id === "string") {
|
|
126
|
+
patternRegistry.set(data.id, data);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return { themeRegistry, patternRegistry };
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// src/telemetry.ts
|
|
133
|
+
import { randomUUID } from "crypto";
|
|
134
|
+
import { existsSync as existsSync3, mkdirSync, readFileSync as readFileSync3, writeFileSync } from "fs";
|
|
135
|
+
import { homedir } from "os";
|
|
136
|
+
import { dirname, join as join3 } from "path";
|
|
137
|
+
import { fileURLToPath } from "url";
|
|
138
|
+
import {
|
|
139
|
+
createFetchTelemetrySink,
|
|
140
|
+
createTelemetryClient
|
|
141
|
+
} from "@decantr/telemetry";
|
|
142
|
+
var TELEMETRY_ENDPOINT = "https://api.decantr.ai/v1/telemetry/guard";
|
|
143
|
+
var DEFAULT_TELEMETRY_EVENTS_ENDPOINT = "https://api.decantr.ai/v1/telemetry/events";
|
|
144
|
+
var TELEMETRY_TIMEOUT_MS = 3e3;
|
|
145
|
+
var DNA_RULES = /* @__PURE__ */ new Set(["theme", "style", "density", "accessibility", "theme-mode"]);
|
|
146
|
+
async function sendGuardMetrics(metrics) {
|
|
147
|
+
try {
|
|
148
|
+
const controller = new AbortController();
|
|
149
|
+
const timer = setTimeout(() => controller.abort(), TELEMETRY_TIMEOUT_MS);
|
|
150
|
+
await fetch(TELEMETRY_ENDPOINT, {
|
|
151
|
+
method: "POST",
|
|
152
|
+
headers: { "Content-Type": "application/json" },
|
|
153
|
+
body: JSON.stringify(metrics),
|
|
154
|
+
signal: controller.signal
|
|
155
|
+
});
|
|
156
|
+
clearTimeout(timer);
|
|
157
|
+
} catch {
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
function isOptedIn(projectRoot) {
|
|
161
|
+
const projectJsonPath = join3(projectRoot, ".decantr", "project.json");
|
|
162
|
+
if (!existsSync3(projectJsonPath)) return false;
|
|
163
|
+
try {
|
|
164
|
+
const data = JSON.parse(readFileSync3(projectJsonPath, "utf-8"));
|
|
165
|
+
return data.telemetry === true;
|
|
166
|
+
} catch {
|
|
167
|
+
return false;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
function optIn(projectRoot) {
|
|
171
|
+
const projectJsonPath = join3(projectRoot, ".decantr", "project.json");
|
|
172
|
+
let data = {};
|
|
173
|
+
if (existsSync3(projectJsonPath)) {
|
|
174
|
+
try {
|
|
175
|
+
data = JSON.parse(readFileSync3(projectJsonPath, "utf-8"));
|
|
176
|
+
} catch {
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
data.telemetry = true;
|
|
180
|
+
mkdirSync(dirname(projectJsonPath), { recursive: true });
|
|
181
|
+
writeFileSync(projectJsonPath, JSON.stringify(data, null, 2) + "\n", "utf-8");
|
|
182
|
+
}
|
|
183
|
+
async function sendCliCommandTelemetry(input) {
|
|
184
|
+
const projectRoot = input.projectRoot ?? process.cwd();
|
|
185
|
+
const command = normalizeCommand(input.args[0]);
|
|
186
|
+
if (!isOptedIn(projectRoot) || !command || command === "help" || command === "version") {
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
const identities = ensureTelemetryIdentities(projectRoot);
|
|
190
|
+
if (!identities) {
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
const client = createTelemetryClient({
|
|
194
|
+
sink: createFetchTelemetrySink({
|
|
195
|
+
endpoint: getTelemetryEventsEndpoint(),
|
|
196
|
+
timeoutMs: TELEMETRY_TIMEOUT_MS
|
|
197
|
+
})
|
|
198
|
+
});
|
|
199
|
+
const event = {
|
|
200
|
+
name: "cli.command.completed",
|
|
201
|
+
context: {
|
|
202
|
+
source: "cli",
|
|
203
|
+
environment: "production",
|
|
204
|
+
decantrVersion: getCliVersion(),
|
|
205
|
+
installId: identities.installId,
|
|
206
|
+
projectId: identities.projectId,
|
|
207
|
+
registrySource: inferRegistrySource(input.args)
|
|
208
|
+
},
|
|
209
|
+
properties: {
|
|
210
|
+
command,
|
|
211
|
+
success: input.success,
|
|
212
|
+
durationMs: input.durationMs,
|
|
213
|
+
adoptionMode: inferAdoptionMode(input.args),
|
|
214
|
+
errorCode: input.success ? void 0 : "cli_command_failed",
|
|
215
|
+
offline: input.args.includes("--offline"),
|
|
216
|
+
projectScope: inferProjectScope(projectRoot),
|
|
217
|
+
registrySource: inferRegistrySource(input.args),
|
|
218
|
+
targetFramework: inferFlagValue(input.args, "--target"),
|
|
219
|
+
workflowMode: inferWorkflowMode(input.args)
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
try {
|
|
223
|
+
await client.capture(event);
|
|
224
|
+
} catch {
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
function collectMetrics(essence, issues) {
|
|
228
|
+
const dna = essence.dna ?? {};
|
|
229
|
+
const blueprint = essence.blueprint ?? {};
|
|
230
|
+
const meta = essence.meta ?? {};
|
|
231
|
+
const guard = meta.guard ?? {};
|
|
232
|
+
const theme = dna.theme ?? {};
|
|
233
|
+
const sections = blueprint.sections ?? [];
|
|
234
|
+
const routes = blueprint.routes ?? {};
|
|
235
|
+
const byRule = {};
|
|
236
|
+
let dnaCount = 0;
|
|
237
|
+
let blueprintCount = 0;
|
|
238
|
+
for (const issue of issues) {
|
|
239
|
+
byRule[issue.rule] = (byRule[issue.rule] ?? 0) + 1;
|
|
240
|
+
if (DNA_RULES.has(issue.rule)) {
|
|
241
|
+
dnaCount++;
|
|
242
|
+
} else {
|
|
243
|
+
blueprintCount++;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
return {
|
|
247
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
248
|
+
cli_version: getCliVersion(),
|
|
249
|
+
essence_version: essence.version ?? "unknown",
|
|
250
|
+
guard_mode: guard.mode ?? "unknown",
|
|
251
|
+
violations: {
|
|
252
|
+
dna: dnaCount,
|
|
253
|
+
blueprint: blueprintCount,
|
|
254
|
+
by_rule: byRule
|
|
255
|
+
},
|
|
256
|
+
resolution_rate: 0,
|
|
257
|
+
sections_count: sections.length,
|
|
258
|
+
routes_count: Object.keys(routes).length,
|
|
259
|
+
theme: theme.id ?? "unknown"
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
function ensureTelemetryIdentities(projectRoot) {
|
|
263
|
+
const installId = getOrCreateInstallId();
|
|
264
|
+
const projectJsonPath = join3(projectRoot, ".decantr", "project.json");
|
|
265
|
+
if (!existsSync3(projectJsonPath)) {
|
|
266
|
+
return null;
|
|
267
|
+
}
|
|
268
|
+
try {
|
|
269
|
+
const data = JSON.parse(readFileSync3(projectJsonPath, "utf-8"));
|
|
270
|
+
let projectId = typeof data.telemetryProjectId === "string" ? data.telemetryProjectId : void 0;
|
|
271
|
+
if (!projectId) {
|
|
272
|
+
projectId = `project_${randomUUID()}`;
|
|
273
|
+
data.telemetryProjectId = projectId;
|
|
274
|
+
writeFileSync(projectJsonPath, JSON.stringify(data, null, 2) + "\n", "utf-8");
|
|
275
|
+
}
|
|
276
|
+
return { installId, projectId };
|
|
277
|
+
} catch {
|
|
278
|
+
return null;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
function getOrCreateInstallId() {
|
|
282
|
+
const configDir = getConfigDir();
|
|
283
|
+
const configPath = join3(configDir, "config.json");
|
|
284
|
+
try {
|
|
285
|
+
if (existsSync3(configPath)) {
|
|
286
|
+
const data = JSON.parse(readFileSync3(configPath, "utf-8"));
|
|
287
|
+
if (typeof data.telemetryInstallId === "string") {
|
|
288
|
+
return data.telemetryInstallId;
|
|
289
|
+
}
|
|
290
|
+
const installId2 = `install_${randomUUID()}`;
|
|
291
|
+
data.telemetryInstallId = installId2;
|
|
292
|
+
writeFileSync(configPath, JSON.stringify(data, null, 2) + "\n", "utf-8");
|
|
293
|
+
return installId2;
|
|
294
|
+
}
|
|
295
|
+
mkdirSync(configDir, { recursive: true });
|
|
296
|
+
const installId = `install_${randomUUID()}`;
|
|
297
|
+
writeFileSync(
|
|
298
|
+
configPath,
|
|
299
|
+
JSON.stringify({ telemetryInstallId: installId }, null, 2) + "\n",
|
|
300
|
+
"utf-8"
|
|
301
|
+
);
|
|
302
|
+
return installId;
|
|
303
|
+
} catch {
|
|
304
|
+
return `install_${randomUUID()}`;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
function getConfigDir() {
|
|
308
|
+
return process.env.DECANTR_CONFIG_DIR || join3(homedir(), ".config", "decantr");
|
|
309
|
+
}
|
|
310
|
+
function getTelemetryEventsEndpoint() {
|
|
311
|
+
return process.env.DECANTR_TELEMETRY_ENDPOINT || DEFAULT_TELEMETRY_EVENTS_ENDPOINT;
|
|
312
|
+
}
|
|
313
|
+
function normalizeCommand(command) {
|
|
314
|
+
if (!command) return null;
|
|
315
|
+
if (command === "--help" || command === "-h") return "help";
|
|
316
|
+
if (command === "--version" || command === "-v") return "version";
|
|
317
|
+
return command;
|
|
318
|
+
}
|
|
319
|
+
function inferFlagValue(args, flag) {
|
|
320
|
+
const equalsPrefix = `${flag}=`;
|
|
321
|
+
const inline = args.find((arg) => arg.startsWith(equalsPrefix));
|
|
322
|
+
if (inline) {
|
|
323
|
+
return inline.slice(equalsPrefix.length) || void 0;
|
|
324
|
+
}
|
|
325
|
+
const index = args.indexOf(flag);
|
|
326
|
+
if (index !== -1 && args[index + 1] && !args[index + 1].startsWith("-")) {
|
|
327
|
+
return args[index + 1];
|
|
328
|
+
}
|
|
329
|
+
return void 0;
|
|
330
|
+
}
|
|
331
|
+
function inferAdoptionMode(args) {
|
|
332
|
+
const value = inferFlagValue(args, "--adoption");
|
|
333
|
+
if (value === "contract-only" || value === "decantr-css" || value === "style-bridge") {
|
|
334
|
+
return value;
|
|
335
|
+
}
|
|
336
|
+
return void 0;
|
|
337
|
+
}
|
|
338
|
+
function inferWorkflowMode(args) {
|
|
339
|
+
const value = inferFlagValue(args, "--workflow");
|
|
340
|
+
if (value === "greenfield" || value === "greenfield-scaffold") {
|
|
341
|
+
return "greenfield-scaffold";
|
|
342
|
+
}
|
|
343
|
+
if (value === "contract" || value === "greenfield-contract-only") {
|
|
344
|
+
return "greenfield-contract-only";
|
|
345
|
+
}
|
|
346
|
+
if (value === "brownfield" || value === "brownfield-attach") {
|
|
347
|
+
return "brownfield-attach";
|
|
348
|
+
}
|
|
349
|
+
if (value === "hybrid" || value === "hybrid-compose") {
|
|
350
|
+
return "hybrid-compose";
|
|
351
|
+
}
|
|
352
|
+
return void 0;
|
|
353
|
+
}
|
|
354
|
+
function inferRegistrySource(args) {
|
|
355
|
+
if (args.includes("--offline")) {
|
|
356
|
+
return "cache";
|
|
357
|
+
}
|
|
358
|
+
if (args.some((arg) => arg === "--registry" || arg.startsWith("--registry="))) {
|
|
359
|
+
return "custom";
|
|
360
|
+
}
|
|
361
|
+
return "official";
|
|
362
|
+
}
|
|
363
|
+
function inferProjectScope(projectRoot) {
|
|
364
|
+
return existsSync3(join3(projectRoot, "pnpm-workspace.yaml")) || existsSync3(join3(projectRoot, "turbo.json")) || existsSync3(join3(projectRoot, "lerna.json")) ? "workspace-app" : "single-app";
|
|
365
|
+
}
|
|
366
|
+
function getCliVersion() {
|
|
367
|
+
try {
|
|
368
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
369
|
+
const candidates = [join3(here, "..", "package.json"), join3(here, "..", "..", "package.json")];
|
|
370
|
+
for (const candidate of candidates) {
|
|
371
|
+
if (existsSync3(candidate)) {
|
|
372
|
+
const pkg = JSON.parse(readFileSync3(candidate, "utf-8"));
|
|
373
|
+
if (pkg.version) {
|
|
374
|
+
return pkg.version;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
} catch {
|
|
379
|
+
}
|
|
380
|
+
return "unknown";
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
export {
|
|
384
|
+
scanProjectInteractions,
|
|
385
|
+
buildGuardRegistryContext,
|
|
386
|
+
sendGuardMetrics,
|
|
387
|
+
isOptedIn,
|
|
388
|
+
optIn,
|
|
389
|
+
sendCliCommandTelemetry,
|
|
390
|
+
collectMetrics
|
|
391
|
+
};
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import {
|
|
2
|
+
buildGuardRegistryContext,
|
|
3
|
+
collectMetrics,
|
|
4
|
+
isOptedIn,
|
|
5
|
+
optIn,
|
|
6
|
+
scanProjectInteractions,
|
|
7
|
+
sendGuardMetrics
|
|
8
|
+
} from "./chunk-RSIOCKZF.js";
|
|
9
|
+
|
|
10
|
+
// src/commands/heal.ts
|
|
11
|
+
import { existsSync, readFileSync } from "fs";
|
|
12
|
+
import { join } from "path";
|
|
13
|
+
import { evaluateGuard, validateEssence } from "@decantr/essence-spec";
|
|
14
|
+
var GREEN = "\x1B[32m";
|
|
15
|
+
var RED = "\x1B[31m";
|
|
16
|
+
var YELLOW = "\x1B[33m";
|
|
17
|
+
var CYAN = "\x1B[36m";
|
|
18
|
+
var RESET = "\x1B[0m";
|
|
19
|
+
var DIM = "\x1B[2m";
|
|
20
|
+
async function cmdHeal(projectRoot = process.cwd(), options = {}) {
|
|
21
|
+
const essencePath = join(projectRoot, "decantr.essence.json");
|
|
22
|
+
if (!existsSync(essencePath)) {
|
|
23
|
+
console.error("No decantr.essence.json found. Run `decantr init` first.");
|
|
24
|
+
process.exitCode = 1;
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
const essence = JSON.parse(readFileSync(essencePath, "utf-8"));
|
|
28
|
+
console.log("Scanning for issues...\n");
|
|
29
|
+
const issues = [];
|
|
30
|
+
const validation = validateEssence(essence);
|
|
31
|
+
if (!validation.valid) {
|
|
32
|
+
for (const err of validation.errors) {
|
|
33
|
+
issues.push({
|
|
34
|
+
type: "error",
|
|
35
|
+
rule: "schema",
|
|
36
|
+
message: err
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
let interactionIssues = [];
|
|
41
|
+
try {
|
|
42
|
+
interactionIssues = scanProjectInteractions(projectRoot);
|
|
43
|
+
} catch {
|
|
44
|
+
}
|
|
45
|
+
try {
|
|
46
|
+
const guardContext = buildGuardRegistryContext(projectRoot);
|
|
47
|
+
const violations = evaluateGuard(essence, {
|
|
48
|
+
...guardContext,
|
|
49
|
+
interaction_issues: interactionIssues
|
|
50
|
+
});
|
|
51
|
+
for (const v of violations) {
|
|
52
|
+
issues.push({
|
|
53
|
+
type: v.severity === "error" ? "error" : "warning",
|
|
54
|
+
rule: v.rule,
|
|
55
|
+
message: v.message,
|
|
56
|
+
suggestion: v.suggestion
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
} catch {
|
|
60
|
+
}
|
|
61
|
+
if (issues.length === 0) {
|
|
62
|
+
console.log(`${GREEN}No issues found. Project is healthy.${RESET}`);
|
|
63
|
+
await maybeSendTelemetry(projectRoot, essence, issues, options);
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
console.log(`Found ${issues.length} issue(s):
|
|
67
|
+
`);
|
|
68
|
+
for (const issue of issues) {
|
|
69
|
+
const icon = issue.type === "error" ? `${RED}x${RESET}` : `${YELLOW}!${RESET}`;
|
|
70
|
+
console.log(`${icon} [${issue.rule}] ${issue.message}`);
|
|
71
|
+
if (issue.suggestion) {
|
|
72
|
+
console.log(` ${DIM}Suggestion: ${issue.suggestion}${RESET}`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
console.log(`
|
|
76
|
+
${YELLOW}Manual fixes required. Review the issues above.${RESET}`);
|
|
77
|
+
const hasError = issues.some((i) => i.type === "error");
|
|
78
|
+
if (hasError) {
|
|
79
|
+
process.exitCode = 1;
|
|
80
|
+
}
|
|
81
|
+
await maybeSendTelemetry(projectRoot, essence, issues, options);
|
|
82
|
+
}
|
|
83
|
+
async function maybeSendTelemetry(projectRoot, essence, issues, options) {
|
|
84
|
+
if (options.telemetry && !isOptedIn(projectRoot)) {
|
|
85
|
+
optIn(projectRoot);
|
|
86
|
+
console.log(
|
|
87
|
+
`
|
|
88
|
+
${CYAN}Telemetry enabled.${RESET} Anonymous guard metrics will be sent on future checks.`
|
|
89
|
+
);
|
|
90
|
+
console.log(`${DIM}Set "telemetry": false in .decantr/project.json to opt out.${RESET}`);
|
|
91
|
+
}
|
|
92
|
+
if (isOptedIn(projectRoot)) {
|
|
93
|
+
const metrics = collectMetrics(essence, issues);
|
|
94
|
+
sendGuardMetrics(metrics);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
export {
|
|
98
|
+
cmdHeal
|
|
99
|
+
};
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import "./chunk-
|
|
2
|
-
import "./chunk-
|
|
3
|
-
import "./chunk-
|
|
1
|
+
import "./chunk-DSVBYVG5.js";
|
|
2
|
+
import "./chunk-GCDFX7UE.js";
|
|
3
|
+
import "./chunk-RSIOCKZF.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@decantr/cli",
|
|
3
|
-
"version": "1.7.
|
|
3
|
+
"version": "1.7.27",
|
|
4
4
|
"description": "Decantr CLI — scaffold, audit, and maintain Decantr projects from the terminal",
|
|
5
5
|
"author": "Decantr AI",
|
|
6
6
|
"license": "MIT",
|
|
@@ -23,14 +23,18 @@
|
|
|
23
23
|
"src/templates",
|
|
24
24
|
"src/bundled"
|
|
25
25
|
],
|
|
26
|
+
"engines": {
|
|
27
|
+
"node": ">=20"
|
|
28
|
+
},
|
|
26
29
|
"publishConfig": {
|
|
27
30
|
"access": "public"
|
|
28
31
|
},
|
|
29
32
|
"dependencies": {
|
|
30
|
-
"@decantr/core": "1.0.
|
|
31
|
-
"@decantr/essence-spec": "1.0.
|
|
32
|
-
"@decantr/
|
|
33
|
-
"@decantr/verifier": "1.0.
|
|
33
|
+
"@decantr/core": "1.0.6",
|
|
34
|
+
"@decantr/essence-spec": "1.0.6",
|
|
35
|
+
"@decantr/telemetry": "0.1.0",
|
|
36
|
+
"@decantr/verifier": "1.0.5",
|
|
37
|
+
"@decantr/registry": "1.0.4"
|
|
34
38
|
},
|
|
35
39
|
"scripts": {
|
|
36
40
|
"build": "tsup",
|
package/dist/chunk-QRQCPD3C.js
DELETED
|
@@ -1,135 +0,0 @@
|
|
|
1
|
-
// src/lib/scan-interactions.ts
|
|
2
|
-
import { existsSync, readdirSync, readFileSync, statSync } from "fs";
|
|
3
|
-
import { extname, join } from "path";
|
|
4
|
-
import { verifyInteractionsInSource } from "@decantr/verifier";
|
|
5
|
-
var SCAN_EXTENSIONS = /* @__PURE__ */ new Set([".tsx", ".jsx", ".ts", ".js", ".html", ".mdx"]);
|
|
6
|
-
var SKIP_DIRECTORIES = /* @__PURE__ */ new Set([
|
|
7
|
-
"node_modules",
|
|
8
|
-
".decantr",
|
|
9
|
-
".git",
|
|
10
|
-
"dist",
|
|
11
|
-
"build",
|
|
12
|
-
".next",
|
|
13
|
-
".turbo",
|
|
14
|
-
"coverage",
|
|
15
|
-
".cache"
|
|
16
|
-
]);
|
|
17
|
-
var MAX_FILE_SIZE = 1024 * 1024;
|
|
18
|
-
function walkSourceTree(rootDir) {
|
|
19
|
-
const sources = /* @__PURE__ */ new Map();
|
|
20
|
-
function walk(dir) {
|
|
21
|
-
let entries;
|
|
22
|
-
try {
|
|
23
|
-
entries = readdirSync(dir);
|
|
24
|
-
} catch {
|
|
25
|
-
return;
|
|
26
|
-
}
|
|
27
|
-
for (const entry of entries) {
|
|
28
|
-
if (SKIP_DIRECTORIES.has(entry)) continue;
|
|
29
|
-
const fullPath = join(dir, entry);
|
|
30
|
-
let s;
|
|
31
|
-
try {
|
|
32
|
-
s = statSync(fullPath);
|
|
33
|
-
} catch {
|
|
34
|
-
continue;
|
|
35
|
-
}
|
|
36
|
-
if (s.isDirectory()) {
|
|
37
|
-
walk(fullPath);
|
|
38
|
-
} else if (s.isFile() && SCAN_EXTENSIONS.has(extname(entry))) {
|
|
39
|
-
if (s.size > MAX_FILE_SIZE) continue;
|
|
40
|
-
try {
|
|
41
|
-
sources.set(fullPath, readFileSync(fullPath, "utf8"));
|
|
42
|
-
} catch {
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
walk(rootDir);
|
|
48
|
-
return sources;
|
|
49
|
-
}
|
|
50
|
-
function collectDeclaredInteractions(projectRoot) {
|
|
51
|
-
const manifestPath = join(projectRoot, ".decantr", "context", "pack-manifest.json");
|
|
52
|
-
if (!existsSync(manifestPath)) return [];
|
|
53
|
-
let manifest;
|
|
54
|
-
try {
|
|
55
|
-
manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
|
|
56
|
-
} catch {
|
|
57
|
-
return [];
|
|
58
|
-
}
|
|
59
|
-
const all = [];
|
|
60
|
-
const pages = manifest.pages ?? [];
|
|
61
|
-
const contextDir = join(projectRoot, ".decantr", "context");
|
|
62
|
-
for (const page of pages) {
|
|
63
|
-
const packPath = join(contextDir, page.json);
|
|
64
|
-
if (!existsSync(packPath)) continue;
|
|
65
|
-
let pack;
|
|
66
|
-
try {
|
|
67
|
-
pack = JSON.parse(readFileSync(packPath, "utf8"));
|
|
68
|
-
} catch {
|
|
69
|
-
continue;
|
|
70
|
-
}
|
|
71
|
-
const patterns = pack.data?.patterns ?? [];
|
|
72
|
-
for (const pat of patterns) {
|
|
73
|
-
if (Array.isArray(pat.interactions)) {
|
|
74
|
-
all.push(...pat.interactions);
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
return all;
|
|
79
|
-
}
|
|
80
|
-
function scanProjectInteractions(projectRoot) {
|
|
81
|
-
const declared = collectDeclaredInteractions(projectRoot);
|
|
82
|
-
if (declared.length === 0) return [];
|
|
83
|
-
const sources = walkSourceTree(projectRoot);
|
|
84
|
-
if (sources.size === 0) return [];
|
|
85
|
-
const missing = verifyInteractionsInSource(declared, sources);
|
|
86
|
-
return missing.map(({ interaction, suggestion }) => `${interaction} \u2192 ${suggestion}`);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// src/guard-context.ts
|
|
90
|
-
import { existsSync as existsSync2, readdirSync as readdirSync2, readFileSync as readFileSync2 } from "fs";
|
|
91
|
-
import { join as join2 } from "path";
|
|
92
|
-
function loadJsonEntries(dir) {
|
|
93
|
-
if (!existsSync2(dir)) return [];
|
|
94
|
-
try {
|
|
95
|
-
return readdirSync2(dir).filter((file) => file.endsWith(".json") && file !== "index.json").map((file) => JSON.parse(readFileSync2(join2(dir, file), "utf-8")));
|
|
96
|
-
} catch {
|
|
97
|
-
return [];
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
function buildGuardRegistryContext(projectRoot = process.cwd()) {
|
|
101
|
-
const themeRegistry = /* @__PURE__ */ new Map();
|
|
102
|
-
const patternRegistry = /* @__PURE__ */ new Map();
|
|
103
|
-
const cacheDir = join2(projectRoot, ".decantr", "cache");
|
|
104
|
-
const customDir = join2(projectRoot, ".decantr", "custom");
|
|
105
|
-
for (const data of loadJsonEntries(join2(cacheDir, "@official", "themes"))) {
|
|
106
|
-
if (typeof data.id === "string" && !themeRegistry.has(data.id)) {
|
|
107
|
-
themeRegistry.set(data.id, {
|
|
108
|
-
modes: Array.isArray(data.modes) ? data.modes.filter((mode) => typeof mode === "string") : ["light", "dark"]
|
|
109
|
-
});
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
for (const data of loadJsonEntries(join2(customDir, "themes"))) {
|
|
113
|
-
if (typeof data.id === "string") {
|
|
114
|
-
themeRegistry.set(`custom:${data.id}`, {
|
|
115
|
-
modes: Array.isArray(data.modes) ? data.modes.filter((mode) => typeof mode === "string") : ["light", "dark"]
|
|
116
|
-
});
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
for (const data of loadJsonEntries(join2(cacheDir, "@official", "patterns"))) {
|
|
120
|
-
if (typeof data.id === "string" && !patternRegistry.has(data.id)) {
|
|
121
|
-
patternRegistry.set(data.id, data);
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
for (const data of loadJsonEntries(join2(customDir, "patterns"))) {
|
|
125
|
-
if (typeof data.id === "string") {
|
|
126
|
-
patternRegistry.set(data.id, data);
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
return { themeRegistry, patternRegistry };
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
export {
|
|
133
|
-
scanProjectInteractions,
|
|
134
|
-
buildGuardRegistryContext
|
|
135
|
-
};
|
package/dist/heal-EMT5LYVZ.js
DELETED
|
@@ -1,175 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
buildGuardRegistryContext,
|
|
3
|
-
scanProjectInteractions
|
|
4
|
-
} from "./chunk-QRQCPD3C.js";
|
|
5
|
-
|
|
6
|
-
// src/commands/heal.ts
|
|
7
|
-
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
|
|
8
|
-
import { join as join2 } from "path";
|
|
9
|
-
import { evaluateGuard, validateEssence } from "@decantr/essence-spec";
|
|
10
|
-
|
|
11
|
-
// src/telemetry.ts
|
|
12
|
-
import { existsSync, readFileSync, writeFileSync } from "fs";
|
|
13
|
-
import { join } from "path";
|
|
14
|
-
var TELEMETRY_ENDPOINT = "https://api.decantr.ai/v1/telemetry/guard";
|
|
15
|
-
var TELEMETRY_TIMEOUT_MS = 3e3;
|
|
16
|
-
var DNA_RULES = /* @__PURE__ */ new Set(["theme", "style", "density", "accessibility", "theme-mode"]);
|
|
17
|
-
async function sendGuardMetrics(metrics) {
|
|
18
|
-
try {
|
|
19
|
-
const controller = new AbortController();
|
|
20
|
-
const timer = setTimeout(() => controller.abort(), TELEMETRY_TIMEOUT_MS);
|
|
21
|
-
await fetch(TELEMETRY_ENDPOINT, {
|
|
22
|
-
method: "POST",
|
|
23
|
-
headers: { "Content-Type": "application/json" },
|
|
24
|
-
body: JSON.stringify(metrics),
|
|
25
|
-
signal: controller.signal
|
|
26
|
-
});
|
|
27
|
-
clearTimeout(timer);
|
|
28
|
-
} catch {
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
function isOptedIn(projectRoot) {
|
|
32
|
-
const projectJsonPath = join(projectRoot, ".decantr", "project.json");
|
|
33
|
-
if (!existsSync(projectJsonPath)) return false;
|
|
34
|
-
try {
|
|
35
|
-
const data = JSON.parse(readFileSync(projectJsonPath, "utf-8"));
|
|
36
|
-
return data.telemetry === true;
|
|
37
|
-
} catch {
|
|
38
|
-
return false;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
function optIn(projectRoot) {
|
|
42
|
-
const projectJsonPath = join(projectRoot, ".decantr", "project.json");
|
|
43
|
-
let data = {};
|
|
44
|
-
if (existsSync(projectJsonPath)) {
|
|
45
|
-
try {
|
|
46
|
-
data = JSON.parse(readFileSync(projectJsonPath, "utf-8"));
|
|
47
|
-
} catch {
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
data.telemetry = true;
|
|
51
|
-
writeFileSync(projectJsonPath, JSON.stringify(data, null, 2) + "\n", "utf-8");
|
|
52
|
-
}
|
|
53
|
-
function collectMetrics(essence, issues) {
|
|
54
|
-
const dna = essence.dna ?? {};
|
|
55
|
-
const blueprint = essence.blueprint ?? {};
|
|
56
|
-
const meta = essence.meta ?? {};
|
|
57
|
-
const guard = meta.guard ?? {};
|
|
58
|
-
const theme = dna.theme ?? {};
|
|
59
|
-
const sections = blueprint.sections ?? [];
|
|
60
|
-
const routes = blueprint.routes ?? {};
|
|
61
|
-
const byRule = {};
|
|
62
|
-
let dnaCount = 0;
|
|
63
|
-
let blueprintCount = 0;
|
|
64
|
-
for (const issue of issues) {
|
|
65
|
-
byRule[issue.rule] = (byRule[issue.rule] ?? 0) + 1;
|
|
66
|
-
if (DNA_RULES.has(issue.rule)) {
|
|
67
|
-
dnaCount++;
|
|
68
|
-
} else {
|
|
69
|
-
blueprintCount++;
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
return {
|
|
73
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
74
|
-
cli_version: "1.5.1",
|
|
75
|
-
essence_version: essence.version ?? "unknown",
|
|
76
|
-
guard_mode: guard.mode ?? "unknown",
|
|
77
|
-
violations: {
|
|
78
|
-
dna: dnaCount,
|
|
79
|
-
blueprint: blueprintCount,
|
|
80
|
-
by_rule: byRule
|
|
81
|
-
},
|
|
82
|
-
resolution_rate: 0,
|
|
83
|
-
sections_count: sections.length,
|
|
84
|
-
routes_count: Object.keys(routes).length,
|
|
85
|
-
theme: theme.id ?? "unknown"
|
|
86
|
-
};
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// src/commands/heal.ts
|
|
90
|
-
var GREEN = "\x1B[32m";
|
|
91
|
-
var RED = "\x1B[31m";
|
|
92
|
-
var YELLOW = "\x1B[33m";
|
|
93
|
-
var CYAN = "\x1B[36m";
|
|
94
|
-
var RESET = "\x1B[0m";
|
|
95
|
-
var DIM = "\x1B[2m";
|
|
96
|
-
async function cmdHeal(projectRoot = process.cwd(), options = {}) {
|
|
97
|
-
const essencePath = join2(projectRoot, "decantr.essence.json");
|
|
98
|
-
if (!existsSync2(essencePath)) {
|
|
99
|
-
console.error("No decantr.essence.json found. Run `decantr init` first.");
|
|
100
|
-
process.exitCode = 1;
|
|
101
|
-
return;
|
|
102
|
-
}
|
|
103
|
-
const essence = JSON.parse(readFileSync2(essencePath, "utf-8"));
|
|
104
|
-
console.log("Scanning for issues...\n");
|
|
105
|
-
const issues = [];
|
|
106
|
-
const validation = validateEssence(essence);
|
|
107
|
-
if (!validation.valid) {
|
|
108
|
-
for (const err of validation.errors) {
|
|
109
|
-
issues.push({
|
|
110
|
-
type: "error",
|
|
111
|
-
rule: "schema",
|
|
112
|
-
message: err
|
|
113
|
-
});
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
let interactionIssues = [];
|
|
117
|
-
try {
|
|
118
|
-
interactionIssues = scanProjectInteractions(projectRoot);
|
|
119
|
-
} catch {
|
|
120
|
-
}
|
|
121
|
-
try {
|
|
122
|
-
const guardContext = buildGuardRegistryContext(projectRoot);
|
|
123
|
-
const violations = evaluateGuard(essence, {
|
|
124
|
-
...guardContext,
|
|
125
|
-
interaction_issues: interactionIssues
|
|
126
|
-
});
|
|
127
|
-
for (const v of violations) {
|
|
128
|
-
issues.push({
|
|
129
|
-
type: v.severity === "error" ? "error" : "warning",
|
|
130
|
-
rule: v.rule,
|
|
131
|
-
message: v.message,
|
|
132
|
-
suggestion: v.suggestion
|
|
133
|
-
});
|
|
134
|
-
}
|
|
135
|
-
} catch {
|
|
136
|
-
}
|
|
137
|
-
if (issues.length === 0) {
|
|
138
|
-
console.log(`${GREEN}No issues found. Project is healthy.${RESET}`);
|
|
139
|
-
await maybeSendTelemetry(projectRoot, essence, issues, options);
|
|
140
|
-
return;
|
|
141
|
-
}
|
|
142
|
-
console.log(`Found ${issues.length} issue(s):
|
|
143
|
-
`);
|
|
144
|
-
for (const issue of issues) {
|
|
145
|
-
const icon = issue.type === "error" ? `${RED}x${RESET}` : `${YELLOW}!${RESET}`;
|
|
146
|
-
console.log(`${icon} [${issue.rule}] ${issue.message}`);
|
|
147
|
-
if (issue.suggestion) {
|
|
148
|
-
console.log(` ${DIM}Suggestion: ${issue.suggestion}${RESET}`);
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
console.log(`
|
|
152
|
-
${YELLOW}Manual fixes required. Review the issues above.${RESET}`);
|
|
153
|
-
const hasError = issues.some((i) => i.type === "error");
|
|
154
|
-
if (hasError) {
|
|
155
|
-
process.exitCode = 1;
|
|
156
|
-
}
|
|
157
|
-
await maybeSendTelemetry(projectRoot, essence, issues, options);
|
|
158
|
-
}
|
|
159
|
-
async function maybeSendTelemetry(projectRoot, essence, issues, options) {
|
|
160
|
-
if (options.telemetry && !isOptedIn(projectRoot)) {
|
|
161
|
-
optIn(projectRoot);
|
|
162
|
-
console.log(
|
|
163
|
-
`
|
|
164
|
-
${CYAN}Telemetry enabled.${RESET} Anonymous guard metrics will be sent on future checks.`
|
|
165
|
-
);
|
|
166
|
-
console.log(`${DIM}Set "telemetry": false in .decantr/project.json to opt out.${RESET}`);
|
|
167
|
-
}
|
|
168
|
-
if (isOptedIn(projectRoot)) {
|
|
169
|
-
const metrics = collectMetrics(essence, issues);
|
|
170
|
-
sendGuardMetrics(metrics);
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
export {
|
|
174
|
-
cmdHeal
|
|
175
|
-
};
|