@ai-driven-dev/cli 3.1.0 → 3.1.2
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 +1332 -725
- package/package.json +4 -1
package/dist/cli.js
CHANGED
|
@@ -71,7 +71,8 @@ var init_prompter_adapter = __esm({
|
|
|
71
71
|
choices: choices.map((c) => ({
|
|
72
72
|
name: c.name,
|
|
73
73
|
value: c.value,
|
|
74
|
-
disabled: c.disabled === true ? "Disabled" : c.disabled || false
|
|
74
|
+
disabled: c.disabled === true ? "Disabled" : c.disabled || false,
|
|
75
|
+
description: c.description
|
|
75
76
|
}))
|
|
76
77
|
},
|
|
77
78
|
this.context
|
|
@@ -133,6 +134,7 @@ var init_sync_status_use_case = __esm({
|
|
|
133
134
|
});
|
|
134
135
|
|
|
135
136
|
// src/cli.ts
|
|
137
|
+
import { spawn } from "child_process";
|
|
136
138
|
import { platform as platform2 } from "os";
|
|
137
139
|
import { Command as Command3 } from "commander";
|
|
138
140
|
|
|
@@ -1430,7 +1432,7 @@ var cursorToolConfig = {
|
|
|
1430
1432
|
toolSuffix: TOOL_SUFFIX3,
|
|
1431
1433
|
signalDir: ".cursor/commands",
|
|
1432
1434
|
rewriteContent(content, docsDir) {
|
|
1433
|
-
return baseRewriteContent(content, DIRECTORY3, docsDir).replace(/(
|
|
1435
|
+
return baseRewriteContent(content, DIRECTORY3, docsDir).replace(/(@?)\.cursor\/commands\/(\d+)[_-][^/]+\/([^\s]+)/g, "$1.cursor/commands/aidd/$2/$3").replace(/(@\.cursor\/rules\/[^\s]+)\.md\b/g, "$1.mdc");
|
|
1434
1436
|
},
|
|
1435
1437
|
reverseRewriteContent(content, docsDir) {
|
|
1436
1438
|
return baseReverseRewriteContent(
|
|
@@ -1541,6 +1543,14 @@ var ConfigConflictError = class extends Error {
|
|
|
1541
1543
|
this.name = "ConfigConflictError";
|
|
1542
1544
|
}
|
|
1543
1545
|
};
|
|
1546
|
+
var UpdateError = class extends Error {
|
|
1547
|
+
constructor() {
|
|
1548
|
+
super(
|
|
1549
|
+
"Update failed. If you saw a 403 error above, ensure your GitHub token includes both repo and read:packages scopes.\nUpdate your token at https://github.com/settings/tokens, then re-run `aidd auth login`."
|
|
1550
|
+
);
|
|
1551
|
+
this.name = "UpdateError";
|
|
1552
|
+
}
|
|
1553
|
+
};
|
|
1544
1554
|
|
|
1545
1555
|
// src/domain/tools/opencode.ts
|
|
1546
1556
|
var DIRECTORY4 = ".opencode/";
|
|
@@ -1558,10 +1568,10 @@ function transformMcpToOpencode(content) {
|
|
|
1558
1568
|
const mcp = {};
|
|
1559
1569
|
for (const [name, server] of Object.entries(parsed.mcpServers ?? {})) {
|
|
1560
1570
|
if ("command" in server) {
|
|
1561
|
-
const { command, args
|
|
1571
|
+
const { command, args = [], env } = server;
|
|
1562
1572
|
const local = {
|
|
1563
1573
|
type: "local",
|
|
1564
|
-
command: [command, ...
|
|
1574
|
+
command: [command, ...args],
|
|
1565
1575
|
enabled: true
|
|
1566
1576
|
};
|
|
1567
1577
|
if (env && Object.keys(env).length > 0) local.environment = env;
|
|
@@ -1588,8 +1598,8 @@ var opencodeToolConfig = {
|
|
|
1588
1598
|
signalDir: ".opencode/commands",
|
|
1589
1599
|
rewriteContent(content, docsDir) {
|
|
1590
1600
|
return baseRewriteContent(content, DIRECTORY4, docsDir).replace(
|
|
1591
|
-
/(
|
|
1592
|
-
"$
|
|
1601
|
+
/(@?)\.opencode\/commands\/(\d+)[_-][^/]+\/([^\s]+)/g,
|
|
1602
|
+
"$1.opencode/commands/aidd/$2/$3"
|
|
1593
1603
|
);
|
|
1594
1604
|
},
|
|
1595
1605
|
reverseRewriteContent(content, docsDir) {
|
|
@@ -1938,7 +1948,8 @@ function detectPackageManager() {
|
|
|
1938
1948
|
const whichCommand = platform() === "win32" ? "where aidd" : "which aidd";
|
|
1939
1949
|
let binaryPath;
|
|
1940
1950
|
try {
|
|
1941
|
-
|
|
1951
|
+
const raw = execSync2(whichCommand, { encoding: "utf8" });
|
|
1952
|
+
binaryPath = raw.trim().split(/\r?\n/)[0].trim();
|
|
1942
1953
|
} catch {
|
|
1943
1954
|
throw new Error(
|
|
1944
1955
|
`Could not detect package manager. Run manually:
|
|
@@ -1948,9 +1959,12 @@ function detectPackageManager() {
|
|
|
1948
1959
|
${PM_INSTALL_COMMANDS.bun}`
|
|
1949
1960
|
);
|
|
1950
1961
|
}
|
|
1951
|
-
|
|
1952
|
-
if (
|
|
1953
|
-
if (
|
|
1962
|
+
const normalised = binaryPath.replace(/\\/g, "/");
|
|
1963
|
+
if (normalised.includes("/pnpm/")) return { pm: "pnpm", binaryPath };
|
|
1964
|
+
if (normalised.includes("/.yarn/") || normalised.toLowerCase().includes("/yarn/bin/"))
|
|
1965
|
+
return { pm: "yarn", binaryPath };
|
|
1966
|
+
if (normalised.includes("/.bun/") || normalised.toLowerCase().includes("/bun/bin/"))
|
|
1967
|
+
return { pm: "bun", binaryPath };
|
|
1954
1968
|
return { pm: "npm", binaryPath };
|
|
1955
1969
|
}
|
|
1956
1970
|
function parseCliRelease(body, url) {
|
|
@@ -1978,7 +1992,11 @@ var CliUpdaterAdapter = class {
|
|
|
1978
1992
|
}
|
|
1979
1993
|
install() {
|
|
1980
1994
|
const { pm, binaryPath } = detectPackageManager();
|
|
1981
|
-
|
|
1995
|
+
try {
|
|
1996
|
+
execSync2(PM_INSTALL_COMMANDS[pm], { stdio: "inherit" });
|
|
1997
|
+
} catch {
|
|
1998
|
+
throw new UpdateError();
|
|
1999
|
+
}
|
|
1982
2000
|
return binaryPath;
|
|
1983
2001
|
}
|
|
1984
2002
|
};
|
|
@@ -1986,7 +2004,7 @@ var CliUpdaterAdapter = class {
|
|
|
1986
2004
|
// package.json
|
|
1987
2005
|
var package_default = {
|
|
1988
2006
|
name: "@ai-driven-dev/cli",
|
|
1989
|
-
version: "3.1.
|
|
2007
|
+
version: "3.1.2",
|
|
1990
2008
|
description: "AI-Driven Development CLI \u2014 distribute the AIDD framework across AI coding assistants",
|
|
1991
2009
|
type: "module",
|
|
1992
2010
|
main: "dist/cli.js",
|
|
@@ -2005,6 +2023,9 @@ var package_default = {
|
|
|
2005
2023
|
build: "tsup",
|
|
2006
2024
|
dev: "tsup --watch",
|
|
2007
2025
|
test: "pnpm build && vitest run",
|
|
2026
|
+
"test:unit": "vitest run --project=unit",
|
|
2027
|
+
"test:integration": "vitest run --project=integration",
|
|
2028
|
+
"test:e2e": "pnpm build && vitest run --project=e2e",
|
|
2008
2029
|
"test:watch": "vitest",
|
|
2009
2030
|
typecheck: "tsc --noEmit",
|
|
2010
2031
|
lint: "biome check .",
|
|
@@ -2142,7 +2163,7 @@ var FileSystemAdapter = class {
|
|
|
2142
2163
|
let existing = {};
|
|
2143
2164
|
try {
|
|
2144
2165
|
const raw = await readFile3(path, "utf-8");
|
|
2145
|
-
existing = JSON.parse(raw);
|
|
2166
|
+
existing = JSON.parse(stripJsoncComments(raw));
|
|
2146
2167
|
} catch (err) {
|
|
2147
2168
|
const code = err.code;
|
|
2148
2169
|
if (code !== "ENOENT") {
|
|
@@ -2208,6 +2229,14 @@ function stripJsoncComments(content) {
|
|
|
2208
2229
|
i += 2;
|
|
2209
2230
|
continue;
|
|
2210
2231
|
}
|
|
2232
|
+
if (ch === ",") {
|
|
2233
|
+
let j = i + 1;
|
|
2234
|
+
while (j < content.length && " \n\r".includes(content[j])) j++;
|
|
2235
|
+
if (content[j] === "}" || content[j] === "]") {
|
|
2236
|
+
i++;
|
|
2237
|
+
continue;
|
|
2238
|
+
}
|
|
2239
|
+
}
|
|
2211
2240
|
result += ch;
|
|
2212
2241
|
i++;
|
|
2213
2242
|
}
|
|
@@ -2592,7 +2621,16 @@ var TarExtractor = class {
|
|
|
2592
2621
|
};
|
|
2593
2622
|
|
|
2594
2623
|
// src/infrastructure/deps.ts
|
|
2624
|
+
var _cache = /* @__PURE__ */ new Map();
|
|
2625
|
+
function createMenuDeps(projectRoot) {
|
|
2626
|
+
return {
|
|
2627
|
+
manifestRepo: new ManifestRepositoryAdapter(projectRoot),
|
|
2628
|
+
prompter: process.stdout.isTTY ? new InquirerPrompterAdapter() : new SilentPrompterAdapter()
|
|
2629
|
+
};
|
|
2630
|
+
}
|
|
2595
2631
|
async function createDeps(projectRoot, options, output) {
|
|
2632
|
+
const cached = _cache.get(projectRoot);
|
|
2633
|
+
if (cached !== void 0) return cached;
|
|
2596
2634
|
const hasher = new HasherAdapter();
|
|
2597
2635
|
const fs = new FileSystemAdapter(hasher);
|
|
2598
2636
|
const loader = new FrameworkLoaderAdapter();
|
|
@@ -2627,7 +2665,7 @@ async function createDeps(projectRoot, options, output) {
|
|
|
2627
2665
|
const git = new GitAdapter(fs);
|
|
2628
2666
|
const platform3 = new PlatformAdapter();
|
|
2629
2667
|
const prompter = process.stdout.isTTY ? new InquirerPrompterAdapter() : new SilentPrompterAdapter();
|
|
2630
|
-
|
|
2668
|
+
const deps = {
|
|
2631
2669
|
fs,
|
|
2632
2670
|
manifestRepo,
|
|
2633
2671
|
loader,
|
|
@@ -2642,6 +2680,8 @@ async function createDeps(projectRoot, options, output) {
|
|
|
2642
2680
|
authReader,
|
|
2643
2681
|
authStorage
|
|
2644
2682
|
};
|
|
2683
|
+
_cache.set(projectRoot, deps);
|
|
2684
|
+
return deps;
|
|
2645
2685
|
}
|
|
2646
2686
|
|
|
2647
2687
|
// src/application/commands/cache.ts
|
|
@@ -3619,6 +3659,29 @@ ${invocation} ${SCRIPT_RELATIVE_PATH}
|
|
|
3619
3659
|
}
|
|
3620
3660
|
};
|
|
3621
3661
|
|
|
3662
|
+
// src/application/use-cases/shared/post-install-pipeline-use-case.ts
|
|
3663
|
+
var PostInstallPipelineUseCase = class {
|
|
3664
|
+
constructor(fs, manifestRepo, hasher, git) {
|
|
3665
|
+
this.fs = fs;
|
|
3666
|
+
this.manifestRepo = manifestRepo;
|
|
3667
|
+
this.hasher = hasher;
|
|
3668
|
+
this.git = git;
|
|
3669
|
+
}
|
|
3670
|
+
async execute(options) {
|
|
3671
|
+
const { projectRoot, version, descriptor, contentFiles, manifest, docsDir } = options;
|
|
3672
|
+
await new MemoryScriptUseCase(this.fs, this.hasher, this.git).execute({
|
|
3673
|
+
projectRoot,
|
|
3674
|
+
version,
|
|
3675
|
+
descriptor,
|
|
3676
|
+
contentFiles,
|
|
3677
|
+
manifest
|
|
3678
|
+
});
|
|
3679
|
+
await this.manifestRepo.save(manifest);
|
|
3680
|
+
await new CatalogUseCase(this.fs).execute({ manifest, docsDir, projectRoot });
|
|
3681
|
+
await new GitignoreUseCase(this.fs).execute(projectRoot, [".aidd/cache/"]);
|
|
3682
|
+
}
|
|
3683
|
+
};
|
|
3684
|
+
|
|
3622
3685
|
// src/application/use-cases/install-use-case.ts
|
|
3623
3686
|
var InstallUseCase = class {
|
|
3624
3687
|
constructor(fs, manifestRepo, loader, hasher, logger, git, platform3, prompter) {
|
|
@@ -3633,100 +3696,115 @@ var InstallUseCase = class {
|
|
|
3633
3696
|
}
|
|
3634
3697
|
async execute(options) {
|
|
3635
3698
|
const { frameworkPath, version, projectRoot, force = false, repo } = options;
|
|
3636
|
-
const interactive = options.interactive ?? false;
|
|
3637
3699
|
const manifest = await this.manifestRepo.load();
|
|
3638
|
-
if (manifest === null)
|
|
3639
|
-
throw new NoManifestError(repo);
|
|
3640
|
-
}
|
|
3700
|
+
if (manifest === null) throw new NoManifestError(repo);
|
|
3641
3701
|
const docsDir = options.docsDir ?? manifest.docsDir;
|
|
3642
|
-
|
|
3643
|
-
if (options.all) {
|
|
3644
|
-
toolIds = [...VALID_TOOL_IDS];
|
|
3645
|
-
} else if (options.toolIds !== void 0 && options.toolIds.length > 0) {
|
|
3646
|
-
toolIds = options.toolIds;
|
|
3647
|
-
} else if (interactive && this.prompter !== void 0) {
|
|
3648
|
-
const installedIds = manifest.getInstalledToolIds();
|
|
3649
|
-
const choices = VALID_TOOL_IDS.map(
|
|
3650
|
-
(id) => installedIds.includes(id) ? { name: id, value: id, checked: true, disabled: "(already installed)" } : { name: id, value: id, checked: false }
|
|
3651
|
-
);
|
|
3652
|
-
const selected = await this.prompter.checkbox("Which tools do you want to install?", choices);
|
|
3653
|
-
if (selected.length === 0) throw new Error("No tools selected.");
|
|
3654
|
-
toolIds = selected;
|
|
3655
|
-
} else {
|
|
3656
|
-
throw new Error(
|
|
3657
|
-
`At least one tool ID is required. Valid tools: ${VALID_TOOL_IDS.join(", ")}`
|
|
3658
|
-
);
|
|
3659
|
-
}
|
|
3702
|
+
const toolIds = await this.resolveToolIds(options, manifest);
|
|
3660
3703
|
assertValidToolIds(toolIds);
|
|
3661
3704
|
const { descriptor, contentFiles } = await this.loader.loadFromDirectory(
|
|
3662
3705
|
frameworkPath,
|
|
3663
3706
|
version
|
|
3664
3707
|
);
|
|
3708
|
+
const results = await this.installAllTools(
|
|
3709
|
+
toolIds,
|
|
3710
|
+
manifest,
|
|
3711
|
+
descriptor,
|
|
3712
|
+
contentFiles,
|
|
3713
|
+
docsDir,
|
|
3714
|
+
projectRoot,
|
|
3715
|
+
force
|
|
3716
|
+
);
|
|
3717
|
+
await new PostInstallPipelineUseCase(this.fs, this.manifestRepo, this.hasher, this.git).execute(
|
|
3718
|
+
{ projectRoot, version, descriptor, contentFiles, manifest, docsDir }
|
|
3719
|
+
);
|
|
3720
|
+
return results;
|
|
3721
|
+
}
|
|
3722
|
+
async installAllTools(toolIds, manifest, descriptor, contentFiles, docsDir, projectRoot, force) {
|
|
3665
3723
|
const results = [];
|
|
3666
3724
|
for (const toolId of toolIds) {
|
|
3667
|
-
|
|
3668
|
-
|
|
3669
|
-
|
|
3670
|
-
}
|
|
3671
|
-
const config = getToolConfig(toolId);
|
|
3672
|
-
const warnings = [];
|
|
3673
|
-
if (!manifest.hasTool(toolId) && force) {
|
|
3674
|
-
const toolDir = join19(projectRoot, config.directory);
|
|
3675
|
-
if (await this.fs.fileExists(toolDir)) {
|
|
3676
|
-
warnings.push(
|
|
3677
|
-
`Directory ${config.directory} exists but tool is not in manifest. Files will be overwritten.`
|
|
3678
|
-
);
|
|
3679
|
-
}
|
|
3680
|
-
}
|
|
3681
|
-
this.logger.info(`Generating ${toolId} distribution...`);
|
|
3682
|
-
const generated = await generateDistribution(
|
|
3725
|
+
const result = await this.installOneTool(
|
|
3726
|
+
toolId,
|
|
3727
|
+
manifest,
|
|
3683
3728
|
descriptor,
|
|
3684
|
-
config,
|
|
3685
|
-
docsDir,
|
|
3686
3729
|
contentFiles,
|
|
3687
|
-
|
|
3688
|
-
this.platform,
|
|
3730
|
+
docsDir,
|
|
3689
3731
|
projectRoot,
|
|
3690
|
-
|
|
3732
|
+
force
|
|
3691
3733
|
);
|
|
3692
|
-
|
|
3693
|
-
|
|
3694
|
-
|
|
3695
|
-
|
|
3696
|
-
|
|
3697
|
-
|
|
3698
|
-
|
|
3699
|
-
|
|
3700
|
-
|
|
3701
|
-
|
|
3702
|
-
|
|
3703
|
-
|
|
3734
|
+
results.push(result);
|
|
3735
|
+
}
|
|
3736
|
+
return results;
|
|
3737
|
+
}
|
|
3738
|
+
/** Resolves which tool IDs to install from the 4-branch selection logic. */
|
|
3739
|
+
async resolveToolIds(options, manifest) {
|
|
3740
|
+
const interactive = options.interactive ?? false;
|
|
3741
|
+
if (options.all) return [...VALID_TOOL_IDS];
|
|
3742
|
+
if (options.toolIds !== void 0 && options.toolIds.length > 0) return options.toolIds;
|
|
3743
|
+
if (interactive && this.prompter !== void 0) return this.promptToolIds(manifest);
|
|
3744
|
+
throw new Error(`At least one tool ID is required. Valid tools: ${VALID_TOOL_IDS.join(", ")}`);
|
|
3745
|
+
}
|
|
3746
|
+
async promptToolIds(manifest) {
|
|
3747
|
+
if (this.prompter === void 0) throw new Error("Prompter is required for interactive mode.");
|
|
3748
|
+
const installedIds = manifest.getInstalledToolIds();
|
|
3749
|
+
const choices = VALID_TOOL_IDS.map(
|
|
3750
|
+
(id) => installedIds.includes(id) ? { name: id, value: id, checked: true, disabled: "(already installed)" } : { name: id, value: id, checked: false }
|
|
3751
|
+
);
|
|
3752
|
+
const selected = await this.prompter.checkbox("Which tools do you want to install?", choices);
|
|
3753
|
+
if (selected.length === 0) throw new Error("No tools selected.");
|
|
3754
|
+
return selected;
|
|
3755
|
+
}
|
|
3756
|
+
/** Installs a single tool and updates the manifest in place. */
|
|
3757
|
+
async installOneTool(toolId, manifest, descriptor, contentFiles, docsDir, projectRoot, force) {
|
|
3758
|
+
if (manifest.hasTool(toolId) && !force) {
|
|
3759
|
+
return { toolId, fileCount: 0, files: [], skipped: true, warnings: [] };
|
|
3760
|
+
}
|
|
3761
|
+
const config = getToolConfig(toolId);
|
|
3762
|
+
const warnings = await this.checkForceWarning(toolId, config, manifest, projectRoot, force);
|
|
3763
|
+
this.logger.info(`Generating ${toolId} distribution...`);
|
|
3764
|
+
const generated = await generateDistribution(
|
|
3765
|
+
descriptor,
|
|
3766
|
+
config,
|
|
3767
|
+
docsDir,
|
|
3768
|
+
contentFiles,
|
|
3769
|
+
this.hasher,
|
|
3770
|
+
this.platform,
|
|
3771
|
+
projectRoot,
|
|
3772
|
+
this.fs
|
|
3773
|
+
);
|
|
3774
|
+
await this.removeStaleFiles(toolId, manifest, generated, projectRoot);
|
|
3775
|
+
const { files: finalFiles, userFileConflicts } = await this.writeToolFiles(
|
|
3776
|
+
generated,
|
|
3777
|
+
projectRoot,
|
|
3778
|
+
manifest
|
|
3779
|
+
);
|
|
3780
|
+
for (const relativePath of userFileConflicts) {
|
|
3781
|
+
warnings.push(
|
|
3782
|
+
`\`${relativePath}\` already exists and was not installed by AIDD \u2014 skipped to preserve user file`
|
|
3704
3783
|
);
|
|
3705
|
-
|
|
3784
|
+
}
|
|
3785
|
+
manifest.addTool(toolId, descriptor.version, finalFiles);
|
|
3786
|
+
return { toolId, fileCount: generated.length, files: generated, skipped: false, warnings };
|
|
3787
|
+
}
|
|
3788
|
+
async checkForceWarning(toolId, config, manifest, projectRoot, force) {
|
|
3789
|
+
const warnings = [];
|
|
3790
|
+
if (!manifest.hasTool(toolId) && force) {
|
|
3791
|
+
const toolDir = join19(projectRoot, config.directory);
|
|
3792
|
+
if (await this.fs.fileExists(toolDir)) {
|
|
3706
3793
|
warnings.push(
|
|
3707
|
-
|
|
3794
|
+
`Directory ${config.directory} exists but tool is not in manifest. Files will be overwritten.`
|
|
3708
3795
|
);
|
|
3709
3796
|
}
|
|
3710
|
-
manifest.addTool(toolId, descriptor.version, finalFiles);
|
|
3711
|
-
results.push({
|
|
3712
|
-
toolId,
|
|
3713
|
-
fileCount: generated.length,
|
|
3714
|
-
files: generated,
|
|
3715
|
-
skipped: false,
|
|
3716
|
-
warnings
|
|
3717
|
-
});
|
|
3718
3797
|
}
|
|
3719
|
-
|
|
3720
|
-
|
|
3721
|
-
|
|
3722
|
-
|
|
3723
|
-
|
|
3724
|
-
|
|
3725
|
-
|
|
3726
|
-
|
|
3727
|
-
|
|
3728
|
-
|
|
3729
|
-
return results;
|
|
3798
|
+
return warnings;
|
|
3799
|
+
}
|
|
3800
|
+
async removeStaleFiles(toolId, manifest, generated, projectRoot) {
|
|
3801
|
+
if (!manifest.hasTool(toolId)) return;
|
|
3802
|
+
const newPaths = new Set(generated.map((f) => f.relativePath));
|
|
3803
|
+
for (const oldFile of manifest.getToolFiles(toolId)) {
|
|
3804
|
+
if (!newPaths.has(oldFile.relativePath)) {
|
|
3805
|
+
await this.fs.deleteFile(join19(projectRoot, oldFile.relativePath));
|
|
3806
|
+
}
|
|
3807
|
+
}
|
|
3730
3808
|
}
|
|
3731
3809
|
async writeToolFiles(generated, projectRoot, manifest) {
|
|
3732
3810
|
const filesByPath = /* @__PURE__ */ new Map();
|
|
@@ -3922,14 +4000,14 @@ function buildDocsDistribution(docsFiles, docsDir, hasher) {
|
|
|
3922
4000
|
|
|
3923
4001
|
// src/application/use-cases/restore-use-case.ts
|
|
3924
4002
|
var RestoreUseCase = class {
|
|
3925
|
-
constructor(fs, manifestRepo, loader, hasher, logger,
|
|
4003
|
+
constructor(fs, manifestRepo, loader, hasher, logger, platform3, prompter) {
|
|
3926
4004
|
this.fs = fs;
|
|
3927
4005
|
this.manifestRepo = manifestRepo;
|
|
3928
4006
|
this.loader = loader;
|
|
3929
4007
|
this.hasher = hasher;
|
|
3930
4008
|
this.logger = logger;
|
|
3931
|
-
this.prompter = prompter;
|
|
3932
4009
|
this.platform = platform3;
|
|
4010
|
+
this.prompter = prompter;
|
|
3933
4011
|
}
|
|
3934
4012
|
async execute(options) {
|
|
3935
4013
|
const {
|
|
@@ -3942,67 +4020,136 @@ var RestoreUseCase = class {
|
|
|
3942
4020
|
repo
|
|
3943
4021
|
} = options;
|
|
3944
4022
|
const docsOnly = options.docsOnly ?? false;
|
|
3945
|
-
const fileFilter = buildFileFilter(options.files);
|
|
3946
4023
|
const manifest = options.manifest ?? await this.manifestRepo.load();
|
|
3947
4024
|
if (manifest === null) throw new NoManifestError(repo);
|
|
3948
|
-
const toolIds = docsOnly ? [] : options.toolIds && options.toolIds.length > 0 ? options.toolIds : manifest.getInstalledToolIds();
|
|
3949
4025
|
const { descriptor, contentFiles, docsFiles } = await this.loader.loadFromDirectory(
|
|
3950
4026
|
frameworkPath,
|
|
3951
4027
|
version
|
|
3952
4028
|
);
|
|
4029
|
+
const fileFilter = buildFileFilter(options.files);
|
|
4030
|
+
return this.executeRestore({
|
|
4031
|
+
options,
|
|
4032
|
+
docsOnly,
|
|
4033
|
+
manifest,
|
|
4034
|
+
descriptor,
|
|
4035
|
+
contentFiles,
|
|
4036
|
+
docsFiles,
|
|
4037
|
+
docsDir,
|
|
4038
|
+
projectRoot,
|
|
4039
|
+
version,
|
|
4040
|
+
force,
|
|
4041
|
+
interactive,
|
|
4042
|
+
fileFilter
|
|
4043
|
+
});
|
|
4044
|
+
}
|
|
4045
|
+
async executeRestore(ctx) {
|
|
4046
|
+
const {
|
|
4047
|
+
options,
|
|
4048
|
+
docsOnly,
|
|
4049
|
+
manifest,
|
|
4050
|
+
descriptor,
|
|
4051
|
+
contentFiles,
|
|
4052
|
+
docsFiles,
|
|
4053
|
+
docsDir,
|
|
4054
|
+
projectRoot,
|
|
4055
|
+
version,
|
|
4056
|
+
force,
|
|
4057
|
+
interactive,
|
|
4058
|
+
fileFilter
|
|
4059
|
+
} = ctx;
|
|
4060
|
+
const toolIds = this.resolveToolIds(options, docsOnly, manifest);
|
|
4061
|
+
const toolResults = await this.restoreAllTools(
|
|
4062
|
+
toolIds,
|
|
4063
|
+
manifest,
|
|
4064
|
+
descriptor,
|
|
4065
|
+
contentFiles,
|
|
4066
|
+
docsDir,
|
|
4067
|
+
projectRoot,
|
|
4068
|
+
version,
|
|
4069
|
+
force,
|
|
4070
|
+
interactive,
|
|
4071
|
+
fileFilter
|
|
4072
|
+
);
|
|
4073
|
+
const hasExplicitToolFilter = !docsOnly && options.toolIds !== void 0 && options.toolIds.length > 0;
|
|
4074
|
+
const docsResult = hasExplicitToolFilter ? null : await this.restoreDocs(
|
|
4075
|
+
manifest,
|
|
4076
|
+
docsFiles,
|
|
4077
|
+
docsDir,
|
|
4078
|
+
projectRoot,
|
|
4079
|
+
version,
|
|
4080
|
+
force,
|
|
4081
|
+
interactive,
|
|
4082
|
+
fileFilter
|
|
4083
|
+
);
|
|
4084
|
+
const hasChanges = toolResults.some((t) => t.restored.length > 0) || docsResult !== null && docsResult.restored.length > 0;
|
|
4085
|
+
if (hasChanges) await this.manifestRepo.save(manifest);
|
|
4086
|
+
return this.buildRestoreTotals(toolResults, docsResult);
|
|
4087
|
+
}
|
|
4088
|
+
resolveToolIds(options, docsOnly, manifest) {
|
|
4089
|
+
if (docsOnly) return [];
|
|
4090
|
+
return options.toolIds?.length ? options.toolIds : manifest.getInstalledToolIds();
|
|
4091
|
+
}
|
|
4092
|
+
async restoreAllTools(toolIds, manifest, descriptor, contentFiles, docsDir, projectRoot, version, force, interactive, fileFilter) {
|
|
3953
4093
|
const toolResults = [];
|
|
3954
4094
|
for (const toolId of toolIds) {
|
|
3955
|
-
|
|
3956
|
-
|
|
3957
|
-
|
|
3958
|
-
const distribution = await generateDistribution(
|
|
4095
|
+
const result = await this.restoreOneTool(
|
|
4096
|
+
toolId,
|
|
4097
|
+
manifest,
|
|
3959
4098
|
descriptor,
|
|
3960
|
-
config,
|
|
3961
|
-
docsDir,
|
|
3962
4099
|
contentFiles,
|
|
3963
|
-
|
|
3964
|
-
this.platform,
|
|
3965
|
-
projectRoot,
|
|
3966
|
-
this.fs
|
|
3967
|
-
);
|
|
3968
|
-
const distMap = new Map(distribution.map((f) => [f.relativePath, f]));
|
|
3969
|
-
const drift = await this.collectDrift(manifestFiles, distMap, projectRoot, fileFilter);
|
|
3970
|
-
if (drift.length === 0) {
|
|
3971
|
-
toolResults.push({ toolId, nothingToRestore: true, restored: [], kept: [] });
|
|
3972
|
-
continue;
|
|
3973
|
-
}
|
|
3974
|
-
const { restored, kept, updatedHashMap } = await this.applyRestorations(
|
|
3975
|
-
drift,
|
|
3976
|
-
new Map(manifestFiles.map((f) => [f.relativePath, f.hash])),
|
|
4100
|
+
docsDir,
|
|
3977
4101
|
projectRoot,
|
|
4102
|
+
version,
|
|
3978
4103
|
force,
|
|
3979
|
-
interactive
|
|
4104
|
+
interactive,
|
|
4105
|
+
fileFilter
|
|
3980
4106
|
);
|
|
3981
|
-
|
|
3982
|
-
toolId,
|
|
3983
|
-
manifest.getToolVersion(toolId) ?? version,
|
|
3984
|
-
Array.from(updatedHashMap.entries()).map(
|
|
3985
|
-
([relativePath, hash]) => new GeneratedFile({ relativePath, content: "", hash })
|
|
3986
|
-
)
|
|
3987
|
-
);
|
|
3988
|
-
toolResults.push({ toolId, nothingToRestore: false, restored, kept });
|
|
4107
|
+
toolResults.push(result);
|
|
3989
4108
|
}
|
|
3990
|
-
|
|
3991
|
-
|
|
3992
|
-
|
|
3993
|
-
|
|
4109
|
+
return toolResults;
|
|
4110
|
+
}
|
|
4111
|
+
async restoreOneTool(toolId, manifest, descriptor, contentFiles, docsDir, projectRoot, version, force, interactive, fileFilter) {
|
|
4112
|
+
this.logger.info(`Checking ${toolId} for files to restore...`);
|
|
4113
|
+
const config = getToolConfig(toolId);
|
|
4114
|
+
const manifestFiles = manifest.getToolFiles(toolId);
|
|
4115
|
+
const distribution = await generateDistribution(
|
|
4116
|
+
descriptor,
|
|
4117
|
+
config,
|
|
3994
4118
|
docsDir,
|
|
4119
|
+
contentFiles,
|
|
4120
|
+
this.hasher,
|
|
4121
|
+
this.platform,
|
|
4122
|
+
projectRoot,
|
|
4123
|
+
this.fs
|
|
4124
|
+
);
|
|
4125
|
+
const distMap = new Map(distribution.map((f) => [f.relativePath, f]));
|
|
4126
|
+
const section = await this.restoreSection(
|
|
4127
|
+
manifestFiles,
|
|
4128
|
+
distMap,
|
|
3995
4129
|
projectRoot,
|
|
3996
|
-
version,
|
|
3997
4130
|
force,
|
|
3998
4131
|
interactive,
|
|
3999
4132
|
fileFilter
|
|
4000
4133
|
);
|
|
4001
|
-
|
|
4002
|
-
|
|
4003
|
-
|
|
4004
|
-
|
|
4005
|
-
|
|
4134
|
+
if (section === null) return { toolId, nothingToRestore: true, restored: [], kept: [] };
|
|
4135
|
+
manifest.addTool(toolId, manifest.getToolVersion(toolId) ?? version, section.updatedFiles);
|
|
4136
|
+
return { toolId, nothingToRestore: false, restored: section.restored, kept: section.kept };
|
|
4137
|
+
}
|
|
4138
|
+
/** Shared restoration logic for both tool files and docs files. Returns null when nothing to restore. */
|
|
4139
|
+
async restoreSection(manifestFiles, distMap, projectRoot, force, interactive, fileFilter) {
|
|
4140
|
+
const drift = await this.collectDrift(manifestFiles, distMap, projectRoot, fileFilter);
|
|
4141
|
+
if (drift.length === 0) return null;
|
|
4142
|
+
const { restored, kept, updatedHashMap } = await this.applyRestorations(
|
|
4143
|
+
drift,
|
|
4144
|
+
new Map(manifestFiles.map((f) => [f.relativePath, f.hash])),
|
|
4145
|
+
projectRoot,
|
|
4146
|
+
force,
|
|
4147
|
+
interactive
|
|
4148
|
+
);
|
|
4149
|
+
const updatedFiles = Array.from(updatedHashMap.entries()).map(
|
|
4150
|
+
([relativePath, hash]) => new GeneratedFile({ relativePath, content: "", hash })
|
|
4151
|
+
);
|
|
4152
|
+
return { restored, kept, updatedFiles };
|
|
4006
4153
|
}
|
|
4007
4154
|
async restoreDocs(manifest, docsFiles, docsDir, projectRoot, version, force, interactive, fileFilter) {
|
|
4008
4155
|
const docsManifestFiles = manifest.getDocsFiles();
|
|
@@ -4011,22 +4158,22 @@ var RestoreUseCase = class {
|
|
|
4011
4158
|
this.logger.info("Checking docs for files to restore...");
|
|
4012
4159
|
const distribution = buildDocsDistribution(docsFiles, docsDir, this.hasher);
|
|
4013
4160
|
const distMap = new Map(distribution.map((f) => [f.relativePath, f]));
|
|
4014
|
-
const
|
|
4015
|
-
|
|
4016
|
-
|
|
4017
|
-
drift,
|
|
4018
|
-
new Map(docsManifestFiles.map((f) => [f.relativePath, f.hash])),
|
|
4161
|
+
const section = await this.restoreSection(
|
|
4162
|
+
docsManifestFiles,
|
|
4163
|
+
distMap,
|
|
4019
4164
|
projectRoot,
|
|
4020
4165
|
force,
|
|
4021
|
-
interactive
|
|
4022
|
-
|
|
4023
|
-
manifest.addDocs(
|
|
4024
|
-
manifest.getDocsVersion() ?? version,
|
|
4025
|
-
Array.from(updatedHashMap.entries()).map(
|
|
4026
|
-
([relativePath, hash]) => new GeneratedFile({ relativePath, content: "", hash })
|
|
4027
|
-
)
|
|
4166
|
+
interactive,
|
|
4167
|
+
fileFilter
|
|
4028
4168
|
);
|
|
4029
|
-
return { nothingToRestore:
|
|
4169
|
+
if (section === null) return { nothingToRestore: true, restored: [], kept: [] };
|
|
4170
|
+
manifest.addDocs(manifest.getDocsVersion() ?? version, section.updatedFiles);
|
|
4171
|
+
return { nothingToRestore: false, restored: section.restored, kept: section.kept };
|
|
4172
|
+
}
|
|
4173
|
+
buildRestoreTotals(toolResults, docsResult) {
|
|
4174
|
+
const totalRestored = toolResults.reduce((s, t) => s + t.restored.length, 0) + (docsResult?.restored.length ?? 0);
|
|
4175
|
+
const totalKept = toolResults.reduce((s, t) => s + t.kept.length, 0) + (docsResult?.kept.length ?? 0);
|
|
4176
|
+
return { tools: toolResults, docs: docsResult, totalRestored, totalKept };
|
|
4030
4177
|
}
|
|
4031
4178
|
async collectDrift(manifestFiles, distMap, projectRoot, fileFilter) {
|
|
4032
4179
|
const drift = [];
|
|
@@ -4240,8 +4387,8 @@ function registerRestoreCommand(program2) {
|
|
|
4240
4387
|
deps.loader,
|
|
4241
4388
|
deps.hasher,
|
|
4242
4389
|
deps.logger,
|
|
4243
|
-
|
|
4244
|
-
|
|
4390
|
+
deps.platform,
|
|
4391
|
+
prompter
|
|
4245
4392
|
);
|
|
4246
4393
|
const result = await restoreUseCase.execute({
|
|
4247
4394
|
frameworkPath,
|
|
@@ -4370,6 +4517,45 @@ var AdoptUseCase = class {
|
|
|
4370
4517
|
}
|
|
4371
4518
|
async execute(options) {
|
|
4372
4519
|
const { toolIds, frameworkPath, docsDir, projectRoot, version } = options;
|
|
4520
|
+
this.validateToolIds(toolIds);
|
|
4521
|
+
const existing = await this.manifestRepo.load();
|
|
4522
|
+
if (existing !== null) throw new Error("Already initialized. Use `aidd update` to upgrade.");
|
|
4523
|
+
await this.deleteLegacyConfig(projectRoot);
|
|
4524
|
+
const { descriptor, contentFiles, docsFiles } = await this.loader.loadFromDirectory(
|
|
4525
|
+
frameworkPath,
|
|
4526
|
+
version
|
|
4527
|
+
);
|
|
4528
|
+
const manifest = Manifest.create(docsDir);
|
|
4529
|
+
const toolResults = await this.registerAllTools(
|
|
4530
|
+
toolIds,
|
|
4531
|
+
manifest,
|
|
4532
|
+
descriptor,
|
|
4533
|
+
contentFiles,
|
|
4534
|
+
docsDir,
|
|
4535
|
+
projectRoot,
|
|
4536
|
+
version
|
|
4537
|
+
);
|
|
4538
|
+
const docsRegistered = await this.registerDocs(
|
|
4539
|
+
manifest,
|
|
4540
|
+
docsFiles,
|
|
4541
|
+
docsDir,
|
|
4542
|
+
projectRoot,
|
|
4543
|
+
version
|
|
4544
|
+
);
|
|
4545
|
+
await this.persistAdopt(manifest, docsDir, projectRoot, version);
|
|
4546
|
+
return {
|
|
4547
|
+
tools: toolResults,
|
|
4548
|
+
totalRegistered: toolResults.reduce((sum, r) => sum + r.registered.length, 0),
|
|
4549
|
+
docsRegistered
|
|
4550
|
+
};
|
|
4551
|
+
}
|
|
4552
|
+
/** Finalizes catalog, saves manifest, and writes gitignore entry. */
|
|
4553
|
+
async persistAdopt(manifest, docsDir, projectRoot, version) {
|
|
4554
|
+
await this.finalizeCatalog(manifest, docsDir, projectRoot, version);
|
|
4555
|
+
await this.manifestRepo.save(manifest);
|
|
4556
|
+
await new GitignoreUseCase(this.fs).execute(projectRoot, [".aidd/cache/"]);
|
|
4557
|
+
}
|
|
4558
|
+
validateToolIds(toolIds) {
|
|
4373
4559
|
const invalid = toolIds.filter((t) => !VALID_TOOL_IDS.includes(t));
|
|
4374
4560
|
if (invalid.length > 0) {
|
|
4375
4561
|
throw new Error(
|
|
@@ -4379,16 +4565,8 @@ var AdoptUseCase = class {
|
|
|
4379
4565
|
if (toolIds.length === 0) {
|
|
4380
4566
|
throw new Error("No tools specified. Use --tools to specify at least one tool.");
|
|
4381
4567
|
}
|
|
4382
|
-
|
|
4383
|
-
|
|
4384
|
-
throw new Error("Already initialized. Use `aidd update` to upgrade.");
|
|
4385
|
-
}
|
|
4386
|
-
await this.deleteLegacyConfig(projectRoot);
|
|
4387
|
-
const { descriptor, contentFiles, docsFiles } = await this.loader.loadFromDirectory(
|
|
4388
|
-
frameworkPath,
|
|
4389
|
-
version
|
|
4390
|
-
);
|
|
4391
|
-
const manifest = Manifest.create(docsDir);
|
|
4568
|
+
}
|
|
4569
|
+
async registerAllTools(toolIds, manifest, descriptor, contentFiles, docsDir, projectRoot, version) {
|
|
4392
4570
|
const toolResults = [];
|
|
4393
4571
|
for (const toolId of toolIds) {
|
|
4394
4572
|
this.logger.info(`Adopting ${toolId}...`);
|
|
@@ -4416,37 +4594,33 @@ var AdoptUseCase = class {
|
|
|
4416
4594
|
manifest.addTool(toolId, version, registeredFiles);
|
|
4417
4595
|
toolResults.push({ toolId, registered: registeredFiles.map((f) => f.relativePath) });
|
|
4418
4596
|
}
|
|
4419
|
-
|
|
4597
|
+
return toolResults;
|
|
4598
|
+
}
|
|
4599
|
+
async registerDocs(manifest, docsFiles, docsDir, projectRoot, version) {
|
|
4420
4600
|
const docsAbsDir = join22(projectRoot, docsDir);
|
|
4421
|
-
if (await this.fs.fileExists(docsAbsDir))
|
|
4422
|
-
|
|
4423
|
-
|
|
4424
|
-
|
|
4425
|
-
|
|
4426
|
-
|
|
4427
|
-
|
|
4601
|
+
if (!await this.fs.fileExists(docsAbsDir)) return 0;
|
|
4602
|
+
this.logger.info("Adopting docs...");
|
|
4603
|
+
const docsDistribution = buildDocsDistribution(docsFiles, docsDir, this.hasher);
|
|
4604
|
+
const registeredFiles = await this.matchDistributionToDisk(docsDistribution, projectRoot);
|
|
4605
|
+
manifest.addDocs(version, registeredFiles);
|
|
4606
|
+
return registeredFiles.length;
|
|
4607
|
+
}
|
|
4608
|
+
async finalizeCatalog(manifest, docsDir, projectRoot, version) {
|
|
4428
4609
|
await new CatalogUseCase(this.fs).execute({ manifest, docsDir, projectRoot });
|
|
4429
4610
|
const catalogRelPath = `${docsDir}/CATALOG.md`;
|
|
4430
4611
|
const catalogAbsPath = join22(projectRoot, catalogRelPath);
|
|
4431
|
-
if (await this.fs.fileExists(catalogAbsPath))
|
|
4432
|
-
|
|
4433
|
-
|
|
4434
|
-
|
|
4435
|
-
|
|
4612
|
+
if (!await this.fs.fileExists(catalogAbsPath)) return;
|
|
4613
|
+
const catalogHash = await this.fs.readFileHash(catalogAbsPath);
|
|
4614
|
+
const currentDocsFiles = manifest.getDocsFiles();
|
|
4615
|
+
const updatedDocsFiles = currentDocsFiles.map(
|
|
4616
|
+
(f) => f.relativePath === catalogRelPath ? new GeneratedFile({ relativePath: f.relativePath, content: "", hash: catalogHash }) : new GeneratedFile({ relativePath: f.relativePath, content: "", hash: f.hash })
|
|
4617
|
+
);
|
|
4618
|
+
if (!currentDocsFiles.some((f) => f.relativePath === catalogRelPath)) {
|
|
4619
|
+
updatedDocsFiles.push(
|
|
4620
|
+
new GeneratedFile({ relativePath: catalogRelPath, content: "", hash: catalogHash })
|
|
4436
4621
|
);
|
|
4437
|
-
if (!currentDocsFiles.some((f) => f.relativePath === catalogRelPath)) {
|
|
4438
|
-
updatedDocsFiles.push(
|
|
4439
|
-
new GeneratedFile({ relativePath: catalogRelPath, content: "", hash: catalogHash })
|
|
4440
|
-
);
|
|
4441
|
-
}
|
|
4442
|
-
manifest.addDocs(manifest.getDocsVersion() ?? version, updatedDocsFiles);
|
|
4443
4622
|
}
|
|
4444
|
-
|
|
4445
|
-
return {
|
|
4446
|
-
tools: toolResults,
|
|
4447
|
-
totalRegistered: toolResults.reduce((sum, r) => sum + r.registered.length, 0),
|
|
4448
|
-
docsRegistered
|
|
4449
|
-
};
|
|
4623
|
+
manifest.addDocs(manifest.getDocsVersion() ?? version, updatedDocsFiles);
|
|
4450
4624
|
}
|
|
4451
4625
|
async matchDistributionToDisk(distribution, projectRoot) {
|
|
4452
4626
|
const result = [];
|
|
@@ -4514,39 +4688,71 @@ var InitUseCase = class {
|
|
|
4514
4688
|
}
|
|
4515
4689
|
async execute(options) {
|
|
4516
4690
|
const { frameworkPath, version, projectRoot, force = false } = options;
|
|
4517
|
-
const
|
|
4518
|
-
let docsDir = options.docsDir;
|
|
4519
|
-
let explicitDocsDir = options.explicitDocsDir;
|
|
4520
|
-
let repo = options.repo;
|
|
4521
|
-
if (interactive && !force && docsDir === void 0 && this.prompter !== void 0) {
|
|
4522
|
-
const docsDirInput = await this.prompter.input(
|
|
4523
|
-
"Documentation directory name:",
|
|
4524
|
-
Manifest.DEFAULT_DOCS_DIR
|
|
4525
|
-
);
|
|
4526
|
-
docsDir = docsDirInput || Manifest.DEFAULT_DOCS_DIR;
|
|
4527
|
-
explicitDocsDir = docsDir;
|
|
4528
|
-
const repoInput = await this.prompter.input(
|
|
4529
|
-
"Framework repository (owner/repo, leave blank to skip):",
|
|
4530
|
-
options.repo ?? ""
|
|
4531
|
-
);
|
|
4532
|
-
if (repoInput !== "") {
|
|
4533
|
-
repo = repoInput.trim();
|
|
4534
|
-
}
|
|
4535
|
-
}
|
|
4691
|
+
const { docsDir, explicitDocsDir, repo } = await this.resolveInitConfig(options);
|
|
4536
4692
|
const resolvedInputDocsDir = docsDir ?? Manifest.DEFAULT_DOCS_DIR;
|
|
4537
4693
|
Manifest.validateDocsDir(resolvedInputDocsDir);
|
|
4538
|
-
await this.checkPreconditions({ docsDir: resolvedInputDocsDir, projectRoot, force, repo });
|
|
4539
4694
|
const existing = await this.manifestRepo.load();
|
|
4695
|
+
await this.checkPreconditions({ docsDir: resolvedInputDocsDir, projectRoot, force, repo });
|
|
4540
4696
|
const resolvedDocsDir = force && existing !== null && explicitDocsDir === void 0 ? existing.docsDir : resolvedInputDocsDir;
|
|
4541
4697
|
const { descriptor, docsFiles } = await this.loader.loadFromDirectory(frameworkPath, version);
|
|
4698
|
+
const generated = await this.writeDocsFiles(
|
|
4699
|
+
docsFiles,
|
|
4700
|
+
resolvedDocsDir,
|
|
4701
|
+
projectRoot,
|
|
4702
|
+
force,
|
|
4703
|
+
existing
|
|
4704
|
+
);
|
|
4705
|
+
if (force && existing !== null)
|
|
4706
|
+
await this.removeStaleDocsFiles(generated, existing, projectRoot);
|
|
4707
|
+
const manifest = this.buildManifest(existing, resolvedDocsDir, repo, force);
|
|
4708
|
+
manifest.addDocs(descriptor.version, generated);
|
|
4709
|
+
await this.persistInit(manifest, resolvedDocsDir, projectRoot, force);
|
|
4710
|
+
return { docsDir: resolvedDocsDir, fileCount: generated.length, manifest };
|
|
4711
|
+
}
|
|
4712
|
+
buildManifest(existing, resolvedDocsDir, repo, force) {
|
|
4713
|
+
return force && existing !== null ? existing.withDocsDir(resolvedDocsDir) : Manifest.create(resolvedDocsDir, repo);
|
|
4714
|
+
}
|
|
4715
|
+
/** Saves manifest, regenerates catalog, and conditionally adds gitignore entry. */
|
|
4716
|
+
async persistInit(manifest, docsDir, projectRoot, force) {
|
|
4717
|
+
await this.manifestRepo.save(manifest);
|
|
4718
|
+
await new CatalogUseCase(this.fs).execute({ manifest, docsDir, projectRoot });
|
|
4719
|
+
if (!force) {
|
|
4720
|
+
await new GitignoreUseCase(this.fs).execute(projectRoot, [".aidd/cache/"]);
|
|
4721
|
+
}
|
|
4722
|
+
}
|
|
4723
|
+
/** Resolves interactive config (docsDir, repo) if prompted, otherwise uses options as-is. */
|
|
4724
|
+
async resolveInitConfig(options) {
|
|
4725
|
+
const interactive = options.interactive ?? false;
|
|
4726
|
+
const force = options.force ?? false;
|
|
4727
|
+
if (!interactive || force || options.docsDir !== void 0 || this.prompter === void 0) {
|
|
4728
|
+
return {
|
|
4729
|
+
docsDir: options.docsDir,
|
|
4730
|
+
explicitDocsDir: options.explicitDocsDir,
|
|
4731
|
+
repo: options.repo
|
|
4732
|
+
};
|
|
4733
|
+
}
|
|
4734
|
+
const docsDirInput = await this.prompter.input(
|
|
4735
|
+
"Documentation directory name:",
|
|
4736
|
+
Manifest.DEFAULT_DOCS_DIR
|
|
4737
|
+
);
|
|
4738
|
+
const docsDir = docsDirInput || Manifest.DEFAULT_DOCS_DIR;
|
|
4739
|
+
const repoInput = await this.prompter.input(
|
|
4740
|
+
"Framework repository (owner/repo, leave blank to skip):",
|
|
4741
|
+
options.repo ?? ""
|
|
4742
|
+
);
|
|
4743
|
+
const repo = repoInput !== "" ? repoInput.trim() : options.repo;
|
|
4744
|
+
return { docsDir, explicitDocsDir: docsDir, repo };
|
|
4745
|
+
}
|
|
4746
|
+
/** Writes docs files to disk, skipping CATALOG.md. Returns the list of generated files. */
|
|
4747
|
+
async writeDocsFiles(docsFiles, docsDir, projectRoot, force, existing) {
|
|
4542
4748
|
const generated = [];
|
|
4543
4749
|
for (const [frameworkRelPath, rawContent] of docsFiles.entries()) {
|
|
4544
4750
|
if (frameworkRelPath.endsWith("CATALOG.md")) continue;
|
|
4545
|
-
const outputRelPath = remapDocsPath(frameworkRelPath,
|
|
4751
|
+
const outputRelPath = remapDocsPath(frameworkRelPath, docsDir);
|
|
4546
4752
|
const outputPath = join23(projectRoot, outputRelPath);
|
|
4547
|
-
const content = rewriteDocsContent(rawContent,
|
|
4753
|
+
const content = rewriteDocsContent(rawContent, docsDir);
|
|
4548
4754
|
const newHash = this.hasher.hash(content);
|
|
4549
|
-
if (force && await this.fs.fileExists(outputPath)) {
|
|
4755
|
+
if (force && existing !== null && await this.fs.fileExists(outputPath)) {
|
|
4550
4756
|
const diskHash = await this.fs.readFileHash(outputPath);
|
|
4551
4757
|
if (!diskHash.equals(newHash)) {
|
|
4552
4758
|
this.logger.warn(`Overwriting modified file: ${outputRelPath}`);
|
|
@@ -4557,28 +4763,74 @@ var InitUseCase = class {
|
|
|
4557
4763
|
}
|
|
4558
4764
|
generated.push(new GeneratedFile({ relativePath: outputRelPath, content, hash: newHash }));
|
|
4559
4765
|
}
|
|
4560
|
-
|
|
4561
|
-
|
|
4562
|
-
|
|
4563
|
-
|
|
4564
|
-
|
|
4565
|
-
|
|
4766
|
+
return generated;
|
|
4767
|
+
}
|
|
4768
|
+
async removeStaleDocsFiles(generated, existing, projectRoot) {
|
|
4769
|
+
const newPaths = new Set(generated.map((f) => f.relativePath));
|
|
4770
|
+
for (const oldFile of existing.getDocsFiles()) {
|
|
4771
|
+
if (!newPaths.has(oldFile.relativePath)) {
|
|
4772
|
+
await this.fs.deleteFile(join23(projectRoot, oldFile.relativePath));
|
|
4566
4773
|
}
|
|
4567
4774
|
}
|
|
4568
|
-
|
|
4569
|
-
|
|
4570
|
-
|
|
4571
|
-
|
|
4572
|
-
|
|
4573
|
-
|
|
4775
|
+
}
|
|
4776
|
+
};
|
|
4777
|
+
|
|
4778
|
+
// src/application/use-cases/shared/setup-state-detector.ts
|
|
4779
|
+
var SetupStateDetector = class {
|
|
4780
|
+
constructor(manifestRepo, fs, resolver) {
|
|
4781
|
+
this.manifestRepo = manifestRepo;
|
|
4782
|
+
this.fs = fs;
|
|
4783
|
+
this.resolver = resolver;
|
|
4784
|
+
}
|
|
4785
|
+
async detect(projectRoot) {
|
|
4786
|
+
const manifest = await this.manifestRepo.load();
|
|
4787
|
+
if (manifest === null) {
|
|
4788
|
+
return this.detectWithoutManifest(projectRoot);
|
|
4574
4789
|
}
|
|
4575
|
-
|
|
4790
|
+
const installedIds = manifest.getInstalledToolIds();
|
|
4791
|
+
if (installedIds.length === 0) {
|
|
4792
|
+
return { kind: "needs-install" };
|
|
4793
|
+
}
|
|
4794
|
+
return this.detectUpdateState(manifest, installedIds);
|
|
4795
|
+
}
|
|
4796
|
+
async detectWithoutManifest(projectRoot) {
|
|
4797
|
+
for (const tool of getAllRegisteredTools().values()) {
|
|
4798
|
+
if (await hasToolSignals(this.fs, tool, projectRoot)) return { kind: "needs-adopt" };
|
|
4799
|
+
}
|
|
4800
|
+
return { kind: "needs-init" };
|
|
4801
|
+
}
|
|
4802
|
+
async detectUpdateState(manifest, installedIds) {
|
|
4803
|
+
try {
|
|
4804
|
+
const latestVersion = await this.resolver.fetchLatestVersion(manifest.repo);
|
|
4805
|
+
const installedVersions = installedIds.map((id) => manifest.getToolVersion(id)).filter((v) => v !== void 0);
|
|
4806
|
+
const currentVersion2 = installedVersions[0] ?? "unknown";
|
|
4807
|
+
const needsUpdate = isSemver(latestVersion) && installedVersions.some((v) => !isSemver(v) || compareSemver(v, latestVersion) < 0);
|
|
4808
|
+
if (needsUpdate) {
|
|
4809
|
+
return { kind: "needs-update", currentVersion: currentVersion2, latestVersion };
|
|
4810
|
+
}
|
|
4811
|
+
} catch {
|
|
4812
|
+
}
|
|
4813
|
+
return { kind: "up-to-date" };
|
|
4576
4814
|
}
|
|
4577
4815
|
};
|
|
4578
4816
|
|
|
4579
4817
|
// src/application/use-cases/update-use-case.ts
|
|
4580
4818
|
import { join as join24 } from "path";
|
|
4581
4819
|
|
|
4820
|
+
// src/domain/models/update-scope.ts
|
|
4821
|
+
function parseUpdateScope(raw) {
|
|
4822
|
+
if (raw === "all") return { kind: "all" };
|
|
4823
|
+
if (raw === "docs") return { kind: "docs" };
|
|
4824
|
+
if (raw.startsWith("tool:")) {
|
|
4825
|
+
const toolId = raw.slice(5);
|
|
4826
|
+
return { kind: "tool", toolId };
|
|
4827
|
+
}
|
|
4828
|
+
throw new Error(`Invalid update scope: "${raw}"`);
|
|
4829
|
+
}
|
|
4830
|
+
function formatToolScopeValue(toolId) {
|
|
4831
|
+
return `tool:${toolId}`;
|
|
4832
|
+
}
|
|
4833
|
+
|
|
4582
4834
|
// src/application/use-cases/conflict-resolution-use-case.ts
|
|
4583
4835
|
var ConflictResolutionUseCase = class {
|
|
4584
4836
|
constructor(prompter) {
|
|
@@ -4642,51 +4894,70 @@ var UpdateUseCase = class {
|
|
|
4642
4894
|
}
|
|
4643
4895
|
async execute(options) {
|
|
4644
4896
|
const { force = false, dryRun = false } = options;
|
|
4645
|
-
const interactive = options.interactive ?? false;
|
|
4646
4897
|
const conflictResolution = new ConflictResolutionUseCase(this.prompter);
|
|
4647
|
-
const isInteractive =
|
|
4648
|
-
if (isInteractive) {
|
|
4649
|
-
|
|
4650
|
-
|
|
4651
|
-
|
|
4652
|
-
|
|
4653
|
-
|
|
4654
|
-
|
|
4655
|
-
|
|
4656
|
-
|
|
4657
|
-
|
|
4658
|
-
|
|
4659
|
-
|
|
4660
|
-
|
|
4661
|
-
|
|
4662
|
-
|
|
4663
|
-
|
|
4664
|
-
|
|
4665
|
-
|
|
4666
|
-
|
|
4667
|
-
|
|
4668
|
-
|
|
4669
|
-
|
|
4670
|
-
|
|
4671
|
-
|
|
4672
|
-
|
|
4673
|
-
}
|
|
4674
|
-
|
|
4675
|
-
|
|
4676
|
-
|
|
4677
|
-
|
|
4678
|
-
|
|
4679
|
-
|
|
4680
|
-
|
|
4681
|
-
|
|
4682
|
-
|
|
4898
|
+
const isInteractive = this.resolveInteractiveFlag(options, force, dryRun);
|
|
4899
|
+
if (!isInteractive) {
|
|
4900
|
+
return this.executeInternal(options, { dryRun, force, conflictResolution }).then((r) => ({
|
|
4901
|
+
...r,
|
|
4902
|
+
version: options.version
|
|
4903
|
+
}));
|
|
4904
|
+
}
|
|
4905
|
+
return this.executeInteractive(options, conflictResolution);
|
|
4906
|
+
}
|
|
4907
|
+
resolveInteractiveFlag(options, force, dryRun) {
|
|
4908
|
+
return (options.interactive ?? false) && !force && !dryRun && options.toolIds === void 0 && !(options.docsOnly ?? false);
|
|
4909
|
+
}
|
|
4910
|
+
async executeInteractive(options, conflictResolution) {
|
|
4911
|
+
const dryRunResult = await this.executeInternal(options, {
|
|
4912
|
+
dryRun: true,
|
|
4913
|
+
force: false,
|
|
4914
|
+
conflictResolution
|
|
4915
|
+
});
|
|
4916
|
+
const outcome = await this.buildInteractiveScope(dryRunResult, options, conflictResolution);
|
|
4917
|
+
if (outcome.kind === "already-applied") {
|
|
4918
|
+
return { ...outcome.result, version: options.version };
|
|
4919
|
+
}
|
|
4920
|
+
if (outcome.kind === "cancelled") {
|
|
4921
|
+
return { ...dryRunResult, cancelled: true, version: options.version };
|
|
4922
|
+
}
|
|
4923
|
+
return this.executeInternal(
|
|
4924
|
+
{ ...options, toolIds: outcome.toolIds, docsOnly: outcome.docsOnly, force: true },
|
|
4925
|
+
{ dryRun: false, force: true, conflictResolution }
|
|
4926
|
+
).then((r) => ({ ...r, cancelled: false, version: options.version }));
|
|
4927
|
+
}
|
|
4928
|
+
/** Resolves the interactive update scope after a dry-run. */
|
|
4929
|
+
async buildInteractiveScope(dryRunResult, options, conflictResolution) {
|
|
4930
|
+
const changedTools = dryRunResult.tools.filter(
|
|
4931
|
+
(t) => t.diff.some((d) => d.kind !== "unchanged")
|
|
4932
|
+
);
|
|
4933
|
+
const docsChanged = dryRunResult.docs?.diff.some((d) => d.kind !== "unchanged") ?? false;
|
|
4934
|
+
if (changedTools.length === 0 && !docsChanged) {
|
|
4935
|
+
const result = await this.executeInternal(
|
|
4936
|
+
{ ...options, force: true },
|
|
4683
4937
|
{ dryRun: false, force: true, conflictResolution }
|
|
4684
|
-
)
|
|
4938
|
+
);
|
|
4939
|
+
return { kind: "already-applied", result };
|
|
4940
|
+
}
|
|
4941
|
+
const scopeChoices = [
|
|
4942
|
+
{ name: "All", value: "all" },
|
|
4943
|
+
...changedTools.map((t) => ({
|
|
4944
|
+
name: `${t.toolId} only`,
|
|
4945
|
+
value: formatToolScopeValue(t.toolId)
|
|
4946
|
+
})),
|
|
4947
|
+
...docsChanged ? [{ name: "docs only", value: "docs" }] : []
|
|
4948
|
+
];
|
|
4949
|
+
const scopeSelection = await this.prompter.select("What to update?", scopeChoices);
|
|
4950
|
+
const confirmed = await this.prompter.confirm("Apply update?");
|
|
4951
|
+
if (!confirmed) return { kind: "cancelled" };
|
|
4952
|
+
const scope = parseUpdateScope(scopeSelection);
|
|
4953
|
+
let toolIds;
|
|
4954
|
+
let docsOnly = false;
|
|
4955
|
+
if (scope.kind === "docs") {
|
|
4956
|
+
docsOnly = true;
|
|
4957
|
+
} else if (scope.kind === "tool") {
|
|
4958
|
+
toolIds = [scope.toolId];
|
|
4685
4959
|
}
|
|
4686
|
-
return
|
|
4687
|
-
...r,
|
|
4688
|
-
version: options.version
|
|
4689
|
-
}));
|
|
4960
|
+
return { kind: "scope", toolIds, docsOnly };
|
|
4690
4961
|
}
|
|
4691
4962
|
async executeInternal(options, internal) {
|
|
4692
4963
|
const { frameworkPath, version, projectRoot, repo } = options;
|
|
@@ -4699,78 +4970,18 @@ var UpdateUseCase = class {
|
|
|
4699
4970
|
frameworkPath,
|
|
4700
4971
|
version
|
|
4701
4972
|
);
|
|
4702
|
-
|
|
4703
|
-
|
|
4704
|
-
|
|
4705
|
-
|
|
4706
|
-
|
|
4707
|
-
|
|
4708
|
-
|
|
4709
|
-
|
|
4710
|
-
|
|
4711
|
-
|
|
4712
|
-
|
|
4713
|
-
|
|
4714
|
-
this.logger.debug(`Checking ${toolId} for updates...`);
|
|
4715
|
-
const config = getToolConfig(toolId);
|
|
4716
|
-
const manifestFiles = manifest.getToolFiles(toolId);
|
|
4717
|
-
const manifestMap = new Map(manifestFiles.map((f) => [f.relativePath, f.hash]));
|
|
4718
|
-
const newDistribution = await generateDistribution(
|
|
4719
|
-
descriptor,
|
|
4720
|
-
config,
|
|
4721
|
-
docsDir,
|
|
4722
|
-
contentFiles,
|
|
4723
|
-
this.hasher,
|
|
4724
|
-
this.platform,
|
|
4725
|
-
projectRoot,
|
|
4726
|
-
this.fs
|
|
4727
|
-
);
|
|
4728
|
-
const newDistMap = new Map(newDistribution.map((f) => [f.relativePath, f]));
|
|
4729
|
-
const diff = await this.computeDiff(newDistribution, newDistMap, manifestMap, projectRoot);
|
|
4730
|
-
let result = {
|
|
4731
|
-
kept: [],
|
|
4732
|
-
written: [],
|
|
4733
|
-
deleted: [],
|
|
4734
|
-
backedUp: [],
|
|
4735
|
-
userFileConflicts: []
|
|
4736
|
-
};
|
|
4737
|
-
if (!dryRun) {
|
|
4738
|
-
const mergedHashMap = /* @__PURE__ */ new Map();
|
|
4739
|
-
for (const newFile of newDistribution) {
|
|
4740
|
-
if (!newFile.merge) continue;
|
|
4741
|
-
const outputPath = join24(projectRoot, newFile.relativePath);
|
|
4742
|
-
await this.fs.mergeJsonFile(outputPath, newFile.content);
|
|
4743
|
-
const diskHash = await this.fs.readFileHash(outputPath);
|
|
4744
|
-
manifest.syncFileHashAcrossTools(newFile.relativePath, diskHash);
|
|
4745
|
-
mergedHashMap.set(newFile.relativePath, diskHash);
|
|
4746
|
-
}
|
|
4747
|
-
const conflictDecisions = await this.resolveConflicts(diff, force, conflictResolution);
|
|
4748
|
-
result = await this.applyDiff(diff, newDistMap, projectRoot, conflictDecisions, manifest);
|
|
4749
|
-
for (const relativePath of result.userFileConflicts) {
|
|
4750
|
-
this.logger.warn(
|
|
4751
|
-
`\`${relativePath}\` already exists and was not installed by AIDD \u2014 skipped to preserve user file`
|
|
4752
|
-
);
|
|
4753
|
-
}
|
|
4754
|
-
const nonMergedFinal = newDistribution.filter((f) => !f.merge).filter(
|
|
4755
|
-
(f) => !result.deleted.includes(f.relativePath) && !result.kept.includes(f.relativePath) && !result.userFileConflicts.includes(f.relativePath)
|
|
4756
|
-
);
|
|
4757
|
-
const keptFiles = manifestFiles.filter((f) => result.kept.includes(f.relativePath)).map(
|
|
4758
|
-
(f) => new GeneratedFile({ relativePath: f.relativePath, content: "", hash: f.hash })
|
|
4759
|
-
);
|
|
4760
|
-
const mergedFiles = manifestFiles.filter((f) => newDistMap.get(f.relativePath)?.merge === true).map((f) => {
|
|
4761
|
-
const hash = mergedHashMap.get(f.relativePath) ?? f.hash;
|
|
4762
|
-
return new GeneratedFile({ relativePath: f.relativePath, content: "", hash });
|
|
4763
|
-
});
|
|
4764
|
-
manifest.addTool(toolId, version, [...nonMergedFinal, ...keptFiles, ...mergedFiles]);
|
|
4765
|
-
}
|
|
4766
|
-
toolResults.push({
|
|
4767
|
-
toolId,
|
|
4768
|
-
alreadyUpToDate: !diff.some((d) => d.kind !== "unchanged"),
|
|
4769
|
-
dryRun,
|
|
4770
|
-
diff,
|
|
4771
|
-
...result
|
|
4772
|
-
});
|
|
4773
|
-
}
|
|
4973
|
+
this.validateToolIds(options.toolIds, manifest);
|
|
4974
|
+
const effectiveToolIds = this.resolveEffectiveToolIds(options, docsOnly, manifest);
|
|
4975
|
+
const toolResults = await this.updateAllTools(
|
|
4976
|
+
effectiveToolIds,
|
|
4977
|
+
manifest,
|
|
4978
|
+
descriptor,
|
|
4979
|
+
contentFiles,
|
|
4980
|
+
docsDir,
|
|
4981
|
+
projectRoot,
|
|
4982
|
+
version,
|
|
4983
|
+
internal
|
|
4984
|
+
);
|
|
4774
4985
|
const hasExplicitToolFilter = !docsOnly && options.toolIds !== void 0 && options.toolIds.length > 0;
|
|
4775
4986
|
const docsResult = hasExplicitToolFilter ? null : await this.updateDocs(
|
|
4776
4987
|
manifest,
|
|
@@ -4783,17 +4994,131 @@ var UpdateUseCase = class {
|
|
|
4783
4994
|
conflictResolution
|
|
4784
4995
|
);
|
|
4785
4996
|
if (!dryRun) {
|
|
4786
|
-
await new
|
|
4997
|
+
await new PostInstallPipelineUseCase(
|
|
4998
|
+
this.fs,
|
|
4999
|
+
this.manifestRepo,
|
|
5000
|
+
this.hasher,
|
|
5001
|
+
this.git
|
|
5002
|
+
).execute({
|
|
4787
5003
|
projectRoot,
|
|
4788
5004
|
version,
|
|
4789
5005
|
descriptor,
|
|
4790
5006
|
contentFiles,
|
|
4791
|
-
manifest
|
|
5007
|
+
manifest,
|
|
5008
|
+
docsDir
|
|
4792
5009
|
});
|
|
4793
|
-
await this.manifestRepo.save(manifest);
|
|
4794
|
-
await new CatalogUseCase(this.fs).execute({ manifest, docsDir, projectRoot });
|
|
4795
|
-
await new GitignoreUseCase(this.fs).execute(projectRoot, [".aidd/cache/"]);
|
|
4796
5010
|
}
|
|
5011
|
+
return this.buildTotals(toolResults, docsResult, dryRun);
|
|
5012
|
+
}
|
|
5013
|
+
validateToolIds(toolIds, manifest) {
|
|
5014
|
+
if (!toolIds || toolIds.length === 0) return;
|
|
5015
|
+
const installedIds = new Set(manifest.getInstalledToolIds());
|
|
5016
|
+
const notInstalled = toolIds.filter((id) => !installedIds.has(id));
|
|
5017
|
+
if (notInstalled.length > 0) {
|
|
5018
|
+
throw new Error(
|
|
5019
|
+
`${notInstalled.join(", ")} ${notInstalled.length === 1 ? "is" : "are"} not installed. Use \`aidd install ${notInstalled.join(" ")}\` first.`
|
|
5020
|
+
);
|
|
5021
|
+
}
|
|
5022
|
+
}
|
|
5023
|
+
resolveEffectiveToolIds(options, docsOnly, manifest) {
|
|
5024
|
+
if (docsOnly) return [];
|
|
5025
|
+
if (options.toolIds && options.toolIds.length > 0) return options.toolIds;
|
|
5026
|
+
return manifest.getInstalledToolIds();
|
|
5027
|
+
}
|
|
5028
|
+
async updateAllTools(toolIds, manifest, descriptor, contentFiles, docsDir, projectRoot, version, internal) {
|
|
5029
|
+
const toolResults = [];
|
|
5030
|
+
for (const toolId of toolIds) {
|
|
5031
|
+
this.logger.debug(`Checking ${toolId} for updates...`);
|
|
5032
|
+
const result = await this.updateToolSection(
|
|
5033
|
+
toolId,
|
|
5034
|
+
manifest,
|
|
5035
|
+
descriptor,
|
|
5036
|
+
contentFiles,
|
|
5037
|
+
docsDir,
|
|
5038
|
+
projectRoot,
|
|
5039
|
+
version,
|
|
5040
|
+
internal
|
|
5041
|
+
);
|
|
5042
|
+
toolResults.push(result);
|
|
5043
|
+
}
|
|
5044
|
+
return toolResults;
|
|
5045
|
+
}
|
|
5046
|
+
async updateToolSection(toolId, manifest, descriptor, contentFiles, docsDir, projectRoot, version, internal) {
|
|
5047
|
+
const { dryRun, force, conflictResolution } = internal;
|
|
5048
|
+
const config = getToolConfig(toolId);
|
|
5049
|
+
const manifestFiles = manifest.getToolFiles(toolId);
|
|
5050
|
+
const manifestMap = new Map(manifestFiles.map((f) => [f.relativePath, f.hash]));
|
|
5051
|
+
const newDistribution = await generateDistribution(
|
|
5052
|
+
descriptor,
|
|
5053
|
+
config,
|
|
5054
|
+
docsDir,
|
|
5055
|
+
contentFiles,
|
|
5056
|
+
this.hasher,
|
|
5057
|
+
this.platform,
|
|
5058
|
+
projectRoot,
|
|
5059
|
+
this.fs
|
|
5060
|
+
);
|
|
5061
|
+
const newDistMap = new Map(newDistribution.map((f) => [f.relativePath, f]));
|
|
5062
|
+
const diff = await this.computeDiff(newDistribution, newDistMap, manifestMap, projectRoot);
|
|
5063
|
+
let result = this.emptyApplyDiffResult();
|
|
5064
|
+
if (!dryRun) {
|
|
5065
|
+
const mergedHashMap = await this.applyMergeFiles(newDistribution, projectRoot, manifest);
|
|
5066
|
+
const conflictDecisions = await this.resolveConflicts(diff, force, conflictResolution);
|
|
5067
|
+
result = await this.applyDiff(diff, newDistMap, projectRoot, conflictDecisions, manifest);
|
|
5068
|
+
this.warnUserFileConflicts(result.userFileConflicts);
|
|
5069
|
+
this.registerToolFiles(
|
|
5070
|
+
toolId,
|
|
5071
|
+
version,
|
|
5072
|
+
manifest,
|
|
5073
|
+
newDistribution,
|
|
5074
|
+
manifestFiles,
|
|
5075
|
+
result,
|
|
5076
|
+
mergedHashMap,
|
|
5077
|
+
newDistMap
|
|
5078
|
+
);
|
|
5079
|
+
}
|
|
5080
|
+
return {
|
|
5081
|
+
toolId,
|
|
5082
|
+
alreadyUpToDate: !diff.some((d) => d.kind !== "unchanged"),
|
|
5083
|
+
dryRun,
|
|
5084
|
+
diff,
|
|
5085
|
+
...result
|
|
5086
|
+
};
|
|
5087
|
+
}
|
|
5088
|
+
emptyApplyDiffResult() {
|
|
5089
|
+
return { kept: [], written: [], deleted: [], backedUp: [], userFileConflicts: [] };
|
|
5090
|
+
}
|
|
5091
|
+
warnUserFileConflicts(conflicts) {
|
|
5092
|
+
for (const relativePath of conflicts) {
|
|
5093
|
+
this.logger.warn(
|
|
5094
|
+
`\`${relativePath}\` already exists and was not installed by AIDD \u2014 skipped to preserve user file`
|
|
5095
|
+
);
|
|
5096
|
+
}
|
|
5097
|
+
}
|
|
5098
|
+
async applyMergeFiles(newDistribution, projectRoot, manifest) {
|
|
5099
|
+
const mergedHashMap = /* @__PURE__ */ new Map();
|
|
5100
|
+
for (const newFile of newDistribution) {
|
|
5101
|
+
if (!newFile.merge) continue;
|
|
5102
|
+
const outputPath = join24(projectRoot, newFile.relativePath);
|
|
5103
|
+
await this.fs.mergeJsonFile(outputPath, newFile.content);
|
|
5104
|
+
const diskHash = await this.fs.readFileHash(outputPath);
|
|
5105
|
+
manifest.syncFileHashAcrossTools(newFile.relativePath, diskHash);
|
|
5106
|
+
mergedHashMap.set(newFile.relativePath, diskHash);
|
|
5107
|
+
}
|
|
5108
|
+
return mergedHashMap;
|
|
5109
|
+
}
|
|
5110
|
+
registerToolFiles(toolId, version, manifest, newDistribution, manifestFiles, result, mergedHashMap, newDistMap) {
|
|
5111
|
+
const nonMergedFinal = newDistribution.filter((f) => !f.merge).filter(
|
|
5112
|
+
(f) => !result.deleted.includes(f.relativePath) && !result.kept.includes(f.relativePath) && !result.userFileConflicts.includes(f.relativePath)
|
|
5113
|
+
);
|
|
5114
|
+
const keptFiles = manifestFiles.filter((f) => result.kept.includes(f.relativePath)).map((f) => new GeneratedFile({ relativePath: f.relativePath, content: "", hash: f.hash }));
|
|
5115
|
+
const mergedFiles = manifestFiles.filter((f) => newDistMap.get(f.relativePath)?.merge === true).map((f) => {
|
|
5116
|
+
const hash = mergedHashMap.get(f.relativePath) ?? f.hash;
|
|
5117
|
+
return new GeneratedFile({ relativePath: f.relativePath, content: "", hash });
|
|
5118
|
+
});
|
|
5119
|
+
manifest.addTool(toolId, version, [...nonMergedFinal, ...keptFiles, ...mergedFiles]);
|
|
5120
|
+
}
|
|
5121
|
+
buildTotals(toolResults, docsResult, dryRun) {
|
|
4797
5122
|
const totalWritten = toolResults.reduce((s, t) => s + t.written.length, 0) + (docsResult?.written.length ?? 0);
|
|
4798
5123
|
const totalDeleted = toolResults.reduce((s, t) => s + t.deleted.length, 0) + (docsResult?.deleted.length ?? 0);
|
|
4799
5124
|
const toolCount = toolResults.filter((t) => !t.alreadyUpToDate).length;
|
|
@@ -4822,21 +5147,11 @@ var UpdateUseCase = class {
|
|
|
4822
5147
|
const manifestFiles = manifest.getDocsFiles();
|
|
4823
5148
|
const manifestMap = new Map(manifestFiles.map((f) => [f.relativePath, f.hash]));
|
|
4824
5149
|
const diff = await this.computeDiff(newDistribution, newDistMap, manifestMap, projectRoot);
|
|
4825
|
-
let result =
|
|
4826
|
-
kept: [],
|
|
4827
|
-
written: [],
|
|
4828
|
-
deleted: [],
|
|
4829
|
-
backedUp: [],
|
|
4830
|
-
userFileConflicts: []
|
|
4831
|
-
};
|
|
5150
|
+
let result = this.emptyApplyDiffResult();
|
|
4832
5151
|
if (!dryRun) {
|
|
4833
5152
|
const conflictDecisions = await this.resolveConflicts(diff, force, conflictResolution);
|
|
4834
5153
|
result = await this.applyDiff(diff, newDistMap, projectRoot, conflictDecisions, manifest);
|
|
4835
|
-
|
|
4836
|
-
this.logger.warn(
|
|
4837
|
-
`\`${relativePath}\` already exists and was not installed by AIDD \u2014 skipped to preserve user file`
|
|
4838
|
-
);
|
|
4839
|
-
}
|
|
5154
|
+
this.warnUserFileConflicts(result.userFileConflicts);
|
|
4840
5155
|
const finalFiles = newDistribution.filter(
|
|
4841
5156
|
(f) => !result.deleted.includes(f.relativePath) && !result.kept.includes(f.relativePath) && !result.userFileConflicts.includes(f.relativePath)
|
|
4842
5157
|
);
|
|
@@ -4965,10 +5280,7 @@ var SetupUseCase = class {
|
|
|
4965
5280
|
}
|
|
4966
5281
|
frameworkResolver;
|
|
4967
5282
|
async execute(options) {
|
|
4968
|
-
const state = await
|
|
4969
|
-
this.manifestRepo,
|
|
4970
|
-
this.fs,
|
|
4971
|
-
this.resolver,
|
|
5283
|
+
const state = await new SetupStateDetector(this.manifestRepo, this.fs, this.resolver).detect(
|
|
4972
5284
|
options.projectRoot
|
|
4973
5285
|
);
|
|
4974
5286
|
switch (state.kind) {
|
|
@@ -4985,79 +5297,23 @@ var SetupUseCase = class {
|
|
|
4985
5297
|
}
|
|
4986
5298
|
}
|
|
4987
5299
|
async handleInit(options) {
|
|
4988
|
-
const { projectRoot,
|
|
4989
|
-
|
|
4990
|
-
|
|
4991
|
-
|
|
4992
|
-
docsDir = options.docsDir;
|
|
4993
|
-
explicitDocsDir = options.docsDir;
|
|
4994
|
-
Manifest.validateDocsDir(docsDir);
|
|
4995
|
-
} else if (!options.interactive) {
|
|
4996
|
-
docsDir = Manifest.DEFAULT_DOCS_DIR;
|
|
4997
|
-
explicitDocsDir = "";
|
|
4998
|
-
} else {
|
|
4999
|
-
const docsDirInput = await this.prompter.input(
|
|
5000
|
-
"Documentation directory name:",
|
|
5001
|
-
Manifest.DEFAULT_DOCS_DIR
|
|
5002
|
-
);
|
|
5003
|
-
docsDir = docsDirInput || Manifest.DEFAULT_DOCS_DIR;
|
|
5004
|
-
explicitDocsDir = docsDirInput;
|
|
5005
|
-
Manifest.validateDocsDir(docsDir);
|
|
5006
|
-
}
|
|
5007
|
-
let frameworkPath;
|
|
5008
|
-
let frameworkRepo;
|
|
5009
|
-
if (options.path !== void 0) {
|
|
5010
|
-
if (options.path) {
|
|
5011
|
-
if (isLocalPath(options.path)) {
|
|
5012
|
-
frameworkPath = options.path;
|
|
5013
|
-
} else {
|
|
5014
|
-
frameworkRepo = options.path;
|
|
5015
|
-
}
|
|
5016
|
-
}
|
|
5017
|
-
} else {
|
|
5018
|
-
const existingManifest = await this.manifestRepo.load();
|
|
5019
|
-
const sourceDefault = existingManifest?.repo ?? this.resolver.getDefaultRepo() ?? "";
|
|
5020
|
-
const sourceInput = options.interactive ? await this.prompter.input("Framework source (owner/repo or local path):", sourceDefault) : sourceDefault;
|
|
5021
|
-
if (sourceInput) {
|
|
5022
|
-
if (isLocalPath(sourceInput)) {
|
|
5023
|
-
frameworkPath = sourceInput;
|
|
5024
|
-
} else {
|
|
5025
|
-
frameworkRepo = sourceInput;
|
|
5026
|
-
}
|
|
5027
|
-
}
|
|
5028
|
-
}
|
|
5029
|
-
let resolvedRelease = release;
|
|
5030
|
-
if (!frameworkPath && !release) {
|
|
5031
|
-
if (options.interactive) {
|
|
5032
|
-
const latest = await this.resolver.fetchLatestVersion(frameworkRepo).catch(() => "");
|
|
5033
|
-
resolvedRelease = await this.prompter.input(
|
|
5034
|
-
latest ? `Framework release tag (latest: ${latest}):` : "Framework release tag:",
|
|
5035
|
-
latest
|
|
5036
|
-
) || latest || void 0;
|
|
5037
|
-
} else {
|
|
5038
|
-
resolvedRelease = await this.resolver.fetchLatestVersion(frameworkRepo).catch(() => void 0);
|
|
5039
|
-
}
|
|
5040
|
-
}
|
|
5300
|
+
const { projectRoot, repo } = options;
|
|
5301
|
+
const { docsDir, explicitDocsDir } = await this.resolveDocsDir(options);
|
|
5302
|
+
const { frameworkPath, frameworkRepo } = await this.resolveFrameworkSource(options);
|
|
5303
|
+
const resolvedRelease = await this.resolveRelease(frameworkRepo, options);
|
|
5041
5304
|
const resolved = await this.frameworkResolver.execute({
|
|
5042
5305
|
path: frameworkPath,
|
|
5043
5306
|
release: resolvedRelease
|
|
5044
5307
|
});
|
|
5045
5308
|
const repoForManifest = frameworkRepo ?? repo;
|
|
5046
|
-
const initResult = await
|
|
5047
|
-
|
|
5048
|
-
|
|
5049
|
-
this.loader,
|
|
5050
|
-
this.hasher,
|
|
5051
|
-
this.logger
|
|
5052
|
-
).execute({
|
|
5053
|
-
frameworkPath: resolved.path,
|
|
5054
|
-
version: resolved.version,
|
|
5309
|
+
const initResult = await this.runInit(
|
|
5310
|
+
resolved.path,
|
|
5311
|
+
resolved.version,
|
|
5055
5312
|
docsDir,
|
|
5056
5313
|
explicitDocsDir,
|
|
5057
5314
|
projectRoot,
|
|
5058
|
-
|
|
5059
|
-
|
|
5060
|
-
});
|
|
5315
|
+
repoForManifest
|
|
5316
|
+
);
|
|
5061
5317
|
const installResults = await this.runInstall(
|
|
5062
5318
|
resolved.path,
|
|
5063
5319
|
resolved.version,
|
|
@@ -5073,40 +5329,94 @@ var SetupUseCase = class {
|
|
|
5073
5329
|
install: { results: installResults }
|
|
5074
5330
|
};
|
|
5075
5331
|
}
|
|
5332
|
+
async runInit(frameworkPath, version, docsDir, explicitDocsDir, projectRoot, repo) {
|
|
5333
|
+
return new InitUseCase(
|
|
5334
|
+
this.fs,
|
|
5335
|
+
this.manifestRepo,
|
|
5336
|
+
this.loader,
|
|
5337
|
+
this.hasher,
|
|
5338
|
+
this.logger
|
|
5339
|
+
).execute({
|
|
5340
|
+
frameworkPath,
|
|
5341
|
+
version,
|
|
5342
|
+
docsDir,
|
|
5343
|
+
explicitDocsDir,
|
|
5344
|
+
projectRoot,
|
|
5345
|
+
force: false,
|
|
5346
|
+
repo
|
|
5347
|
+
});
|
|
5348
|
+
}
|
|
5349
|
+
async resolveDocsDir(options) {
|
|
5350
|
+
if (options.docsDir !== void 0) {
|
|
5351
|
+
Manifest.validateDocsDir(options.docsDir);
|
|
5352
|
+
return { docsDir: options.docsDir, explicitDocsDir: options.docsDir };
|
|
5353
|
+
}
|
|
5354
|
+
if (!options.interactive) {
|
|
5355
|
+
return { docsDir: Manifest.DEFAULT_DOCS_DIR, explicitDocsDir: "" };
|
|
5356
|
+
}
|
|
5357
|
+
const docsDirInput = await this.prompter.input(
|
|
5358
|
+
"Documentation directory name:",
|
|
5359
|
+
Manifest.DEFAULT_DOCS_DIR
|
|
5360
|
+
);
|
|
5361
|
+
const docsDir = docsDirInput || Manifest.DEFAULT_DOCS_DIR;
|
|
5362
|
+
Manifest.validateDocsDir(docsDir);
|
|
5363
|
+
return { docsDir, explicitDocsDir: docsDirInput };
|
|
5364
|
+
}
|
|
5365
|
+
async resolveFrameworkSource(options) {
|
|
5366
|
+
if (options.path !== void 0) {
|
|
5367
|
+
if (!options.path) return {};
|
|
5368
|
+
if (isLocalPath(options.path)) return { frameworkPath: options.path };
|
|
5369
|
+
return { frameworkRepo: options.path };
|
|
5370
|
+
}
|
|
5371
|
+
const existingManifest = await this.manifestRepo.load();
|
|
5372
|
+
const sourceDefault = existingManifest?.repo ?? this.resolver.getDefaultRepo() ?? "";
|
|
5373
|
+
const sourceInput = options.interactive ? await this.prompter.input("Framework source (owner/repo or local path):", sourceDefault) : sourceDefault;
|
|
5374
|
+
if (!sourceInput) return {};
|
|
5375
|
+
if (isLocalPath(sourceInput)) return { frameworkPath: sourceInput };
|
|
5376
|
+
return { frameworkRepo: sourceInput };
|
|
5377
|
+
}
|
|
5378
|
+
async resolveRelease(frameworkRepo, options) {
|
|
5379
|
+
if (options.path && isLocalPath(options.path)) return options.release;
|
|
5380
|
+
if (options.release) return options.release;
|
|
5381
|
+
if (options.interactive) {
|
|
5382
|
+
const latest = await this.resolver.fetchLatestVersion(frameworkRepo).catch(() => "");
|
|
5383
|
+
const label = latest ? `Framework release tag (latest: ${latest}):` : "Framework release tag:";
|
|
5384
|
+
return await this.prompter.input(label, latest) || latest || void 0;
|
|
5385
|
+
}
|
|
5386
|
+
return this.resolver.fetchLatestVersion(frameworkRepo).catch(() => void 0);
|
|
5387
|
+
}
|
|
5076
5388
|
async handleAdopt(options) {
|
|
5077
5389
|
const { projectRoot, repo } = options;
|
|
5390
|
+
this.validateAdoptNonInteractive(options, repo);
|
|
5391
|
+
const selected = await this.resolveAdoptTools(options);
|
|
5392
|
+
const fromInput = await this.resolveAdoptFrom(options, repo);
|
|
5393
|
+
const { path: frameworkPath, version } = await this.frameworkResolver.execute({
|
|
5394
|
+
from: fromInput
|
|
5395
|
+
});
|
|
5396
|
+
const adoptResult = await this.runAdopt(
|
|
5397
|
+
selected,
|
|
5398
|
+
frameworkPath,
|
|
5399
|
+
projectRoot,
|
|
5400
|
+
version
|
|
5401
|
+
);
|
|
5402
|
+
return {
|
|
5403
|
+
kind: "adopted",
|
|
5404
|
+
version,
|
|
5405
|
+
toolCount: adoptResult.tools.length,
|
|
5406
|
+
totalRegistered: adoptResult.totalRegistered,
|
|
5407
|
+
docsRegistered: adoptResult.docsRegistered
|
|
5408
|
+
};
|
|
5409
|
+
}
|
|
5410
|
+
validateAdoptNonInteractive(options, repo) {
|
|
5078
5411
|
if (!options.interactive) {
|
|
5079
5412
|
if (!options.toolIds || options.toolIds.length === 0) {
|
|
5080
5413
|
throw new Error("--tools <ids> is required for adopt in non-interactive mode.");
|
|
5081
5414
|
}
|
|
5082
|
-
if (options.from === void 0)
|
|
5083
|
-
throw new AdoptRequiresVersionError(repo);
|
|
5084
|
-
}
|
|
5085
|
-
}
|
|
5086
|
-
let selected;
|
|
5087
|
-
if (options.toolIds !== void 0 && options.toolIds.length > 0) {
|
|
5088
|
-
selected = options.toolIds;
|
|
5089
|
-
} else {
|
|
5090
|
-
const choices = VALID_TOOL_IDS.map((id) => ({ name: id, value: id, checked: false }));
|
|
5091
|
-
const checkedIds = await this.prompter.checkbox("Which tools do you want to adopt?", choices);
|
|
5092
|
-
if (checkedIds.length === 0) throw new Error("No tools selected.");
|
|
5093
|
-
selected = checkedIds;
|
|
5415
|
+
if (options.from === void 0) throw new AdoptRequiresVersionError(repo);
|
|
5094
5416
|
}
|
|
5095
|
-
|
|
5096
|
-
|
|
5097
|
-
|
|
5098
|
-
if (!fromInput) throw new AdoptRequiresVersionError(repo);
|
|
5099
|
-
} else {
|
|
5100
|
-
fromInput = await this.prompter.input(
|
|
5101
|
-
"Which version of the framework do you already have installed? (e.g. v1.2.3 or local path):",
|
|
5102
|
-
""
|
|
5103
|
-
);
|
|
5104
|
-
if (!fromInput) throw new AdoptRequiresVersionError(repo);
|
|
5105
|
-
}
|
|
5106
|
-
const { path: frameworkPath, version } = await this.frameworkResolver.execute({
|
|
5107
|
-
from: fromInput
|
|
5108
|
-
});
|
|
5109
|
-
const adoptResult = await new AdoptUseCase(
|
|
5417
|
+
}
|
|
5418
|
+
async runAdopt(toolIds, frameworkPath, projectRoot, version) {
|
|
5419
|
+
return new AdoptUseCase(
|
|
5110
5420
|
this.fs,
|
|
5111
5421
|
this.manifestRepo,
|
|
5112
5422
|
this.loader,
|
|
@@ -5114,19 +5424,33 @@ var SetupUseCase = class {
|
|
|
5114
5424
|
this.logger,
|
|
5115
5425
|
this.platform
|
|
5116
5426
|
).execute({
|
|
5117
|
-
toolIds
|
|
5427
|
+
toolIds,
|
|
5118
5428
|
frameworkPath,
|
|
5119
5429
|
docsDir: Manifest.DEFAULT_DOCS_DIR,
|
|
5120
5430
|
projectRoot,
|
|
5121
5431
|
version
|
|
5122
5432
|
});
|
|
5123
|
-
|
|
5124
|
-
|
|
5125
|
-
|
|
5126
|
-
|
|
5127
|
-
|
|
5128
|
-
|
|
5129
|
-
|
|
5433
|
+
}
|
|
5434
|
+
async resolveAdoptTools(options) {
|
|
5435
|
+
if (options.toolIds !== void 0 && options.toolIds.length > 0) {
|
|
5436
|
+
return options.toolIds;
|
|
5437
|
+
}
|
|
5438
|
+
const choices = VALID_TOOL_IDS.map((id) => ({ name: id, value: id, checked: false }));
|
|
5439
|
+
const checkedIds = await this.prompter.checkbox("Which tools do you want to adopt?", choices);
|
|
5440
|
+
if (checkedIds.length === 0) throw new Error("No tools selected.");
|
|
5441
|
+
return checkedIds;
|
|
5442
|
+
}
|
|
5443
|
+
async resolveAdoptFrom(options, repo) {
|
|
5444
|
+
if (options.from !== void 0) {
|
|
5445
|
+
if (!options.from) throw new AdoptRequiresVersionError(repo);
|
|
5446
|
+
return options.from;
|
|
5447
|
+
}
|
|
5448
|
+
const fromInput = await this.prompter.input(
|
|
5449
|
+
"Which version of the framework do you already have installed? (e.g. v1.2.3 or local path):",
|
|
5450
|
+
""
|
|
5451
|
+
);
|
|
5452
|
+
if (!fromInput) throw new AdoptRequiresVersionError(repo);
|
|
5453
|
+
return fromInput;
|
|
5130
5454
|
}
|
|
5131
5455
|
async handleInstall(options) {
|
|
5132
5456
|
const { projectRoot, path, release, repo } = options;
|
|
@@ -5168,9 +5492,17 @@ var SetupUseCase = class {
|
|
|
5168
5492
|
interactive: options.interactive ?? false,
|
|
5169
5493
|
repo
|
|
5170
5494
|
});
|
|
5171
|
-
if (updateResult.cancelled) {
|
|
5172
|
-
|
|
5173
|
-
|
|
5495
|
+
if (updateResult.cancelled) return { kind: "update-cancelled" };
|
|
5496
|
+
return this.buildUpdateResult(
|
|
5497
|
+
updateResult,
|
|
5498
|
+
frameworkPath,
|
|
5499
|
+
version,
|
|
5500
|
+
projectRoot,
|
|
5501
|
+
repo,
|
|
5502
|
+
options.interactive
|
|
5503
|
+
);
|
|
5504
|
+
}
|
|
5505
|
+
async buildUpdateResult(updateResult, frameworkPath, version, projectRoot, repo, interactive) {
|
|
5174
5506
|
const updatedManifest = await this.manifestRepo.load();
|
|
5175
5507
|
const updatedInstalledIds = updatedManifest?.getInstalledToolIds() ?? [];
|
|
5176
5508
|
const missingTools = VALID_TOOL_IDS.filter((id) => !updatedInstalledIds.includes(id));
|
|
@@ -5180,7 +5512,7 @@ var SetupUseCase = class {
|
|
|
5180
5512
|
version,
|
|
5181
5513
|
projectRoot,
|
|
5182
5514
|
repo,
|
|
5183
|
-
|
|
5515
|
+
interactive
|
|
5184
5516
|
);
|
|
5185
5517
|
return {
|
|
5186
5518
|
kind: "updated",
|
|
@@ -5196,16 +5528,13 @@ var SetupUseCase = class {
|
|
|
5196
5528
|
const manifest = await this.manifestRepo.load();
|
|
5197
5529
|
const installedIds = manifest?.getInstalledToolIds() ?? [];
|
|
5198
5530
|
const missingTools = VALID_TOOL_IDS.filter((id) => !installedIds.includes(id));
|
|
5199
|
-
if (missingTools.length === 0) {
|
|
5200
|
-
|
|
5201
|
-
}
|
|
5202
|
-
if (!options.interactive) {
|
|
5203
|
-
return { kind: "up-to-date", hasAdditionalTools: true };
|
|
5204
|
-
}
|
|
5531
|
+
if (missingTools.length === 0) return { kind: "up-to-date", hasAdditionalTools: false };
|
|
5532
|
+
if (!options.interactive) return { kind: "up-to-date", hasAdditionalTools: true };
|
|
5205
5533
|
const wantsMore = await this.prompter.confirm("Install additional tools?");
|
|
5206
|
-
if (!wantsMore) {
|
|
5207
|
-
|
|
5208
|
-
|
|
5534
|
+
if (!wantsMore) return { kind: "up-to-date", hasAdditionalTools: true };
|
|
5535
|
+
return this.installAdditionalTools(path, release, repo, projectRoot);
|
|
5536
|
+
}
|
|
5537
|
+
async installAdditionalTools(path, release, repo, projectRoot) {
|
|
5209
5538
|
const { path: frameworkPath, version } = await this.frameworkResolver.execute({
|
|
5210
5539
|
path,
|
|
5211
5540
|
release,
|
|
@@ -5264,30 +5593,6 @@ var SetupUseCase = class {
|
|
|
5264
5593
|
});
|
|
5265
5594
|
}
|
|
5266
5595
|
};
|
|
5267
|
-
async function detectSetupState(manifestRepo, fs, resolver, projectRoot) {
|
|
5268
|
-
const manifest = await manifestRepo.load();
|
|
5269
|
-
if (manifest === null) {
|
|
5270
|
-
for (const tool of getAllRegisteredTools().values()) {
|
|
5271
|
-
if (await hasToolSignals(fs, tool, projectRoot)) return { kind: "needs-adopt" };
|
|
5272
|
-
}
|
|
5273
|
-
return { kind: "needs-init" };
|
|
5274
|
-
}
|
|
5275
|
-
const installedIds = manifest.getInstalledToolIds();
|
|
5276
|
-
if (installedIds.length === 0) {
|
|
5277
|
-
return { kind: "needs-install" };
|
|
5278
|
-
}
|
|
5279
|
-
try {
|
|
5280
|
-
const latestVersion = await resolver.fetchLatestVersion(manifest.repo);
|
|
5281
|
-
const installedVersions = installedIds.map((id) => manifest.getToolVersion(id)).filter((v) => v !== void 0);
|
|
5282
|
-
const currentVersion2 = installedVersions[0] ?? "unknown";
|
|
5283
|
-
const needsUpdate = isSemver(latestVersion) && installedVersions.some((v) => !isSemver(v) || compareSemver(v, latestVersion) < 0);
|
|
5284
|
-
if (needsUpdate) {
|
|
5285
|
-
return { kind: "needs-update", currentVersion: currentVersion2, latestVersion };
|
|
5286
|
-
}
|
|
5287
|
-
} catch {
|
|
5288
|
-
}
|
|
5289
|
-
return { kind: "up-to-date" };
|
|
5290
|
-
}
|
|
5291
5596
|
|
|
5292
5597
|
// src/application/commands/setup.ts
|
|
5293
5598
|
function displayInstall(output, results, verbose) {
|
|
@@ -5427,7 +5732,7 @@ function registerStatusCommand(program2) {
|
|
|
5427
5732
|
filterDocs,
|
|
5428
5733
|
repo
|
|
5429
5734
|
});
|
|
5430
|
-
if (report.tools.length === 0 && !filterToolId) {
|
|
5735
|
+
if (report.tools.length === 0 && !filterToolId && !filterDocs) {
|
|
5431
5736
|
output.print("No tools installed. Run `aidd install <tool>` to get started.");
|
|
5432
5737
|
if (report.inSync) return;
|
|
5433
5738
|
} else if (report.inSync) {
|
|
@@ -5460,6 +5765,27 @@ docs (v${report.docs.version}):`);
|
|
|
5460
5765
|
|
|
5461
5766
|
// src/application/use-cases/sync-use-case.ts
|
|
5462
5767
|
import { join as join26 } from "path";
|
|
5768
|
+
|
|
5769
|
+
// src/domain/models/sync-exclusions.ts
|
|
5770
|
+
var SYNC_EXCLUDED_FILES = /* @__PURE__ */ new Set([
|
|
5771
|
+
"CLAUDE.md",
|
|
5772
|
+
"AGENTS.md",
|
|
5773
|
+
".github/copilot-instructions.md",
|
|
5774
|
+
".mcp.json",
|
|
5775
|
+
".cursor/mcp.json",
|
|
5776
|
+
".vscode/mcp.json",
|
|
5777
|
+
"opencode.json",
|
|
5778
|
+
"opencode.jsonc"
|
|
5779
|
+
]);
|
|
5780
|
+
function isSyncExcluded(relativePath, docsDir) {
|
|
5781
|
+
if (SYNC_EXCLUDED_FILES.has(relativePath)) return true;
|
|
5782
|
+
if (relativePath.startsWith(".vscode/")) return true;
|
|
5783
|
+
if (relativePath.startsWith(`${docsDir}/`)) return true;
|
|
5784
|
+
if (relativePath.startsWith(".aidd/")) return true;
|
|
5785
|
+
return false;
|
|
5786
|
+
}
|
|
5787
|
+
|
|
5788
|
+
// src/application/use-cases/sync-use-case.ts
|
|
5463
5789
|
function getSectionKeyFromFrameworkPath(frameworkPath) {
|
|
5464
5790
|
if (frameworkPath.startsWith("agents/"))
|
|
5465
5791
|
return { section: "agents", key: frameworkPath.slice("agents/".length) };
|
|
@@ -5482,23 +5808,6 @@ function transformContent(content, sourceConfig, targetConfig, sectionKey, docsD
|
|
|
5482
5808
|
const targetBody = targetConfig.rewriteContent(canonicalBody, docsDir);
|
|
5483
5809
|
return serializeFrontmatter(targetFrontmatter, targetBody);
|
|
5484
5810
|
}
|
|
5485
|
-
var EXCLUDED_FILES = /* @__PURE__ */ new Set([
|
|
5486
|
-
"CLAUDE.md",
|
|
5487
|
-
"AGENTS.md",
|
|
5488
|
-
".github/copilot-instructions.md",
|
|
5489
|
-
".mcp.json",
|
|
5490
|
-
".cursor/mcp.json",
|
|
5491
|
-
".vscode/mcp.json",
|
|
5492
|
-
"opencode.json",
|
|
5493
|
-
"opencode.jsonc"
|
|
5494
|
-
]);
|
|
5495
|
-
function isExcluded(relativePath, docsDir) {
|
|
5496
|
-
if (EXCLUDED_FILES.has(relativePath)) return true;
|
|
5497
|
-
if (relativePath.startsWith(".vscode/")) return true;
|
|
5498
|
-
if (relativePath.startsWith(`${docsDir}/`)) return true;
|
|
5499
|
-
if (relativePath.startsWith(".aidd/")) return true;
|
|
5500
|
-
return false;
|
|
5501
|
-
}
|
|
5502
5811
|
function buildTargetPath(targetConfig, sectionKey) {
|
|
5503
5812
|
return targetConfig[sectionKey.section]().buildFilePath(sectionKey.key);
|
|
5504
5813
|
}
|
|
@@ -5512,143 +5821,166 @@ var SyncUseCase = class {
|
|
|
5512
5821
|
}
|
|
5513
5822
|
async execute(options) {
|
|
5514
5823
|
const { projectRoot, force = false, includeUserFiles = false, repo } = options;
|
|
5515
|
-
const interactive = options.interactive ?? false;
|
|
5516
5824
|
const manifest = await this.manifestRepo.load();
|
|
5517
|
-
if (manifest === null)
|
|
5518
|
-
throw new NoManifestError(repo);
|
|
5519
|
-
}
|
|
5825
|
+
if (manifest === null) throw new NoManifestError(repo);
|
|
5520
5826
|
const docsDir = options.docsDir ?? manifest.docsDir;
|
|
5521
|
-
|
|
5522
|
-
|
|
5523
|
-
|
|
5524
|
-
|
|
5525
|
-
|
|
5526
|
-
}
|
|
5527
|
-
const { SyncStatusUseCase: SyncStatusUseCase2 } = await Promise.resolve().then(() => (init_sync_status_use_case(), sync_status_use_case_exports));
|
|
5528
|
-
const installedIds = manifest.getInstalledToolIds();
|
|
5529
|
-
if (installedIds.length < 2) {
|
|
5530
|
-
throw new Error("Sync requires at least 2 installed tools.");
|
|
5531
|
-
}
|
|
5532
|
-
const modCounts = await new SyncStatusUseCase2(this.fs).execute(
|
|
5533
|
-
manifest,
|
|
5534
|
-
installedIds,
|
|
5535
|
-
projectRoot
|
|
5536
|
-
);
|
|
5537
|
-
const hasAnyChanges = installedIds.some((id) => {
|
|
5538
|
-
const { modified, deleted } = modCounts[id] ?? { modified: 0, deleted: 0 };
|
|
5539
|
-
return modified > 0 || deleted > 0;
|
|
5540
|
-
});
|
|
5541
|
-
if (!hasAnyChanges) {
|
|
5542
|
-
return {
|
|
5543
|
-
sourceTool: installedIds[0],
|
|
5544
|
-
tools: [],
|
|
5545
|
-
totalWritten: 0,
|
|
5546
|
-
totalDeleted: 0,
|
|
5547
|
-
totalConflicts: 0,
|
|
5548
|
-
totalSkipped: 0
|
|
5549
|
-
};
|
|
5550
|
-
}
|
|
5551
|
-
const sourceChoices = installedIds.map((id) => {
|
|
5552
|
-
const { modified, deleted } = modCounts[id] ?? { modified: 0, deleted: 0 };
|
|
5553
|
-
const hasChanges = modified > 0 || deleted > 0;
|
|
5554
|
-
const parts = [];
|
|
5555
|
-
if (modified > 0) parts.push(`${modified} modified`);
|
|
5556
|
-
if (deleted > 0) parts.push(`${deleted} deleted`);
|
|
5557
|
-
const label = hasChanges ? ` (${parts.join(", ")})` : "";
|
|
5558
|
-
return {
|
|
5559
|
-
name: `${id}${label}`,
|
|
5560
|
-
value: id,
|
|
5561
|
-
disabled: hasChanges ? false : "(no changes)"
|
|
5562
|
-
};
|
|
5563
|
-
});
|
|
5564
|
-
sourceTool = await this.prompter.select("Source tool to sync from?", sourceChoices);
|
|
5565
|
-
const targetChoices = installedIds.filter((id) => id !== sourceTool).map((id) => ({ name: id, value: id }));
|
|
5566
|
-
targetTools = await this.prompter.checkbox("Target tools?", targetChoices);
|
|
5567
|
-
if (targetTools.length === 0) throw new Error("No target tools selected.");
|
|
5568
|
-
} else {
|
|
5569
|
-
sourceTool = options.sourceTool;
|
|
5570
|
-
if (!manifest.hasTool(sourceTool)) {
|
|
5571
|
-
throw new Error(`Source tool '${sourceTool}' is not installed.`);
|
|
5572
|
-
}
|
|
5573
|
-
const installedToolIds = manifest.getInstalledToolIds();
|
|
5574
|
-
if (installedToolIds.length < 2) {
|
|
5575
|
-
throw new Error("Sync requires at least 2 installed tools.");
|
|
5576
|
-
}
|
|
5577
|
-
targetTools = targetTools && targetTools.length > 0 ? targetTools : installedToolIds.filter((id) => id !== sourceTool);
|
|
5578
|
-
for (const target of targetTools) {
|
|
5579
|
-
if (target === sourceTool) {
|
|
5580
|
-
throw new Error("Source and target cannot be the same tool.");
|
|
5581
|
-
}
|
|
5582
|
-
if (!manifest.hasTool(target)) {
|
|
5583
|
-
throw new Error(`Target tool '${target}' is not installed.`);
|
|
5584
|
-
}
|
|
5585
|
-
}
|
|
5586
|
-
}
|
|
5827
|
+
const { sourceTool, targetTools } = await this.selectSyncScope(
|
|
5828
|
+
options,
|
|
5829
|
+
manifest,
|
|
5830
|
+
options.interactive ?? false
|
|
5831
|
+
);
|
|
5587
5832
|
const sourceConfig = getToolConfig(sourceTool);
|
|
5588
5833
|
const sourceManifestFiles = manifest.getToolFiles(sourceTool);
|
|
5589
5834
|
const sourceManifestMap = new Map(sourceManifestFiles.map((f) => [f.relativePath, f.hash]));
|
|
5835
|
+
const toolResults = await this.syncAllTargets(
|
|
5836
|
+
targetTools,
|
|
5837
|
+
sourceTool,
|
|
5838
|
+
sourceConfig,
|
|
5839
|
+
sourceManifestFiles,
|
|
5840
|
+
sourceManifestMap,
|
|
5841
|
+
manifest,
|
|
5842
|
+
projectRoot,
|
|
5843
|
+
docsDir,
|
|
5844
|
+
force,
|
|
5845
|
+
includeUserFiles
|
|
5846
|
+
);
|
|
5847
|
+
return this.buildSyncTotals(sourceTool, toolResults);
|
|
5848
|
+
}
|
|
5849
|
+
async syncAllTargets(targetTools, sourceTool, sourceConfig, sourceManifestFiles, sourceManifestMap, manifest, projectRoot, docsDir, force, includeUserFiles) {
|
|
5590
5850
|
const toolResults = [];
|
|
5591
|
-
for (const targetToolId of targetTools
|
|
5851
|
+
for (const targetToolId of targetTools) {
|
|
5592
5852
|
this.logger.info(`Syncing ${sourceTool} \u2192 ${targetToolId}...`);
|
|
5593
|
-
const
|
|
5594
|
-
|
|
5595
|
-
const targetManifestMap = new Map(targetManifestFiles.map((f) => [f.relativePath, f.hash]));
|
|
5596
|
-
const targetByFrameworkPath = new Map(
|
|
5597
|
-
targetManifestFiles.filter((f) => f.frameworkPath !== void 0).map((f) => [f.frameworkPath, f.relativePath])
|
|
5598
|
-
);
|
|
5599
|
-
const fileResults = [];
|
|
5600
|
-
await this.propagateModified({
|
|
5601
|
-
sourceManifestFiles,
|
|
5853
|
+
const result = await this.syncOneTool(
|
|
5854
|
+
targetToolId,
|
|
5602
5855
|
sourceConfig,
|
|
5603
|
-
|
|
5604
|
-
targetManifestMap,
|
|
5605
|
-
targetByFrameworkPath,
|
|
5606
|
-
fileResults,
|
|
5607
|
-
projectRoot,
|
|
5608
|
-
docsDir,
|
|
5609
|
-
force
|
|
5610
|
-
});
|
|
5611
|
-
await this.propagateAdded({
|
|
5856
|
+
sourceManifestFiles,
|
|
5612
5857
|
sourceManifestMap,
|
|
5613
|
-
|
|
5614
|
-
targetConfig,
|
|
5615
|
-
fileResults,
|
|
5858
|
+
manifest,
|
|
5616
5859
|
projectRoot,
|
|
5617
5860
|
docsDir,
|
|
5861
|
+
force,
|
|
5618
5862
|
includeUserFiles
|
|
5619
|
-
|
|
5620
|
-
|
|
5621
|
-
sourceManifestFiles,
|
|
5622
|
-
targetByFrameworkPath,
|
|
5623
|
-
fileResults,
|
|
5624
|
-
projectRoot,
|
|
5625
|
-
docsDir
|
|
5626
|
-
});
|
|
5627
|
-
toolResults.push({ targetToolId, files: fileResults });
|
|
5863
|
+
);
|
|
5864
|
+
toolResults.push(result);
|
|
5628
5865
|
}
|
|
5629
|
-
|
|
5630
|
-
|
|
5631
|
-
|
|
5632
|
-
);
|
|
5633
|
-
const
|
|
5634
|
-
|
|
5635
|
-
|
|
5636
|
-
|
|
5637
|
-
const totalConflicts = toolResults.reduce(
|
|
5638
|
-
(s, t) => s + t.files.filter((f) => f.conflict && !f.written).length,
|
|
5639
|
-
0
|
|
5866
|
+
return toolResults;
|
|
5867
|
+
}
|
|
5868
|
+
async syncOneTool(targetToolId, sourceConfig, sourceManifestFiles, sourceManifestMap, manifest, projectRoot, docsDir, force, includeUserFiles) {
|
|
5869
|
+
const targetConfig = getToolConfig(targetToolId);
|
|
5870
|
+
const targetManifestFiles = manifest.getToolFiles(targetToolId);
|
|
5871
|
+
const targetManifestMap = new Map(targetManifestFiles.map((f) => [f.relativePath, f.hash]));
|
|
5872
|
+
const targetByFrameworkPath = new Map(
|
|
5873
|
+
targetManifestFiles.filter((f) => f.frameworkPath !== void 0).map((f) => [f.frameworkPath, f.relativePath])
|
|
5640
5874
|
);
|
|
5641
|
-
const
|
|
5642
|
-
|
|
5643
|
-
|
|
5875
|
+
const fileResults = [];
|
|
5876
|
+
await this.propagateModified({
|
|
5877
|
+
sourceManifestFiles,
|
|
5878
|
+
sourceConfig,
|
|
5879
|
+
targetConfig,
|
|
5880
|
+
targetManifestMap,
|
|
5881
|
+
targetByFrameworkPath,
|
|
5882
|
+
fileResults,
|
|
5883
|
+
projectRoot,
|
|
5884
|
+
docsDir,
|
|
5885
|
+
force
|
|
5886
|
+
});
|
|
5887
|
+
await this.propagateAdded({
|
|
5888
|
+
sourceManifestMap,
|
|
5889
|
+
sourceConfig,
|
|
5890
|
+
targetConfig,
|
|
5891
|
+
fileResults,
|
|
5892
|
+
projectRoot,
|
|
5893
|
+
docsDir,
|
|
5894
|
+
includeUserFiles
|
|
5895
|
+
});
|
|
5896
|
+
await this.propagateDeleted({
|
|
5897
|
+
sourceManifestFiles,
|
|
5898
|
+
targetByFrameworkPath,
|
|
5899
|
+
fileResults,
|
|
5900
|
+
projectRoot,
|
|
5901
|
+
docsDir
|
|
5902
|
+
});
|
|
5903
|
+
return { targetToolId, files: fileResults };
|
|
5904
|
+
}
|
|
5905
|
+
/** Resolves sourceTool and targetTools from options — prompts if interactive. */
|
|
5906
|
+
async selectSyncScope(options, manifest, interactive) {
|
|
5907
|
+
if (options.sourceTool !== void 0) {
|
|
5908
|
+
return this.resolveExplicitScope(options, manifest);
|
|
5909
|
+
}
|
|
5910
|
+
return this.resolveInteractiveScope(manifest, interactive, options.projectRoot);
|
|
5911
|
+
}
|
|
5912
|
+
resolveExplicitScope(options, manifest) {
|
|
5913
|
+
const sourceTool = options.sourceTool;
|
|
5914
|
+
if (!manifest.hasTool(sourceTool)) {
|
|
5915
|
+
throw new Error(`Source tool '${sourceTool}' is not installed.`);
|
|
5916
|
+
}
|
|
5917
|
+
const installedToolIds = manifest.getInstalledToolIds();
|
|
5918
|
+
if (installedToolIds.length < 2) {
|
|
5919
|
+
throw new Error("Sync requires at least 2 installed tools.");
|
|
5920
|
+
}
|
|
5921
|
+
const targetTools = options.targetTools && options.targetTools.length > 0 ? options.targetTools : installedToolIds.filter((id) => id !== sourceTool);
|
|
5922
|
+
for (const target of targetTools) {
|
|
5923
|
+
if (target === sourceTool) {
|
|
5924
|
+
throw new Error("Source and target cannot be the same tool.");
|
|
5925
|
+
}
|
|
5926
|
+
if (!manifest.hasTool(target)) {
|
|
5927
|
+
throw new Error(`Target tool '${target}' is not installed.`);
|
|
5928
|
+
}
|
|
5929
|
+
}
|
|
5930
|
+
return { sourceTool, targetTools };
|
|
5931
|
+
}
|
|
5932
|
+
async resolveInteractiveScope(manifest, interactive, projectRoot) {
|
|
5933
|
+
if (!interactive || this.prompter === void 0) {
|
|
5934
|
+
throw new Error("Source tool required in non-interactive mode.");
|
|
5935
|
+
}
|
|
5936
|
+
const { SyncStatusUseCase: SyncStatusUseCase2 } = await Promise.resolve().then(() => (init_sync_status_use_case(), sync_status_use_case_exports));
|
|
5937
|
+
const installedIds = manifest.getInstalledToolIds();
|
|
5938
|
+
if (installedIds.length < 2) {
|
|
5939
|
+
throw new Error("Sync requires at least 2 installed tools.");
|
|
5940
|
+
}
|
|
5941
|
+
const modCounts = await new SyncStatusUseCase2(this.fs).execute(
|
|
5942
|
+
manifest,
|
|
5943
|
+
installedIds,
|
|
5944
|
+
projectRoot
|
|
5644
5945
|
);
|
|
5946
|
+
const hasAnyChanges = installedIds.some((id) => {
|
|
5947
|
+
const { modified, deleted } = modCounts[id] ?? { modified: 0, deleted: 0 };
|
|
5948
|
+
return modified > 0 || deleted > 0;
|
|
5949
|
+
});
|
|
5950
|
+
if (!hasAnyChanges) {
|
|
5951
|
+
return { sourceTool: installedIds[0], targetTools: [] };
|
|
5952
|
+
}
|
|
5953
|
+
return this.promptSyncScope(installedIds, modCounts, this.prompter);
|
|
5954
|
+
}
|
|
5955
|
+
async promptSyncScope(installedIds, modCounts, prompter) {
|
|
5956
|
+
const sourceChoices = installedIds.map((id) => {
|
|
5957
|
+
const { modified, deleted } = modCounts[id] ?? { modified: 0, deleted: 0 };
|
|
5958
|
+
const hasChanges = modified > 0 || deleted > 0;
|
|
5959
|
+
const parts = [];
|
|
5960
|
+
if (modified > 0) parts.push(`${modified} modified`);
|
|
5961
|
+
if (deleted > 0) parts.push(`${deleted} deleted`);
|
|
5962
|
+
const label = hasChanges ? ` (${parts.join(", ")})` : "";
|
|
5963
|
+
return {
|
|
5964
|
+
name: `${id}${label}`,
|
|
5965
|
+
value: id,
|
|
5966
|
+
disabled: hasChanges ? false : "(no changes)"
|
|
5967
|
+
};
|
|
5968
|
+
});
|
|
5969
|
+
const sourceTool = await prompter.select("Source tool to sync from?", sourceChoices);
|
|
5970
|
+
const targetChoices = installedIds.filter((id) => id !== sourceTool).map((id) => ({ name: id, value: id }));
|
|
5971
|
+
const targetTools = await prompter.checkbox("Target tools?", targetChoices);
|
|
5972
|
+
if (targetTools.length === 0) throw new Error("No target tools selected.");
|
|
5973
|
+
return { sourceTool, targetTools };
|
|
5974
|
+
}
|
|
5975
|
+
buildSyncTotals(sourceTool, toolResults) {
|
|
5976
|
+
const count = (pred) => toolResults.reduce((s, t) => s + t.files.filter(pred).length, 0);
|
|
5645
5977
|
return {
|
|
5646
5978
|
sourceTool,
|
|
5647
5979
|
tools: toolResults,
|
|
5648
|
-
totalWritten,
|
|
5649
|
-
totalDeleted,
|
|
5650
|
-
totalConflicts,
|
|
5651
|
-
totalSkipped
|
|
5980
|
+
totalWritten: count((f) => f.written),
|
|
5981
|
+
totalDeleted: count((f) => Boolean(f.deleted)),
|
|
5982
|
+
totalConflicts: count((f) => f.conflict && !f.written),
|
|
5983
|
+
totalSkipped: count((f) => f.skipped)
|
|
5652
5984
|
};
|
|
5653
5985
|
}
|
|
5654
5986
|
async propagateModified(ctx) {
|
|
@@ -5664,64 +5996,71 @@ var SyncUseCase = class {
|
|
|
5664
5996
|
force
|
|
5665
5997
|
} = ctx;
|
|
5666
5998
|
for (const sourceManifestFile of sourceManifestFiles) {
|
|
5667
|
-
|
|
5668
|
-
|
|
5669
|
-
if (frameworkPath === void 0) continue;
|
|
5670
|
-
const diskSourcePath = join26(projectRoot, relativePath);
|
|
5671
|
-
if (!await this.fs.fileExists(diskSourcePath)) continue;
|
|
5672
|
-
const diskSourceHash = await this.fs.readFileHash(diskSourcePath);
|
|
5673
|
-
if (diskSourceHash.value === manifestHash.value) continue;
|
|
5674
|
-
const sectionKey = getSectionKeyFromFrameworkPath(frameworkPath);
|
|
5675
|
-
if (sectionKey === null) continue;
|
|
5676
|
-
const targetRelativePath = targetByFrameworkPath.get(frameworkPath);
|
|
5677
|
-
if (targetRelativePath === void 0) continue;
|
|
5678
|
-
const diskSourceContent = await this.fs.readFile(diskSourcePath);
|
|
5679
|
-
const targetContent = transformContent(
|
|
5680
|
-
diskSourceContent,
|
|
5999
|
+
await this.propagateOneModified(
|
|
6000
|
+
sourceManifestFile,
|
|
5681
6001
|
sourceConfig,
|
|
5682
6002
|
targetConfig,
|
|
5683
|
-
|
|
5684
|
-
|
|
6003
|
+
targetManifestMap,
|
|
6004
|
+
targetByFrameworkPath,
|
|
6005
|
+
fileResults,
|
|
6006
|
+
projectRoot,
|
|
6007
|
+
docsDir,
|
|
6008
|
+
force
|
|
5685
6009
|
);
|
|
5686
|
-
|
|
5687
|
-
|
|
5688
|
-
|
|
5689
|
-
|
|
5690
|
-
|
|
5691
|
-
|
|
5692
|
-
|
|
5693
|
-
|
|
5694
|
-
|
|
5695
|
-
|
|
5696
|
-
|
|
5697
|
-
|
|
5698
|
-
|
|
5699
|
-
|
|
5700
|
-
|
|
5701
|
-
|
|
5702
|
-
|
|
5703
|
-
|
|
5704
|
-
|
|
5705
|
-
|
|
5706
|
-
|
|
5707
|
-
|
|
5708
|
-
|
|
5709
|
-
|
|
5710
|
-
|
|
5711
|
-
|
|
5712
|
-
|
|
5713
|
-
|
|
5714
|
-
|
|
5715
|
-
|
|
5716
|
-
|
|
5717
|
-
|
|
6010
|
+
}
|
|
6011
|
+
}
|
|
6012
|
+
async propagateOneModified(sourceManifestFile, sourceConfig, targetConfig, targetManifestMap, targetByFrameworkPath, fileResults, projectRoot, docsDir, force) {
|
|
6013
|
+
const { relativePath, hash: manifestHash, frameworkPath } = sourceManifestFile;
|
|
6014
|
+
if (isSyncExcluded(relativePath, docsDir) || frameworkPath === void 0) return;
|
|
6015
|
+
const diskSourcePath = join26(projectRoot, relativePath);
|
|
6016
|
+
if (!await this.fs.fileExists(diskSourcePath)) return;
|
|
6017
|
+
const diskSourceHash = await this.fs.readFileHash(diskSourcePath);
|
|
6018
|
+
if (diskSourceHash.value === manifestHash.value) return;
|
|
6019
|
+
const sectionKey = getSectionKeyFromFrameworkPath(frameworkPath);
|
|
6020
|
+
const targetRelativePath = targetByFrameworkPath.get(frameworkPath);
|
|
6021
|
+
if (sectionKey === null || targetRelativePath === void 0) return;
|
|
6022
|
+
const diskSourceContent = await this.fs.readFile(diskSourcePath);
|
|
6023
|
+
const targetContent = transformContent(
|
|
6024
|
+
diskSourceContent,
|
|
6025
|
+
sourceConfig,
|
|
6026
|
+
targetConfig,
|
|
6027
|
+
sectionKey,
|
|
6028
|
+
docsDir
|
|
6029
|
+
);
|
|
6030
|
+
const diskTargetPath = join26(projectRoot, targetRelativePath);
|
|
6031
|
+
const diskTargetExists = await this.fs.fileExists(diskTargetPath);
|
|
6032
|
+
const conflict = await this.detectTargetConflict(
|
|
6033
|
+
diskTargetExists,
|
|
6034
|
+
diskTargetPath,
|
|
6035
|
+
targetRelativePath,
|
|
6036
|
+
targetManifestMap
|
|
6037
|
+
);
|
|
6038
|
+
if (diskTargetExists && await this.fs.readFile(diskTargetPath) === targetContent) {
|
|
6039
|
+
fileResults.push({
|
|
6040
|
+
relativePath: targetRelativePath,
|
|
6041
|
+
conflict: false,
|
|
6042
|
+
skipped: true,
|
|
6043
|
+
written: false
|
|
6044
|
+
});
|
|
6045
|
+
return;
|
|
6046
|
+
}
|
|
6047
|
+
if (conflict && !force) {
|
|
5718
6048
|
fileResults.push({
|
|
5719
6049
|
relativePath: targetRelativePath,
|
|
5720
|
-
conflict,
|
|
6050
|
+
conflict: true,
|
|
5721
6051
|
skipped: false,
|
|
5722
|
-
written:
|
|
6052
|
+
written: false
|
|
5723
6053
|
});
|
|
6054
|
+
return;
|
|
5724
6055
|
}
|
|
6056
|
+
await this.fs.writeFile(diskTargetPath, targetContent);
|
|
6057
|
+
fileResults.push({ relativePath: targetRelativePath, conflict, skipped: false, written: true });
|
|
6058
|
+
}
|
|
6059
|
+
async detectTargetConflict(diskTargetExists, diskTargetPath, targetRelativePath, targetManifestMap) {
|
|
6060
|
+
if (!diskTargetExists) return false;
|
|
6061
|
+
const diskTargetHash = await this.fs.readFileHash(diskTargetPath);
|
|
6062
|
+
const targetManifestHash = targetManifestMap.get(targetRelativePath);
|
|
6063
|
+
return targetManifestHash !== void 0 && diskTargetHash.value !== targetManifestHash.value;
|
|
5725
6064
|
}
|
|
5726
6065
|
async propagateAdded(ctx) {
|
|
5727
6066
|
const {
|
|
@@ -5738,65 +6077,80 @@ var SyncUseCase = class {
|
|
|
5738
6077
|
if (!sourceDirExists) return;
|
|
5739
6078
|
const sourceDiskFiles = await this.fs.listDirectory(join26(projectRoot, sourceConfig.directory));
|
|
5740
6079
|
for (const diskRelative of sourceDiskFiles) {
|
|
5741
|
-
|
|
5742
|
-
|
|
5743
|
-
|
|
5744
|
-
const sectionKey = sourceConfig.detectUserFileSectionKey(sourceRelativePath);
|
|
5745
|
-
if (sectionKey === null) continue;
|
|
5746
|
-
const targetRelativePath = buildTargetPath(targetConfig, sectionKey);
|
|
5747
|
-
if (targetRelativePath === null) continue;
|
|
5748
|
-
const diskSourceContent = await this.fs.readFile(join26(projectRoot, sourceRelativePath));
|
|
5749
|
-
const targetContent = transformContent(
|
|
5750
|
-
diskSourceContent,
|
|
6080
|
+
await this.propagateOneAdded(
|
|
6081
|
+
diskRelative,
|
|
6082
|
+
sourceManifestMap,
|
|
5751
6083
|
sourceConfig,
|
|
5752
6084
|
targetConfig,
|
|
5753
|
-
|
|
6085
|
+
fileResults,
|
|
6086
|
+
projectRoot,
|
|
5754
6087
|
docsDir
|
|
5755
6088
|
);
|
|
5756
|
-
|
|
5757
|
-
|
|
5758
|
-
|
|
5759
|
-
|
|
5760
|
-
|
|
5761
|
-
|
|
5762
|
-
|
|
5763
|
-
|
|
5764
|
-
|
|
5765
|
-
|
|
5766
|
-
|
|
5767
|
-
|
|
5768
|
-
|
|
5769
|
-
|
|
5770
|
-
|
|
6089
|
+
}
|
|
6090
|
+
}
|
|
6091
|
+
async propagateOneAdded(diskRelative, sourceManifestMap, sourceConfig, targetConfig, fileResults, projectRoot, docsDir) {
|
|
6092
|
+
const sourceRelativePath = `${sourceConfig.directory}${diskRelative}`;
|
|
6093
|
+
if (isSyncExcluded(sourceRelativePath, docsDir) || sourceManifestMap.has(sourceRelativePath))
|
|
6094
|
+
return;
|
|
6095
|
+
const sectionKey = sourceConfig.detectUserFileSectionKey(sourceRelativePath);
|
|
6096
|
+
if (sectionKey === null) return;
|
|
6097
|
+
const targetRelativePath = buildTargetPath(targetConfig, sectionKey);
|
|
6098
|
+
if (targetRelativePath === null) return;
|
|
6099
|
+
const diskSourceContent = await this.fs.readFile(join26(projectRoot, sourceRelativePath));
|
|
6100
|
+
const targetContent = transformContent(
|
|
6101
|
+
diskSourceContent,
|
|
6102
|
+
sourceConfig,
|
|
6103
|
+
targetConfig,
|
|
6104
|
+
sectionKey,
|
|
6105
|
+
docsDir
|
|
6106
|
+
);
|
|
6107
|
+
const diskTargetPath = join26(projectRoot, targetRelativePath);
|
|
6108
|
+
if (await this.fs.fileExists(diskTargetPath) && await this.fs.readFile(diskTargetPath) === targetContent) {
|
|
5771
6109
|
fileResults.push({
|
|
5772
6110
|
relativePath: targetRelativePath,
|
|
5773
6111
|
conflict: false,
|
|
5774
|
-
skipped:
|
|
5775
|
-
written:
|
|
6112
|
+
skipped: true,
|
|
6113
|
+
written: false
|
|
5776
6114
|
});
|
|
6115
|
+
return;
|
|
5777
6116
|
}
|
|
6117
|
+
await this.fs.writeFile(diskTargetPath, targetContent);
|
|
6118
|
+
fileResults.push({
|
|
6119
|
+
relativePath: targetRelativePath,
|
|
6120
|
+
conflict: false,
|
|
6121
|
+
skipped: false,
|
|
6122
|
+
written: true
|
|
6123
|
+
});
|
|
5778
6124
|
}
|
|
5779
6125
|
async propagateDeleted(ctx) {
|
|
5780
6126
|
const { sourceManifestFiles, targetByFrameworkPath, fileResults, projectRoot, docsDir } = ctx;
|
|
5781
6127
|
for (const sourceManifestFile of sourceManifestFiles) {
|
|
5782
|
-
|
|
5783
|
-
|
|
5784
|
-
|
|
5785
|
-
|
|
5786
|
-
|
|
5787
|
-
|
|
5788
|
-
|
|
5789
|
-
if (!await this.fs.fileExists(diskTargetPath)) continue;
|
|
5790
|
-
await this.fs.deleteFile(diskTargetPath);
|
|
5791
|
-
fileResults.push({
|
|
5792
|
-
relativePath: targetRelativePath,
|
|
5793
|
-
conflict: false,
|
|
5794
|
-
skipped: false,
|
|
5795
|
-
written: false,
|
|
5796
|
-
deleted: true
|
|
5797
|
-
});
|
|
6128
|
+
await this.propagateOneDeleted(
|
|
6129
|
+
sourceManifestFile,
|
|
6130
|
+
targetByFrameworkPath,
|
|
6131
|
+
fileResults,
|
|
6132
|
+
projectRoot,
|
|
6133
|
+
docsDir
|
|
6134
|
+
);
|
|
5798
6135
|
}
|
|
5799
6136
|
}
|
|
6137
|
+
async propagateOneDeleted(sourceManifestFile, targetByFrameworkPath, fileResults, projectRoot, docsDir) {
|
|
6138
|
+
const { relativePath, frameworkPath } = sourceManifestFile;
|
|
6139
|
+
if (isSyncExcluded(relativePath, docsDir) || frameworkPath === void 0) return;
|
|
6140
|
+
if (await this.fs.fileExists(join26(projectRoot, relativePath))) return;
|
|
6141
|
+
const targetRelativePath = targetByFrameworkPath.get(frameworkPath);
|
|
6142
|
+
if (targetRelativePath === void 0) return;
|
|
6143
|
+
const diskTargetPath = join26(projectRoot, targetRelativePath);
|
|
6144
|
+
if (!await this.fs.fileExists(diskTargetPath)) return;
|
|
6145
|
+
await this.fs.deleteFile(diskTargetPath);
|
|
6146
|
+
fileResults.push({
|
|
6147
|
+
relativePath: targetRelativePath,
|
|
6148
|
+
conflict: false,
|
|
6149
|
+
skipped: false,
|
|
6150
|
+
written: false,
|
|
6151
|
+
deleted: true
|
|
6152
|
+
});
|
|
6153
|
+
}
|
|
5800
6154
|
};
|
|
5801
6155
|
|
|
5802
6156
|
// src/application/commands/sync.ts
|
|
@@ -6212,6 +6566,230 @@ var BannerUseCase = class {
|
|
|
6212
6566
|
}
|
|
6213
6567
|
};
|
|
6214
6568
|
|
|
6569
|
+
// src/application/use-cases/interactive-menu-use-case.ts
|
|
6570
|
+
function isBranch(node) {
|
|
6571
|
+
return "children" in node;
|
|
6572
|
+
}
|
|
6573
|
+
function toChoice(node) {
|
|
6574
|
+
return { name: node.name, value: node.value, description: node.description };
|
|
6575
|
+
}
|
|
6576
|
+
var FRESH_NODES = [
|
|
6577
|
+
{
|
|
6578
|
+
name: "Install AIDD in this project",
|
|
6579
|
+
value: "setup",
|
|
6580
|
+
description: "Set up the AI-Driven Development framework",
|
|
6581
|
+
command: ["setup"]
|
|
6582
|
+
}
|
|
6583
|
+
];
|
|
6584
|
+
var INSTALLED_NODES = [
|
|
6585
|
+
{
|
|
6586
|
+
name: "Inspect",
|
|
6587
|
+
value: "inspect",
|
|
6588
|
+
description: "Check status, health and drift detection",
|
|
6589
|
+
children: [
|
|
6590
|
+
{
|
|
6591
|
+
name: "Status",
|
|
6592
|
+
value: "status",
|
|
6593
|
+
description: "Show installed files and detect drift",
|
|
6594
|
+
command: ["status"]
|
|
6595
|
+
},
|
|
6596
|
+
{
|
|
6597
|
+
name: "Doctor",
|
|
6598
|
+
value: "doctor",
|
|
6599
|
+
description: "Run a structural health check",
|
|
6600
|
+
command: ["doctor"]
|
|
6601
|
+
}
|
|
6602
|
+
]
|
|
6603
|
+
},
|
|
6604
|
+
{
|
|
6605
|
+
name: "Manage tools",
|
|
6606
|
+
value: "manage-tools",
|
|
6607
|
+
description: "Install, remove and sync AI tools",
|
|
6608
|
+
children: [
|
|
6609
|
+
{
|
|
6610
|
+
name: "Install",
|
|
6611
|
+
value: "install",
|
|
6612
|
+
description: "Add AI tools to this project",
|
|
6613
|
+
command: ["install"]
|
|
6614
|
+
},
|
|
6615
|
+
{
|
|
6616
|
+
name: "Uninstall",
|
|
6617
|
+
value: "uninstall",
|
|
6618
|
+
description: "Remove installed tools",
|
|
6619
|
+
command: ["uninstall"]
|
|
6620
|
+
},
|
|
6621
|
+
{
|
|
6622
|
+
name: "Sync",
|
|
6623
|
+
value: "sync",
|
|
6624
|
+
description: "Propagate changes across installed tools",
|
|
6625
|
+
command: ["sync"]
|
|
6626
|
+
}
|
|
6627
|
+
]
|
|
6628
|
+
},
|
|
6629
|
+
{
|
|
6630
|
+
name: "Maintain & repair",
|
|
6631
|
+
value: "maintain",
|
|
6632
|
+
description: "Update, restore and clean your files",
|
|
6633
|
+
children: [
|
|
6634
|
+
{
|
|
6635
|
+
name: "Update",
|
|
6636
|
+
value: "update",
|
|
6637
|
+
description: "Pull the latest framework version",
|
|
6638
|
+
command: ["update"]
|
|
6639
|
+
},
|
|
6640
|
+
{
|
|
6641
|
+
name: "Restore",
|
|
6642
|
+
value: "restore",
|
|
6643
|
+
description: "Restore modified or deleted tracked files",
|
|
6644
|
+
command: ["restore"]
|
|
6645
|
+
},
|
|
6646
|
+
{
|
|
6647
|
+
name: "Clean",
|
|
6648
|
+
value: "clean",
|
|
6649
|
+
description: "Remove untracked or orphaned files",
|
|
6650
|
+
command: ["clean"]
|
|
6651
|
+
}
|
|
6652
|
+
]
|
|
6653
|
+
},
|
|
6654
|
+
{
|
|
6655
|
+
name: "System",
|
|
6656
|
+
value: "system",
|
|
6657
|
+
description: "CLI updates, configuration and cache",
|
|
6658
|
+
children: [
|
|
6659
|
+
{
|
|
6660
|
+
name: "Self-update",
|
|
6661
|
+
value: "self-update",
|
|
6662
|
+
description: "Update the AIDD CLI binary",
|
|
6663
|
+
command: ["self-update"]
|
|
6664
|
+
},
|
|
6665
|
+
{
|
|
6666
|
+
name: "Config",
|
|
6667
|
+
value: "config",
|
|
6668
|
+
description: "View or edit project settings",
|
|
6669
|
+
children: [
|
|
6670
|
+
{
|
|
6671
|
+
name: "Show all settings",
|
|
6672
|
+
value: "list",
|
|
6673
|
+
description: "List all config values",
|
|
6674
|
+
command: ["config", "list"]
|
|
6675
|
+
},
|
|
6676
|
+
{
|
|
6677
|
+
name: "Get a value",
|
|
6678
|
+
value: "get",
|
|
6679
|
+
description: "Read a specific config key",
|
|
6680
|
+
children: [
|
|
6681
|
+
{ name: "Docs directory", value: "docsDir", command: ["config", "get", "docsDir"] },
|
|
6682
|
+
{ name: "Repository", value: "repo", command: ["config", "get", "repo"] },
|
|
6683
|
+
{ name: "Installed tools", value: "tools", command: ["config", "get", "tools"] }
|
|
6684
|
+
]
|
|
6685
|
+
},
|
|
6686
|
+
{
|
|
6687
|
+
name: "Set docs directory",
|
|
6688
|
+
value: "set-docs",
|
|
6689
|
+
description: "Change the docs folder name",
|
|
6690
|
+
command: ["config", "set", "docsDir"],
|
|
6691
|
+
inputPrompt: "New value for docsDir",
|
|
6692
|
+
commandSuffix: ["--force"]
|
|
6693
|
+
},
|
|
6694
|
+
{
|
|
6695
|
+
name: "Set repository",
|
|
6696
|
+
value: "set-repo",
|
|
6697
|
+
description: "Change the framework repository",
|
|
6698
|
+
command: ["config", "set", "repo"],
|
|
6699
|
+
inputPrompt: "New value for repo",
|
|
6700
|
+
commandSuffix: ["--force"]
|
|
6701
|
+
}
|
|
6702
|
+
]
|
|
6703
|
+
},
|
|
6704
|
+
{
|
|
6705
|
+
name: "Cache",
|
|
6706
|
+
value: "cache",
|
|
6707
|
+
description: "Manage cached framework versions",
|
|
6708
|
+
children: [
|
|
6709
|
+
{
|
|
6710
|
+
name: "List cached versions",
|
|
6711
|
+
value: "list",
|
|
6712
|
+
description: "Show all cached framework versions",
|
|
6713
|
+
command: ["cache", "list"]
|
|
6714
|
+
},
|
|
6715
|
+
{
|
|
6716
|
+
name: "Clear a specific version",
|
|
6717
|
+
value: "clear-version",
|
|
6718
|
+
description: "Remove one cached version",
|
|
6719
|
+
command: ["cache", "clear"],
|
|
6720
|
+
inputPrompt: "Version to clear (e.g. v3.2.0)"
|
|
6721
|
+
},
|
|
6722
|
+
{
|
|
6723
|
+
name: "Clear all versions",
|
|
6724
|
+
value: "clear-all",
|
|
6725
|
+
description: "Remove all cached versions",
|
|
6726
|
+
command: ["cache", "clear", "--all"]
|
|
6727
|
+
}
|
|
6728
|
+
]
|
|
6729
|
+
}
|
|
6730
|
+
]
|
|
6731
|
+
}
|
|
6732
|
+
];
|
|
6733
|
+
var BACK = { name: "\u2190 Back", value: "back" };
|
|
6734
|
+
var EXIT = { name: "Exit", value: "exit" };
|
|
6735
|
+
var InteractiveMenuUseCase = class {
|
|
6736
|
+
constructor(manifestRepo, prompter) {
|
|
6737
|
+
this.manifestRepo = manifestRepo;
|
|
6738
|
+
this.prompter = prompter;
|
|
6739
|
+
}
|
|
6740
|
+
async execute(options) {
|
|
6741
|
+
const manifest = await this.manifestRepo.load();
|
|
6742
|
+
const rootNodes = manifest === null ? FRESH_NODES : INSTALLED_NODES;
|
|
6743
|
+
const result = await this.navigateFrom(
|
|
6744
|
+
rootNodes,
|
|
6745
|
+
"What would you like to do?",
|
|
6746
|
+
options?.startAt ?? [],
|
|
6747
|
+
[]
|
|
6748
|
+
);
|
|
6749
|
+
if (result.type !== "command") return { command: ["exit"] };
|
|
6750
|
+
return {
|
|
6751
|
+
command: result.command,
|
|
6752
|
+
returnTo: result.returnTo.length > 0 ? result.returnTo : void 0
|
|
6753
|
+
};
|
|
6754
|
+
}
|
|
6755
|
+
async navigateFrom(nodes, label, path, breadcrumb) {
|
|
6756
|
+
if (path.length > 0) {
|
|
6757
|
+
const [head, ...tail] = path;
|
|
6758
|
+
const node = nodes.find((n) => n.value === head);
|
|
6759
|
+
if (node && isBranch(node)) {
|
|
6760
|
+
const result = await this.navigateFrom(node.children, node.name, tail, [
|
|
6761
|
+
...breadcrumb,
|
|
6762
|
+
node.value
|
|
6763
|
+
]);
|
|
6764
|
+
if (result.type === "back") return this.showMenu(nodes, label, breadcrumb);
|
|
6765
|
+
return result;
|
|
6766
|
+
}
|
|
6767
|
+
}
|
|
6768
|
+
return this.showMenu(nodes, label, breadcrumb);
|
|
6769
|
+
}
|
|
6770
|
+
async showMenu(nodes, label, breadcrumb) {
|
|
6771
|
+
const nav = breadcrumb.length > 0 ? [BACK, EXIT] : [EXIT];
|
|
6772
|
+
const picked = await this.prompter.select(label, [...nodes.map(toChoice), ...nav]);
|
|
6773
|
+
if (picked === "exit") return { type: "exit" };
|
|
6774
|
+
if (picked === "back") return { type: "back" };
|
|
6775
|
+
const node = nodes.find((n) => n.value === picked);
|
|
6776
|
+
if (!node) return { type: "exit" };
|
|
6777
|
+
if (isBranch(node)) {
|
|
6778
|
+
const result = await this.showMenu(node.children, node.name, [...breadcrumb, node.value]);
|
|
6779
|
+
if (result.type === "back") return this.showMenu(nodes, label, breadcrumb);
|
|
6780
|
+
return result;
|
|
6781
|
+
}
|
|
6782
|
+
return { type: "command", command: await this.resolveCommand(node), returnTo: breadcrumb };
|
|
6783
|
+
}
|
|
6784
|
+
async resolveCommand(node) {
|
|
6785
|
+
if (node.inputPrompt !== void 0) {
|
|
6786
|
+
const input2 = await this.prompter.input(node.inputPrompt);
|
|
6787
|
+
return [...node.command, input2, ...node.commandSuffix ?? []];
|
|
6788
|
+
}
|
|
6789
|
+
return node.command;
|
|
6790
|
+
}
|
|
6791
|
+
};
|
|
6792
|
+
|
|
6215
6793
|
// src/cli.ts
|
|
6216
6794
|
function formatVersion(version) {
|
|
6217
6795
|
return `aidd/${version} node/${process.versions.node} ${platform2()}-${process.arch}`;
|
|
@@ -6253,8 +6831,37 @@ program.hook("preAction", async (_thisCommand, actionCommand) => {
|
|
|
6253
6831
|
);
|
|
6254
6832
|
}
|
|
6255
6833
|
});
|
|
6256
|
-
var
|
|
6257
|
-
if (
|
|
6834
|
+
var cliArgs = process.argv.slice(2);
|
|
6835
|
+
if (cliArgs.length === 0 || cliArgs.includes("--help") || cliArgs.includes("-h")) {
|
|
6258
6836
|
await new BannerUseCase().execute();
|
|
6259
6837
|
}
|
|
6260
|
-
|
|
6838
|
+
if (cliArgs.length === 0 && process.stdout.isTTY) {
|
|
6839
|
+
runMenuLoop();
|
|
6840
|
+
} else {
|
|
6841
|
+
program.parse(process.argv);
|
|
6842
|
+
}
|
|
6843
|
+
async function runMenuLoop() {
|
|
6844
|
+
const { manifestRepo, prompter } = createMenuDeps(process.cwd());
|
|
6845
|
+
let returnTo;
|
|
6846
|
+
for (; ; ) {
|
|
6847
|
+
try {
|
|
6848
|
+
const result = await new InteractiveMenuUseCase(manifestRepo, prompter).execute({
|
|
6849
|
+
startAt: returnTo
|
|
6850
|
+
});
|
|
6851
|
+
if (result.command[0] === "exit") process.exit(0);
|
|
6852
|
+
returnTo = result.returnTo;
|
|
6853
|
+
await spawnCliCommand(result.command);
|
|
6854
|
+
} catch (error) {
|
|
6855
|
+
if (error instanceof Error && error.name === "ExitPromptError") process.exit(0);
|
|
6856
|
+
returnTo = void 0;
|
|
6857
|
+
}
|
|
6858
|
+
}
|
|
6859
|
+
}
|
|
6860
|
+
function spawnCliCommand(command) {
|
|
6861
|
+
return new Promise((resolve) => {
|
|
6862
|
+
spawn(process.execPath, [process.argv[1], ...command], { stdio: "inherit" }).on(
|
|
6863
|
+
"close",
|
|
6864
|
+
resolve
|
|
6865
|
+
);
|
|
6866
|
+
});
|
|
6867
|
+
}
|