@ai-driven-dev/cli 3.0.0 → 3.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +1120 -764
- package/package.json +4 -1
package/dist/cli.js
CHANGED
|
@@ -1986,7 +1986,7 @@ var CliUpdaterAdapter = class {
|
|
|
1986
1986
|
// package.json
|
|
1987
1987
|
var package_default = {
|
|
1988
1988
|
name: "@ai-driven-dev/cli",
|
|
1989
|
-
version: "3.
|
|
1989
|
+
version: "3.1.1",
|
|
1990
1990
|
description: "AI-Driven Development CLI \u2014 distribute the AIDD framework across AI coding assistants",
|
|
1991
1991
|
type: "module",
|
|
1992
1992
|
main: "dist/cli.js",
|
|
@@ -2005,6 +2005,9 @@ var package_default = {
|
|
|
2005
2005
|
build: "tsup",
|
|
2006
2006
|
dev: "tsup --watch",
|
|
2007
2007
|
test: "pnpm build && vitest run",
|
|
2008
|
+
"test:unit": "vitest run --project=unit",
|
|
2009
|
+
"test:integration": "vitest run --project=integration",
|
|
2010
|
+
"test:e2e": "pnpm build && vitest run --project=e2e",
|
|
2008
2011
|
"test:watch": "vitest",
|
|
2009
2012
|
typecheck: "tsc --noEmit",
|
|
2010
2013
|
lint: "biome check .",
|
|
@@ -2142,7 +2145,7 @@ var FileSystemAdapter = class {
|
|
|
2142
2145
|
let existing = {};
|
|
2143
2146
|
try {
|
|
2144
2147
|
const raw = await readFile3(path, "utf-8");
|
|
2145
|
-
existing = JSON.parse(raw);
|
|
2148
|
+
existing = JSON.parse(stripJsoncComments(raw));
|
|
2146
2149
|
} catch (err) {
|
|
2147
2150
|
const code = err.code;
|
|
2148
2151
|
if (code !== "ENOENT") {
|
|
@@ -2208,6 +2211,14 @@ function stripJsoncComments(content) {
|
|
|
2208
2211
|
i += 2;
|
|
2209
2212
|
continue;
|
|
2210
2213
|
}
|
|
2214
|
+
if (ch === ",") {
|
|
2215
|
+
let j = i + 1;
|
|
2216
|
+
while (j < content.length && " \n\r".includes(content[j])) j++;
|
|
2217
|
+
if (content[j] === "}" || content[j] === "]") {
|
|
2218
|
+
i++;
|
|
2219
|
+
continue;
|
|
2220
|
+
}
|
|
2221
|
+
}
|
|
2211
2222
|
result += ch;
|
|
2212
2223
|
i++;
|
|
2213
2224
|
}
|
|
@@ -3619,6 +3630,29 @@ ${invocation} ${SCRIPT_RELATIVE_PATH}
|
|
|
3619
3630
|
}
|
|
3620
3631
|
};
|
|
3621
3632
|
|
|
3633
|
+
// src/application/use-cases/shared/post-install-pipeline-use-case.ts
|
|
3634
|
+
var PostInstallPipelineUseCase = class {
|
|
3635
|
+
constructor(fs, manifestRepo, hasher, git) {
|
|
3636
|
+
this.fs = fs;
|
|
3637
|
+
this.manifestRepo = manifestRepo;
|
|
3638
|
+
this.hasher = hasher;
|
|
3639
|
+
this.git = git;
|
|
3640
|
+
}
|
|
3641
|
+
async execute(options) {
|
|
3642
|
+
const { projectRoot, version, descriptor, contentFiles, manifest, docsDir } = options;
|
|
3643
|
+
await new MemoryScriptUseCase(this.fs, this.hasher, this.git).execute({
|
|
3644
|
+
projectRoot,
|
|
3645
|
+
version,
|
|
3646
|
+
descriptor,
|
|
3647
|
+
contentFiles,
|
|
3648
|
+
manifest
|
|
3649
|
+
});
|
|
3650
|
+
await this.manifestRepo.save(manifest);
|
|
3651
|
+
await new CatalogUseCase(this.fs).execute({ manifest, docsDir, projectRoot });
|
|
3652
|
+
await new GitignoreUseCase(this.fs).execute(projectRoot, [".aidd/cache/"]);
|
|
3653
|
+
}
|
|
3654
|
+
};
|
|
3655
|
+
|
|
3622
3656
|
// src/application/use-cases/install-use-case.ts
|
|
3623
3657
|
var InstallUseCase = class {
|
|
3624
3658
|
constructor(fs, manifestRepo, loader, hasher, logger, git, platform3, prompter) {
|
|
@@ -3633,100 +3667,115 @@ var InstallUseCase = class {
|
|
|
3633
3667
|
}
|
|
3634
3668
|
async execute(options) {
|
|
3635
3669
|
const { frameworkPath, version, projectRoot, force = false, repo } = options;
|
|
3636
|
-
const interactive = options.interactive ?? false;
|
|
3637
3670
|
const manifest = await this.manifestRepo.load();
|
|
3638
|
-
if (manifest === null)
|
|
3639
|
-
throw new NoManifestError(repo);
|
|
3640
|
-
}
|
|
3671
|
+
if (manifest === null) throw new NoManifestError(repo);
|
|
3641
3672
|
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
|
-
}
|
|
3673
|
+
const toolIds = await this.resolveToolIds(options, manifest);
|
|
3660
3674
|
assertValidToolIds(toolIds);
|
|
3661
3675
|
const { descriptor, contentFiles } = await this.loader.loadFromDirectory(
|
|
3662
3676
|
frameworkPath,
|
|
3663
3677
|
version
|
|
3664
3678
|
);
|
|
3679
|
+
const results = await this.installAllTools(
|
|
3680
|
+
toolIds,
|
|
3681
|
+
manifest,
|
|
3682
|
+
descriptor,
|
|
3683
|
+
contentFiles,
|
|
3684
|
+
docsDir,
|
|
3685
|
+
projectRoot,
|
|
3686
|
+
force
|
|
3687
|
+
);
|
|
3688
|
+
await new PostInstallPipelineUseCase(this.fs, this.manifestRepo, this.hasher, this.git).execute(
|
|
3689
|
+
{ projectRoot, version, descriptor, contentFiles, manifest, docsDir }
|
|
3690
|
+
);
|
|
3691
|
+
return results;
|
|
3692
|
+
}
|
|
3693
|
+
async installAllTools(toolIds, manifest, descriptor, contentFiles, docsDir, projectRoot, force) {
|
|
3665
3694
|
const results = [];
|
|
3666
3695
|
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(
|
|
3696
|
+
const result = await this.installOneTool(
|
|
3697
|
+
toolId,
|
|
3698
|
+
manifest,
|
|
3683
3699
|
descriptor,
|
|
3684
|
-
config,
|
|
3685
|
-
docsDir,
|
|
3686
3700
|
contentFiles,
|
|
3687
|
-
|
|
3688
|
-
this.platform,
|
|
3701
|
+
docsDir,
|
|
3689
3702
|
projectRoot,
|
|
3690
|
-
|
|
3703
|
+
force
|
|
3691
3704
|
);
|
|
3692
|
-
|
|
3693
|
-
|
|
3694
|
-
|
|
3695
|
-
|
|
3696
|
-
|
|
3697
|
-
|
|
3698
|
-
|
|
3699
|
-
|
|
3700
|
-
|
|
3701
|
-
|
|
3702
|
-
|
|
3703
|
-
|
|
3705
|
+
results.push(result);
|
|
3706
|
+
}
|
|
3707
|
+
return results;
|
|
3708
|
+
}
|
|
3709
|
+
/** Resolves which tool IDs to install from the 4-branch selection logic. */
|
|
3710
|
+
async resolveToolIds(options, manifest) {
|
|
3711
|
+
const interactive = options.interactive ?? false;
|
|
3712
|
+
if (options.all) return [...VALID_TOOL_IDS];
|
|
3713
|
+
if (options.toolIds !== void 0 && options.toolIds.length > 0) return options.toolIds;
|
|
3714
|
+
if (interactive && this.prompter !== void 0) return this.promptToolIds(manifest);
|
|
3715
|
+
throw new Error(`At least one tool ID is required. Valid tools: ${VALID_TOOL_IDS.join(", ")}`);
|
|
3716
|
+
}
|
|
3717
|
+
async promptToolIds(manifest) {
|
|
3718
|
+
if (this.prompter === void 0) throw new Error("Prompter is required for interactive mode.");
|
|
3719
|
+
const installedIds = manifest.getInstalledToolIds();
|
|
3720
|
+
const choices = VALID_TOOL_IDS.map(
|
|
3721
|
+
(id) => installedIds.includes(id) ? { name: id, value: id, checked: true, disabled: "(already installed)" } : { name: id, value: id, checked: false }
|
|
3722
|
+
);
|
|
3723
|
+
const selected = await this.prompter.checkbox("Which tools do you want to install?", choices);
|
|
3724
|
+
if (selected.length === 0) throw new Error("No tools selected.");
|
|
3725
|
+
return selected;
|
|
3726
|
+
}
|
|
3727
|
+
/** Installs a single tool and updates the manifest in place. */
|
|
3728
|
+
async installOneTool(toolId, manifest, descriptor, contentFiles, docsDir, projectRoot, force) {
|
|
3729
|
+
if (manifest.hasTool(toolId) && !force) {
|
|
3730
|
+
return { toolId, fileCount: 0, files: [], skipped: true, warnings: [] };
|
|
3731
|
+
}
|
|
3732
|
+
const config = getToolConfig(toolId);
|
|
3733
|
+
const warnings = await this.checkForceWarning(toolId, config, manifest, projectRoot, force);
|
|
3734
|
+
this.logger.info(`Generating ${toolId} distribution...`);
|
|
3735
|
+
const generated = await generateDistribution(
|
|
3736
|
+
descriptor,
|
|
3737
|
+
config,
|
|
3738
|
+
docsDir,
|
|
3739
|
+
contentFiles,
|
|
3740
|
+
this.hasher,
|
|
3741
|
+
this.platform,
|
|
3742
|
+
projectRoot,
|
|
3743
|
+
this.fs
|
|
3744
|
+
);
|
|
3745
|
+
await this.removeStaleFiles(toolId, manifest, generated, projectRoot);
|
|
3746
|
+
const { files: finalFiles, userFileConflicts } = await this.writeToolFiles(
|
|
3747
|
+
generated,
|
|
3748
|
+
projectRoot,
|
|
3749
|
+
manifest
|
|
3750
|
+
);
|
|
3751
|
+
for (const relativePath of userFileConflicts) {
|
|
3752
|
+
warnings.push(
|
|
3753
|
+
`\`${relativePath}\` already exists and was not installed by AIDD \u2014 skipped to preserve user file`
|
|
3704
3754
|
);
|
|
3705
|
-
|
|
3755
|
+
}
|
|
3756
|
+
manifest.addTool(toolId, descriptor.version, finalFiles);
|
|
3757
|
+
return { toolId, fileCount: generated.length, files: generated, skipped: false, warnings };
|
|
3758
|
+
}
|
|
3759
|
+
async checkForceWarning(toolId, config, manifest, projectRoot, force) {
|
|
3760
|
+
const warnings = [];
|
|
3761
|
+
if (!manifest.hasTool(toolId) && force) {
|
|
3762
|
+
const toolDir = join19(projectRoot, config.directory);
|
|
3763
|
+
if (await this.fs.fileExists(toolDir)) {
|
|
3706
3764
|
warnings.push(
|
|
3707
|
-
|
|
3765
|
+
`Directory ${config.directory} exists but tool is not in manifest. Files will be overwritten.`
|
|
3708
3766
|
);
|
|
3709
3767
|
}
|
|
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
3768
|
}
|
|
3719
|
-
|
|
3720
|
-
|
|
3721
|
-
|
|
3722
|
-
|
|
3723
|
-
|
|
3724
|
-
|
|
3725
|
-
|
|
3726
|
-
|
|
3727
|
-
|
|
3728
|
-
|
|
3729
|
-
return results;
|
|
3769
|
+
return warnings;
|
|
3770
|
+
}
|
|
3771
|
+
async removeStaleFiles(toolId, manifest, generated, projectRoot) {
|
|
3772
|
+
if (!manifest.hasTool(toolId)) return;
|
|
3773
|
+
const newPaths = new Set(generated.map((f) => f.relativePath));
|
|
3774
|
+
for (const oldFile of manifest.getToolFiles(toolId)) {
|
|
3775
|
+
if (!newPaths.has(oldFile.relativePath)) {
|
|
3776
|
+
await this.fs.deleteFile(join19(projectRoot, oldFile.relativePath));
|
|
3777
|
+
}
|
|
3778
|
+
}
|
|
3730
3779
|
}
|
|
3731
3780
|
async writeToolFiles(generated, projectRoot, manifest) {
|
|
3732
3781
|
const filesByPath = /* @__PURE__ */ new Map();
|
|
@@ -3922,14 +3971,14 @@ function buildDocsDistribution(docsFiles, docsDir, hasher) {
|
|
|
3922
3971
|
|
|
3923
3972
|
// src/application/use-cases/restore-use-case.ts
|
|
3924
3973
|
var RestoreUseCase = class {
|
|
3925
|
-
constructor(fs, manifestRepo, loader, hasher, logger,
|
|
3974
|
+
constructor(fs, manifestRepo, loader, hasher, logger, platform3, prompter) {
|
|
3926
3975
|
this.fs = fs;
|
|
3927
3976
|
this.manifestRepo = manifestRepo;
|
|
3928
3977
|
this.loader = loader;
|
|
3929
3978
|
this.hasher = hasher;
|
|
3930
3979
|
this.logger = logger;
|
|
3931
|
-
this.prompter = prompter;
|
|
3932
3980
|
this.platform = platform3;
|
|
3981
|
+
this.prompter = prompter;
|
|
3933
3982
|
}
|
|
3934
3983
|
async execute(options) {
|
|
3935
3984
|
const {
|
|
@@ -3942,67 +3991,136 @@ var RestoreUseCase = class {
|
|
|
3942
3991
|
repo
|
|
3943
3992
|
} = options;
|
|
3944
3993
|
const docsOnly = options.docsOnly ?? false;
|
|
3945
|
-
const fileFilter = buildFileFilter(options.files);
|
|
3946
3994
|
const manifest = options.manifest ?? await this.manifestRepo.load();
|
|
3947
3995
|
if (manifest === null) throw new NoManifestError(repo);
|
|
3948
|
-
const toolIds = docsOnly ? [] : options.toolIds && options.toolIds.length > 0 ? options.toolIds : manifest.getInstalledToolIds();
|
|
3949
3996
|
const { descriptor, contentFiles, docsFiles } = await this.loader.loadFromDirectory(
|
|
3950
3997
|
frameworkPath,
|
|
3951
3998
|
version
|
|
3952
3999
|
);
|
|
4000
|
+
const fileFilter = buildFileFilter(options.files);
|
|
4001
|
+
return this.executeRestore({
|
|
4002
|
+
options,
|
|
4003
|
+
docsOnly,
|
|
4004
|
+
manifest,
|
|
4005
|
+
descriptor,
|
|
4006
|
+
contentFiles,
|
|
4007
|
+
docsFiles,
|
|
4008
|
+
docsDir,
|
|
4009
|
+
projectRoot,
|
|
4010
|
+
version,
|
|
4011
|
+
force,
|
|
4012
|
+
interactive,
|
|
4013
|
+
fileFilter
|
|
4014
|
+
});
|
|
4015
|
+
}
|
|
4016
|
+
async executeRestore(ctx) {
|
|
4017
|
+
const {
|
|
4018
|
+
options,
|
|
4019
|
+
docsOnly,
|
|
4020
|
+
manifest,
|
|
4021
|
+
descriptor,
|
|
4022
|
+
contentFiles,
|
|
4023
|
+
docsFiles,
|
|
4024
|
+
docsDir,
|
|
4025
|
+
projectRoot,
|
|
4026
|
+
version,
|
|
4027
|
+
force,
|
|
4028
|
+
interactive,
|
|
4029
|
+
fileFilter
|
|
4030
|
+
} = ctx;
|
|
4031
|
+
const toolIds = this.resolveToolIds(options, docsOnly, manifest);
|
|
4032
|
+
const toolResults = await this.restoreAllTools(
|
|
4033
|
+
toolIds,
|
|
4034
|
+
manifest,
|
|
4035
|
+
descriptor,
|
|
4036
|
+
contentFiles,
|
|
4037
|
+
docsDir,
|
|
4038
|
+
projectRoot,
|
|
4039
|
+
version,
|
|
4040
|
+
force,
|
|
4041
|
+
interactive,
|
|
4042
|
+
fileFilter
|
|
4043
|
+
);
|
|
4044
|
+
const hasExplicitToolFilter = !docsOnly && options.toolIds !== void 0 && options.toolIds.length > 0;
|
|
4045
|
+
const docsResult = hasExplicitToolFilter ? null : await this.restoreDocs(
|
|
4046
|
+
manifest,
|
|
4047
|
+
docsFiles,
|
|
4048
|
+
docsDir,
|
|
4049
|
+
projectRoot,
|
|
4050
|
+
version,
|
|
4051
|
+
force,
|
|
4052
|
+
interactive,
|
|
4053
|
+
fileFilter
|
|
4054
|
+
);
|
|
4055
|
+
const hasChanges = toolResults.some((t) => t.restored.length > 0) || docsResult !== null && docsResult.restored.length > 0;
|
|
4056
|
+
if (hasChanges) await this.manifestRepo.save(manifest);
|
|
4057
|
+
return this.buildRestoreTotals(toolResults, docsResult);
|
|
4058
|
+
}
|
|
4059
|
+
resolveToolIds(options, docsOnly, manifest) {
|
|
4060
|
+
if (docsOnly) return [];
|
|
4061
|
+
return options.toolIds?.length ? options.toolIds : manifest.getInstalledToolIds();
|
|
4062
|
+
}
|
|
4063
|
+
async restoreAllTools(toolIds, manifest, descriptor, contentFiles, docsDir, projectRoot, version, force, interactive, fileFilter) {
|
|
3953
4064
|
const toolResults = [];
|
|
3954
4065
|
for (const toolId of toolIds) {
|
|
3955
|
-
|
|
3956
|
-
|
|
3957
|
-
|
|
3958
|
-
const distribution = await generateDistribution(
|
|
4066
|
+
const result = await this.restoreOneTool(
|
|
4067
|
+
toolId,
|
|
4068
|
+
manifest,
|
|
3959
4069
|
descriptor,
|
|
3960
|
-
config,
|
|
3961
|
-
docsDir,
|
|
3962
4070
|
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])),
|
|
4071
|
+
docsDir,
|
|
3977
4072
|
projectRoot,
|
|
4073
|
+
version,
|
|
3978
4074
|
force,
|
|
3979
|
-
interactive
|
|
4075
|
+
interactive,
|
|
4076
|
+
fileFilter
|
|
3980
4077
|
);
|
|
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 });
|
|
4078
|
+
toolResults.push(result);
|
|
3989
4079
|
}
|
|
3990
|
-
|
|
3991
|
-
|
|
3992
|
-
|
|
3993
|
-
|
|
4080
|
+
return toolResults;
|
|
4081
|
+
}
|
|
4082
|
+
async restoreOneTool(toolId, manifest, descriptor, contentFiles, docsDir, projectRoot, version, force, interactive, fileFilter) {
|
|
4083
|
+
this.logger.info(`Checking ${toolId} for files to restore...`);
|
|
4084
|
+
const config = getToolConfig(toolId);
|
|
4085
|
+
const manifestFiles = manifest.getToolFiles(toolId);
|
|
4086
|
+
const distribution = await generateDistribution(
|
|
4087
|
+
descriptor,
|
|
4088
|
+
config,
|
|
3994
4089
|
docsDir,
|
|
4090
|
+
contentFiles,
|
|
4091
|
+
this.hasher,
|
|
4092
|
+
this.platform,
|
|
4093
|
+
projectRoot,
|
|
4094
|
+
this.fs
|
|
4095
|
+
);
|
|
4096
|
+
const distMap = new Map(distribution.map((f) => [f.relativePath, f]));
|
|
4097
|
+
const section = await this.restoreSection(
|
|
4098
|
+
manifestFiles,
|
|
4099
|
+
distMap,
|
|
3995
4100
|
projectRoot,
|
|
3996
|
-
version,
|
|
3997
4101
|
force,
|
|
3998
4102
|
interactive,
|
|
3999
4103
|
fileFilter
|
|
4000
4104
|
);
|
|
4001
|
-
|
|
4002
|
-
|
|
4003
|
-
|
|
4004
|
-
|
|
4005
|
-
|
|
4105
|
+
if (section === null) return { toolId, nothingToRestore: true, restored: [], kept: [] };
|
|
4106
|
+
manifest.addTool(toolId, manifest.getToolVersion(toolId) ?? version, section.updatedFiles);
|
|
4107
|
+
return { toolId, nothingToRestore: false, restored: section.restored, kept: section.kept };
|
|
4108
|
+
}
|
|
4109
|
+
/** Shared restoration logic for both tool files and docs files. Returns null when nothing to restore. */
|
|
4110
|
+
async restoreSection(manifestFiles, distMap, projectRoot, force, interactive, fileFilter) {
|
|
4111
|
+
const drift = await this.collectDrift(manifestFiles, distMap, projectRoot, fileFilter);
|
|
4112
|
+
if (drift.length === 0) return null;
|
|
4113
|
+
const { restored, kept, updatedHashMap } = await this.applyRestorations(
|
|
4114
|
+
drift,
|
|
4115
|
+
new Map(manifestFiles.map((f) => [f.relativePath, f.hash])),
|
|
4116
|
+
projectRoot,
|
|
4117
|
+
force,
|
|
4118
|
+
interactive
|
|
4119
|
+
);
|
|
4120
|
+
const updatedFiles = Array.from(updatedHashMap.entries()).map(
|
|
4121
|
+
([relativePath, hash]) => new GeneratedFile({ relativePath, content: "", hash })
|
|
4122
|
+
);
|
|
4123
|
+
return { restored, kept, updatedFiles };
|
|
4006
4124
|
}
|
|
4007
4125
|
async restoreDocs(manifest, docsFiles, docsDir, projectRoot, version, force, interactive, fileFilter) {
|
|
4008
4126
|
const docsManifestFiles = manifest.getDocsFiles();
|
|
@@ -4011,22 +4129,22 @@ var RestoreUseCase = class {
|
|
|
4011
4129
|
this.logger.info("Checking docs for files to restore...");
|
|
4012
4130
|
const distribution = buildDocsDistribution(docsFiles, docsDir, this.hasher);
|
|
4013
4131
|
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])),
|
|
4132
|
+
const section = await this.restoreSection(
|
|
4133
|
+
docsManifestFiles,
|
|
4134
|
+
distMap,
|
|
4019
4135
|
projectRoot,
|
|
4020
4136
|
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
|
-
)
|
|
4137
|
+
interactive,
|
|
4138
|
+
fileFilter
|
|
4028
4139
|
);
|
|
4029
|
-
return { nothingToRestore:
|
|
4140
|
+
if (section === null) return { nothingToRestore: true, restored: [], kept: [] };
|
|
4141
|
+
manifest.addDocs(manifest.getDocsVersion() ?? version, section.updatedFiles);
|
|
4142
|
+
return { nothingToRestore: false, restored: section.restored, kept: section.kept };
|
|
4143
|
+
}
|
|
4144
|
+
buildRestoreTotals(toolResults, docsResult) {
|
|
4145
|
+
const totalRestored = toolResults.reduce((s, t) => s + t.restored.length, 0) + (docsResult?.restored.length ?? 0);
|
|
4146
|
+
const totalKept = toolResults.reduce((s, t) => s + t.kept.length, 0) + (docsResult?.kept.length ?? 0);
|
|
4147
|
+
return { tools: toolResults, docs: docsResult, totalRestored, totalKept };
|
|
4030
4148
|
}
|
|
4031
4149
|
async collectDrift(manifestFiles, distMap, projectRoot, fileFilter) {
|
|
4032
4150
|
const drift = [];
|
|
@@ -4240,8 +4358,8 @@ function registerRestoreCommand(program2) {
|
|
|
4240
4358
|
deps.loader,
|
|
4241
4359
|
deps.hasher,
|
|
4242
4360
|
deps.logger,
|
|
4243
|
-
|
|
4244
|
-
|
|
4361
|
+
deps.platform,
|
|
4362
|
+
prompter
|
|
4245
4363
|
);
|
|
4246
4364
|
const result = await restoreUseCase.execute({
|
|
4247
4365
|
frameworkPath,
|
|
@@ -4370,6 +4488,45 @@ var AdoptUseCase = class {
|
|
|
4370
4488
|
}
|
|
4371
4489
|
async execute(options) {
|
|
4372
4490
|
const { toolIds, frameworkPath, docsDir, projectRoot, version } = options;
|
|
4491
|
+
this.validateToolIds(toolIds);
|
|
4492
|
+
const existing = await this.manifestRepo.load();
|
|
4493
|
+
if (existing !== null) throw new Error("Already initialized. Use `aidd update` to upgrade.");
|
|
4494
|
+
await this.deleteLegacyConfig(projectRoot);
|
|
4495
|
+
const { descriptor, contentFiles, docsFiles } = await this.loader.loadFromDirectory(
|
|
4496
|
+
frameworkPath,
|
|
4497
|
+
version
|
|
4498
|
+
);
|
|
4499
|
+
const manifest = Manifest.create(docsDir);
|
|
4500
|
+
const toolResults = await this.registerAllTools(
|
|
4501
|
+
toolIds,
|
|
4502
|
+
manifest,
|
|
4503
|
+
descriptor,
|
|
4504
|
+
contentFiles,
|
|
4505
|
+
docsDir,
|
|
4506
|
+
projectRoot,
|
|
4507
|
+
version
|
|
4508
|
+
);
|
|
4509
|
+
const docsRegistered = await this.registerDocs(
|
|
4510
|
+
manifest,
|
|
4511
|
+
docsFiles,
|
|
4512
|
+
docsDir,
|
|
4513
|
+
projectRoot,
|
|
4514
|
+
version
|
|
4515
|
+
);
|
|
4516
|
+
await this.persistAdopt(manifest, docsDir, projectRoot, version);
|
|
4517
|
+
return {
|
|
4518
|
+
tools: toolResults,
|
|
4519
|
+
totalRegistered: toolResults.reduce((sum, r) => sum + r.registered.length, 0),
|
|
4520
|
+
docsRegistered
|
|
4521
|
+
};
|
|
4522
|
+
}
|
|
4523
|
+
/** Finalizes catalog, saves manifest, and writes gitignore entry. */
|
|
4524
|
+
async persistAdopt(manifest, docsDir, projectRoot, version) {
|
|
4525
|
+
await this.finalizeCatalog(manifest, docsDir, projectRoot, version);
|
|
4526
|
+
await this.manifestRepo.save(manifest);
|
|
4527
|
+
await new GitignoreUseCase(this.fs).execute(projectRoot, [".aidd/cache/"]);
|
|
4528
|
+
}
|
|
4529
|
+
validateToolIds(toolIds) {
|
|
4373
4530
|
const invalid = toolIds.filter((t) => !VALID_TOOL_IDS.includes(t));
|
|
4374
4531
|
if (invalid.length > 0) {
|
|
4375
4532
|
throw new Error(
|
|
@@ -4379,16 +4536,8 @@ var AdoptUseCase = class {
|
|
|
4379
4536
|
if (toolIds.length === 0) {
|
|
4380
4537
|
throw new Error("No tools specified. Use --tools to specify at least one tool.");
|
|
4381
4538
|
}
|
|
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);
|
|
4539
|
+
}
|
|
4540
|
+
async registerAllTools(toolIds, manifest, descriptor, contentFiles, docsDir, projectRoot, version) {
|
|
4392
4541
|
const toolResults = [];
|
|
4393
4542
|
for (const toolId of toolIds) {
|
|
4394
4543
|
this.logger.info(`Adopting ${toolId}...`);
|
|
@@ -4416,37 +4565,33 @@ var AdoptUseCase = class {
|
|
|
4416
4565
|
manifest.addTool(toolId, version, registeredFiles);
|
|
4417
4566
|
toolResults.push({ toolId, registered: registeredFiles.map((f) => f.relativePath) });
|
|
4418
4567
|
}
|
|
4419
|
-
|
|
4568
|
+
return toolResults;
|
|
4569
|
+
}
|
|
4570
|
+
async registerDocs(manifest, docsFiles, docsDir, projectRoot, version) {
|
|
4420
4571
|
const docsAbsDir = join22(projectRoot, docsDir);
|
|
4421
|
-
if (await this.fs.fileExists(docsAbsDir))
|
|
4422
|
-
|
|
4423
|
-
|
|
4424
|
-
|
|
4425
|
-
|
|
4426
|
-
|
|
4427
|
-
|
|
4572
|
+
if (!await this.fs.fileExists(docsAbsDir)) return 0;
|
|
4573
|
+
this.logger.info("Adopting docs...");
|
|
4574
|
+
const docsDistribution = buildDocsDistribution(docsFiles, docsDir, this.hasher);
|
|
4575
|
+
const registeredFiles = await this.matchDistributionToDisk(docsDistribution, projectRoot);
|
|
4576
|
+
manifest.addDocs(version, registeredFiles);
|
|
4577
|
+
return registeredFiles.length;
|
|
4578
|
+
}
|
|
4579
|
+
async finalizeCatalog(manifest, docsDir, projectRoot, version) {
|
|
4428
4580
|
await new CatalogUseCase(this.fs).execute({ manifest, docsDir, projectRoot });
|
|
4429
4581
|
const catalogRelPath = `${docsDir}/CATALOG.md`;
|
|
4430
4582
|
const catalogAbsPath = join22(projectRoot, catalogRelPath);
|
|
4431
|
-
if (await this.fs.fileExists(catalogAbsPath))
|
|
4432
|
-
|
|
4433
|
-
|
|
4434
|
-
|
|
4435
|
-
|
|
4583
|
+
if (!await this.fs.fileExists(catalogAbsPath)) return;
|
|
4584
|
+
const catalogHash = await this.fs.readFileHash(catalogAbsPath);
|
|
4585
|
+
const currentDocsFiles = manifest.getDocsFiles();
|
|
4586
|
+
const updatedDocsFiles = currentDocsFiles.map(
|
|
4587
|
+
(f) => f.relativePath === catalogRelPath ? new GeneratedFile({ relativePath: f.relativePath, content: "", hash: catalogHash }) : new GeneratedFile({ relativePath: f.relativePath, content: "", hash: f.hash })
|
|
4588
|
+
);
|
|
4589
|
+
if (!currentDocsFiles.some((f) => f.relativePath === catalogRelPath)) {
|
|
4590
|
+
updatedDocsFiles.push(
|
|
4591
|
+
new GeneratedFile({ relativePath: catalogRelPath, content: "", hash: catalogHash })
|
|
4436
4592
|
);
|
|
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
4593
|
}
|
|
4444
|
-
|
|
4445
|
-
return {
|
|
4446
|
-
tools: toolResults,
|
|
4447
|
-
totalRegistered: toolResults.reduce((sum, r) => sum + r.registered.length, 0),
|
|
4448
|
-
docsRegistered
|
|
4449
|
-
};
|
|
4594
|
+
manifest.addDocs(manifest.getDocsVersion() ?? version, updatedDocsFiles);
|
|
4450
4595
|
}
|
|
4451
4596
|
async matchDistributionToDisk(distribution, projectRoot) {
|
|
4452
4597
|
const result = [];
|
|
@@ -4514,39 +4659,71 @@ var InitUseCase = class {
|
|
|
4514
4659
|
}
|
|
4515
4660
|
async execute(options) {
|
|
4516
4661
|
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
|
-
}
|
|
4662
|
+
const { docsDir, explicitDocsDir, repo } = await this.resolveInitConfig(options);
|
|
4536
4663
|
const resolvedInputDocsDir = docsDir ?? Manifest.DEFAULT_DOCS_DIR;
|
|
4537
4664
|
Manifest.validateDocsDir(resolvedInputDocsDir);
|
|
4538
|
-
await this.checkPreconditions({ docsDir: resolvedInputDocsDir, projectRoot, force, repo });
|
|
4539
4665
|
const existing = await this.manifestRepo.load();
|
|
4666
|
+
await this.checkPreconditions({ docsDir: resolvedInputDocsDir, projectRoot, force, repo });
|
|
4540
4667
|
const resolvedDocsDir = force && existing !== null && explicitDocsDir === void 0 ? existing.docsDir : resolvedInputDocsDir;
|
|
4541
4668
|
const { descriptor, docsFiles } = await this.loader.loadFromDirectory(frameworkPath, version);
|
|
4669
|
+
const generated = await this.writeDocsFiles(
|
|
4670
|
+
docsFiles,
|
|
4671
|
+
resolvedDocsDir,
|
|
4672
|
+
projectRoot,
|
|
4673
|
+
force,
|
|
4674
|
+
existing
|
|
4675
|
+
);
|
|
4676
|
+
if (force && existing !== null)
|
|
4677
|
+
await this.removeStaleDocsFiles(generated, existing, projectRoot);
|
|
4678
|
+
const manifest = this.buildManifest(existing, resolvedDocsDir, repo, force);
|
|
4679
|
+
manifest.addDocs(descriptor.version, generated);
|
|
4680
|
+
await this.persistInit(manifest, resolvedDocsDir, projectRoot, force);
|
|
4681
|
+
return { docsDir: resolvedDocsDir, fileCount: generated.length, manifest };
|
|
4682
|
+
}
|
|
4683
|
+
buildManifest(existing, resolvedDocsDir, repo, force) {
|
|
4684
|
+
return force && existing !== null ? existing.withDocsDir(resolvedDocsDir) : Manifest.create(resolvedDocsDir, repo);
|
|
4685
|
+
}
|
|
4686
|
+
/** Saves manifest, regenerates catalog, and conditionally adds gitignore entry. */
|
|
4687
|
+
async persistInit(manifest, docsDir, projectRoot, force) {
|
|
4688
|
+
await this.manifestRepo.save(manifest);
|
|
4689
|
+
await new CatalogUseCase(this.fs).execute({ manifest, docsDir, projectRoot });
|
|
4690
|
+
if (!force) {
|
|
4691
|
+
await new GitignoreUseCase(this.fs).execute(projectRoot, [".aidd/cache/"]);
|
|
4692
|
+
}
|
|
4693
|
+
}
|
|
4694
|
+
/** Resolves interactive config (docsDir, repo) if prompted, otherwise uses options as-is. */
|
|
4695
|
+
async resolveInitConfig(options) {
|
|
4696
|
+
const interactive = options.interactive ?? false;
|
|
4697
|
+
const force = options.force ?? false;
|
|
4698
|
+
if (!interactive || force || options.docsDir !== void 0 || this.prompter === void 0) {
|
|
4699
|
+
return {
|
|
4700
|
+
docsDir: options.docsDir,
|
|
4701
|
+
explicitDocsDir: options.explicitDocsDir,
|
|
4702
|
+
repo: options.repo
|
|
4703
|
+
};
|
|
4704
|
+
}
|
|
4705
|
+
const docsDirInput = await this.prompter.input(
|
|
4706
|
+
"Documentation directory name:",
|
|
4707
|
+
Manifest.DEFAULT_DOCS_DIR
|
|
4708
|
+
);
|
|
4709
|
+
const docsDir = docsDirInput || Manifest.DEFAULT_DOCS_DIR;
|
|
4710
|
+
const repoInput = await this.prompter.input(
|
|
4711
|
+
"Framework repository (owner/repo, leave blank to skip):",
|
|
4712
|
+
options.repo ?? ""
|
|
4713
|
+
);
|
|
4714
|
+
const repo = repoInput !== "" ? repoInput.trim() : options.repo;
|
|
4715
|
+
return { docsDir, explicitDocsDir: docsDir, repo };
|
|
4716
|
+
}
|
|
4717
|
+
/** Writes docs files to disk, skipping CATALOG.md. Returns the list of generated files. */
|
|
4718
|
+
async writeDocsFiles(docsFiles, docsDir, projectRoot, force, existing) {
|
|
4542
4719
|
const generated = [];
|
|
4543
4720
|
for (const [frameworkRelPath, rawContent] of docsFiles.entries()) {
|
|
4544
4721
|
if (frameworkRelPath.endsWith("CATALOG.md")) continue;
|
|
4545
|
-
const outputRelPath = remapDocsPath(frameworkRelPath,
|
|
4722
|
+
const outputRelPath = remapDocsPath(frameworkRelPath, docsDir);
|
|
4546
4723
|
const outputPath = join23(projectRoot, outputRelPath);
|
|
4547
|
-
const content = rewriteDocsContent(rawContent,
|
|
4724
|
+
const content = rewriteDocsContent(rawContent, docsDir);
|
|
4548
4725
|
const newHash = this.hasher.hash(content);
|
|
4549
|
-
if (force && await this.fs.fileExists(outputPath)) {
|
|
4726
|
+
if (force && existing !== null && await this.fs.fileExists(outputPath)) {
|
|
4550
4727
|
const diskHash = await this.fs.readFileHash(outputPath);
|
|
4551
4728
|
if (!diskHash.equals(newHash)) {
|
|
4552
4729
|
this.logger.warn(`Overwriting modified file: ${outputRelPath}`);
|
|
@@ -4557,28 +4734,74 @@ var InitUseCase = class {
|
|
|
4557
4734
|
}
|
|
4558
4735
|
generated.push(new GeneratedFile({ relativePath: outputRelPath, content, hash: newHash }));
|
|
4559
4736
|
}
|
|
4560
|
-
|
|
4561
|
-
|
|
4562
|
-
|
|
4563
|
-
|
|
4564
|
-
|
|
4565
|
-
|
|
4737
|
+
return generated;
|
|
4738
|
+
}
|
|
4739
|
+
async removeStaleDocsFiles(generated, existing, projectRoot) {
|
|
4740
|
+
const newPaths = new Set(generated.map((f) => f.relativePath));
|
|
4741
|
+
for (const oldFile of existing.getDocsFiles()) {
|
|
4742
|
+
if (!newPaths.has(oldFile.relativePath)) {
|
|
4743
|
+
await this.fs.deleteFile(join23(projectRoot, oldFile.relativePath));
|
|
4566
4744
|
}
|
|
4567
4745
|
}
|
|
4568
|
-
|
|
4569
|
-
|
|
4570
|
-
|
|
4571
|
-
|
|
4572
|
-
|
|
4573
|
-
|
|
4746
|
+
}
|
|
4747
|
+
};
|
|
4748
|
+
|
|
4749
|
+
// src/application/use-cases/shared/setup-state-detector.ts
|
|
4750
|
+
var SetupStateDetector = class {
|
|
4751
|
+
constructor(manifestRepo, fs, resolver) {
|
|
4752
|
+
this.manifestRepo = manifestRepo;
|
|
4753
|
+
this.fs = fs;
|
|
4754
|
+
this.resolver = resolver;
|
|
4755
|
+
}
|
|
4756
|
+
async detect(projectRoot) {
|
|
4757
|
+
const manifest = await this.manifestRepo.load();
|
|
4758
|
+
if (manifest === null) {
|
|
4759
|
+
return this.detectWithoutManifest(projectRoot);
|
|
4574
4760
|
}
|
|
4575
|
-
|
|
4761
|
+
const installedIds = manifest.getInstalledToolIds();
|
|
4762
|
+
if (installedIds.length === 0) {
|
|
4763
|
+
return { kind: "needs-install" };
|
|
4764
|
+
}
|
|
4765
|
+
return this.detectUpdateState(manifest, installedIds);
|
|
4766
|
+
}
|
|
4767
|
+
async detectWithoutManifest(projectRoot) {
|
|
4768
|
+
for (const tool of getAllRegisteredTools().values()) {
|
|
4769
|
+
if (await hasToolSignals(this.fs, tool, projectRoot)) return { kind: "needs-adopt" };
|
|
4770
|
+
}
|
|
4771
|
+
return { kind: "needs-init" };
|
|
4772
|
+
}
|
|
4773
|
+
async detectUpdateState(manifest, installedIds) {
|
|
4774
|
+
try {
|
|
4775
|
+
const latestVersion = await this.resolver.fetchLatestVersion(manifest.repo);
|
|
4776
|
+
const installedVersions = installedIds.map((id) => manifest.getToolVersion(id)).filter((v) => v !== void 0);
|
|
4777
|
+
const currentVersion2 = installedVersions[0] ?? "unknown";
|
|
4778
|
+
const needsUpdate = isSemver(latestVersion) && installedVersions.some((v) => !isSemver(v) || compareSemver(v, latestVersion) < 0);
|
|
4779
|
+
if (needsUpdate) {
|
|
4780
|
+
return { kind: "needs-update", currentVersion: currentVersion2, latestVersion };
|
|
4781
|
+
}
|
|
4782
|
+
} catch {
|
|
4783
|
+
}
|
|
4784
|
+
return { kind: "up-to-date" };
|
|
4576
4785
|
}
|
|
4577
4786
|
};
|
|
4578
4787
|
|
|
4579
4788
|
// src/application/use-cases/update-use-case.ts
|
|
4580
4789
|
import { join as join24 } from "path";
|
|
4581
4790
|
|
|
4791
|
+
// src/domain/models/update-scope.ts
|
|
4792
|
+
function parseUpdateScope(raw) {
|
|
4793
|
+
if (raw === "all") return { kind: "all" };
|
|
4794
|
+
if (raw === "docs") return { kind: "docs" };
|
|
4795
|
+
if (raw.startsWith("tool:")) {
|
|
4796
|
+
const toolId = raw.slice(5);
|
|
4797
|
+
return { kind: "tool", toolId };
|
|
4798
|
+
}
|
|
4799
|
+
throw new Error(`Invalid update scope: "${raw}"`);
|
|
4800
|
+
}
|
|
4801
|
+
function formatToolScopeValue(toolId) {
|
|
4802
|
+
return `tool:${toolId}`;
|
|
4803
|
+
}
|
|
4804
|
+
|
|
4582
4805
|
// src/application/use-cases/conflict-resolution-use-case.ts
|
|
4583
4806
|
var ConflictResolutionUseCase = class {
|
|
4584
4807
|
constructor(prompter) {
|
|
@@ -4642,51 +4865,70 @@ var UpdateUseCase = class {
|
|
|
4642
4865
|
}
|
|
4643
4866
|
async execute(options) {
|
|
4644
4867
|
const { force = false, dryRun = false } = options;
|
|
4645
|
-
const interactive = options.interactive ?? false;
|
|
4646
4868
|
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
|
-
|
|
4869
|
+
const isInteractive = this.resolveInteractiveFlag(options, force, dryRun);
|
|
4870
|
+
if (!isInteractive) {
|
|
4871
|
+
return this.executeInternal(options, { dryRun, force, conflictResolution }).then((r) => ({
|
|
4872
|
+
...r,
|
|
4873
|
+
version: options.version
|
|
4874
|
+
}));
|
|
4875
|
+
}
|
|
4876
|
+
return this.executeInteractive(options, conflictResolution);
|
|
4877
|
+
}
|
|
4878
|
+
resolveInteractiveFlag(options, force, dryRun) {
|
|
4879
|
+
return (options.interactive ?? false) && !force && !dryRun && options.toolIds === void 0 && !(options.docsOnly ?? false);
|
|
4880
|
+
}
|
|
4881
|
+
async executeInteractive(options, conflictResolution) {
|
|
4882
|
+
const dryRunResult = await this.executeInternal(options, {
|
|
4883
|
+
dryRun: true,
|
|
4884
|
+
force: false,
|
|
4885
|
+
conflictResolution
|
|
4886
|
+
});
|
|
4887
|
+
const outcome = await this.buildInteractiveScope(dryRunResult, options, conflictResolution);
|
|
4888
|
+
if (outcome.kind === "already-applied") {
|
|
4889
|
+
return { ...outcome.result, version: options.version };
|
|
4890
|
+
}
|
|
4891
|
+
if (outcome.kind === "cancelled") {
|
|
4892
|
+
return { ...dryRunResult, cancelled: true, version: options.version };
|
|
4893
|
+
}
|
|
4894
|
+
return this.executeInternal(
|
|
4895
|
+
{ ...options, toolIds: outcome.toolIds, docsOnly: outcome.docsOnly, force: true },
|
|
4896
|
+
{ dryRun: false, force: true, conflictResolution }
|
|
4897
|
+
).then((r) => ({ ...r, cancelled: false, version: options.version }));
|
|
4898
|
+
}
|
|
4899
|
+
/** Resolves the interactive update scope after a dry-run. */
|
|
4900
|
+
async buildInteractiveScope(dryRunResult, options, conflictResolution) {
|
|
4901
|
+
const changedTools = dryRunResult.tools.filter(
|
|
4902
|
+
(t) => t.diff.some((d) => d.kind !== "unchanged")
|
|
4903
|
+
);
|
|
4904
|
+
const docsChanged = dryRunResult.docs?.diff.some((d) => d.kind !== "unchanged") ?? false;
|
|
4905
|
+
if (changedTools.length === 0 && !docsChanged) {
|
|
4906
|
+
const result = await this.executeInternal(
|
|
4907
|
+
{ ...options, force: true },
|
|
4683
4908
|
{ dryRun: false, force: true, conflictResolution }
|
|
4684
|
-
)
|
|
4909
|
+
);
|
|
4910
|
+
return { kind: "already-applied", result };
|
|
4911
|
+
}
|
|
4912
|
+
const scopeChoices = [
|
|
4913
|
+
{ name: "All", value: "all" },
|
|
4914
|
+
...changedTools.map((t) => ({
|
|
4915
|
+
name: `${t.toolId} only`,
|
|
4916
|
+
value: formatToolScopeValue(t.toolId)
|
|
4917
|
+
})),
|
|
4918
|
+
...docsChanged ? [{ name: "docs only", value: "docs" }] : []
|
|
4919
|
+
];
|
|
4920
|
+
const scopeSelection = await this.prompter.select("What to update?", scopeChoices);
|
|
4921
|
+
const confirmed = await this.prompter.confirm("Apply update?");
|
|
4922
|
+
if (!confirmed) return { kind: "cancelled" };
|
|
4923
|
+
const scope = parseUpdateScope(scopeSelection);
|
|
4924
|
+
let toolIds;
|
|
4925
|
+
let docsOnly = false;
|
|
4926
|
+
if (scope.kind === "docs") {
|
|
4927
|
+
docsOnly = true;
|
|
4928
|
+
} else if (scope.kind === "tool") {
|
|
4929
|
+
toolIds = [scope.toolId];
|
|
4685
4930
|
}
|
|
4686
|
-
return
|
|
4687
|
-
...r,
|
|
4688
|
-
version: options.version
|
|
4689
|
-
}));
|
|
4931
|
+
return { kind: "scope", toolIds, docsOnly };
|
|
4690
4932
|
}
|
|
4691
4933
|
async executeInternal(options, internal) {
|
|
4692
4934
|
const { frameworkPath, version, projectRoot, repo } = options;
|
|
@@ -4699,78 +4941,18 @@ var UpdateUseCase = class {
|
|
|
4699
4941
|
frameworkPath,
|
|
4700
4942
|
version
|
|
4701
4943
|
);
|
|
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
|
-
}
|
|
4944
|
+
this.validateToolIds(options.toolIds, manifest);
|
|
4945
|
+
const effectiveToolIds = this.resolveEffectiveToolIds(options, docsOnly, manifest);
|
|
4946
|
+
const toolResults = await this.updateAllTools(
|
|
4947
|
+
effectiveToolIds,
|
|
4948
|
+
manifest,
|
|
4949
|
+
descriptor,
|
|
4950
|
+
contentFiles,
|
|
4951
|
+
docsDir,
|
|
4952
|
+
projectRoot,
|
|
4953
|
+
version,
|
|
4954
|
+
internal
|
|
4955
|
+
);
|
|
4774
4956
|
const hasExplicitToolFilter = !docsOnly && options.toolIds !== void 0 && options.toolIds.length > 0;
|
|
4775
4957
|
const docsResult = hasExplicitToolFilter ? null : await this.updateDocs(
|
|
4776
4958
|
manifest,
|
|
@@ -4783,17 +4965,131 @@ var UpdateUseCase = class {
|
|
|
4783
4965
|
conflictResolution
|
|
4784
4966
|
);
|
|
4785
4967
|
if (!dryRun) {
|
|
4786
|
-
await new
|
|
4968
|
+
await new PostInstallPipelineUseCase(
|
|
4969
|
+
this.fs,
|
|
4970
|
+
this.manifestRepo,
|
|
4971
|
+
this.hasher,
|
|
4972
|
+
this.git
|
|
4973
|
+
).execute({
|
|
4787
4974
|
projectRoot,
|
|
4788
4975
|
version,
|
|
4789
4976
|
descriptor,
|
|
4790
4977
|
contentFiles,
|
|
4791
|
-
manifest
|
|
4978
|
+
manifest,
|
|
4979
|
+
docsDir
|
|
4792
4980
|
});
|
|
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
4981
|
}
|
|
4982
|
+
return this.buildTotals(toolResults, docsResult, dryRun);
|
|
4983
|
+
}
|
|
4984
|
+
validateToolIds(toolIds, manifest) {
|
|
4985
|
+
if (!toolIds || toolIds.length === 0) return;
|
|
4986
|
+
const installedIds = new Set(manifest.getInstalledToolIds());
|
|
4987
|
+
const notInstalled = toolIds.filter((id) => !installedIds.has(id));
|
|
4988
|
+
if (notInstalled.length > 0) {
|
|
4989
|
+
throw new Error(
|
|
4990
|
+
`${notInstalled.join(", ")} ${notInstalled.length === 1 ? "is" : "are"} not installed. Use \`aidd install ${notInstalled.join(" ")}\` first.`
|
|
4991
|
+
);
|
|
4992
|
+
}
|
|
4993
|
+
}
|
|
4994
|
+
resolveEffectiveToolIds(options, docsOnly, manifest) {
|
|
4995
|
+
if (docsOnly) return [];
|
|
4996
|
+
if (options.toolIds && options.toolIds.length > 0) return options.toolIds;
|
|
4997
|
+
return manifest.getInstalledToolIds();
|
|
4998
|
+
}
|
|
4999
|
+
async updateAllTools(toolIds, manifest, descriptor, contentFiles, docsDir, projectRoot, version, internal) {
|
|
5000
|
+
const toolResults = [];
|
|
5001
|
+
for (const toolId of toolIds) {
|
|
5002
|
+
this.logger.debug(`Checking ${toolId} for updates...`);
|
|
5003
|
+
const result = await this.updateToolSection(
|
|
5004
|
+
toolId,
|
|
5005
|
+
manifest,
|
|
5006
|
+
descriptor,
|
|
5007
|
+
contentFiles,
|
|
5008
|
+
docsDir,
|
|
5009
|
+
projectRoot,
|
|
5010
|
+
version,
|
|
5011
|
+
internal
|
|
5012
|
+
);
|
|
5013
|
+
toolResults.push(result);
|
|
5014
|
+
}
|
|
5015
|
+
return toolResults;
|
|
5016
|
+
}
|
|
5017
|
+
async updateToolSection(toolId, manifest, descriptor, contentFiles, docsDir, projectRoot, version, internal) {
|
|
5018
|
+
const { dryRun, force, conflictResolution } = internal;
|
|
5019
|
+
const config = getToolConfig(toolId);
|
|
5020
|
+
const manifestFiles = manifest.getToolFiles(toolId);
|
|
5021
|
+
const manifestMap = new Map(manifestFiles.map((f) => [f.relativePath, f.hash]));
|
|
5022
|
+
const newDistribution = await generateDistribution(
|
|
5023
|
+
descriptor,
|
|
5024
|
+
config,
|
|
5025
|
+
docsDir,
|
|
5026
|
+
contentFiles,
|
|
5027
|
+
this.hasher,
|
|
5028
|
+
this.platform,
|
|
5029
|
+
projectRoot,
|
|
5030
|
+
this.fs
|
|
5031
|
+
);
|
|
5032
|
+
const newDistMap = new Map(newDistribution.map((f) => [f.relativePath, f]));
|
|
5033
|
+
const diff = await this.computeDiff(newDistribution, newDistMap, manifestMap, projectRoot);
|
|
5034
|
+
let result = this.emptyApplyDiffResult();
|
|
5035
|
+
if (!dryRun) {
|
|
5036
|
+
const mergedHashMap = await this.applyMergeFiles(newDistribution, projectRoot, manifest);
|
|
5037
|
+
const conflictDecisions = await this.resolveConflicts(diff, force, conflictResolution);
|
|
5038
|
+
result = await this.applyDiff(diff, newDistMap, projectRoot, conflictDecisions, manifest);
|
|
5039
|
+
this.warnUserFileConflicts(result.userFileConflicts);
|
|
5040
|
+
this.registerToolFiles(
|
|
5041
|
+
toolId,
|
|
5042
|
+
version,
|
|
5043
|
+
manifest,
|
|
5044
|
+
newDistribution,
|
|
5045
|
+
manifestFiles,
|
|
5046
|
+
result,
|
|
5047
|
+
mergedHashMap,
|
|
5048
|
+
newDistMap
|
|
5049
|
+
);
|
|
5050
|
+
}
|
|
5051
|
+
return {
|
|
5052
|
+
toolId,
|
|
5053
|
+
alreadyUpToDate: !diff.some((d) => d.kind !== "unchanged"),
|
|
5054
|
+
dryRun,
|
|
5055
|
+
diff,
|
|
5056
|
+
...result
|
|
5057
|
+
};
|
|
5058
|
+
}
|
|
5059
|
+
emptyApplyDiffResult() {
|
|
5060
|
+
return { kept: [], written: [], deleted: [], backedUp: [], userFileConflicts: [] };
|
|
5061
|
+
}
|
|
5062
|
+
warnUserFileConflicts(conflicts) {
|
|
5063
|
+
for (const relativePath of conflicts) {
|
|
5064
|
+
this.logger.warn(
|
|
5065
|
+
`\`${relativePath}\` already exists and was not installed by AIDD \u2014 skipped to preserve user file`
|
|
5066
|
+
);
|
|
5067
|
+
}
|
|
5068
|
+
}
|
|
5069
|
+
async applyMergeFiles(newDistribution, projectRoot, manifest) {
|
|
5070
|
+
const mergedHashMap = /* @__PURE__ */ new Map();
|
|
5071
|
+
for (const newFile of newDistribution) {
|
|
5072
|
+
if (!newFile.merge) continue;
|
|
5073
|
+
const outputPath = join24(projectRoot, newFile.relativePath);
|
|
5074
|
+
await this.fs.mergeJsonFile(outputPath, newFile.content);
|
|
5075
|
+
const diskHash = await this.fs.readFileHash(outputPath);
|
|
5076
|
+
manifest.syncFileHashAcrossTools(newFile.relativePath, diskHash);
|
|
5077
|
+
mergedHashMap.set(newFile.relativePath, diskHash);
|
|
5078
|
+
}
|
|
5079
|
+
return mergedHashMap;
|
|
5080
|
+
}
|
|
5081
|
+
registerToolFiles(toolId, version, manifest, newDistribution, manifestFiles, result, mergedHashMap, newDistMap) {
|
|
5082
|
+
const nonMergedFinal = newDistribution.filter((f) => !f.merge).filter(
|
|
5083
|
+
(f) => !result.deleted.includes(f.relativePath) && !result.kept.includes(f.relativePath) && !result.userFileConflicts.includes(f.relativePath)
|
|
5084
|
+
);
|
|
5085
|
+
const keptFiles = manifestFiles.filter((f) => result.kept.includes(f.relativePath)).map((f) => new GeneratedFile({ relativePath: f.relativePath, content: "", hash: f.hash }));
|
|
5086
|
+
const mergedFiles = manifestFiles.filter((f) => newDistMap.get(f.relativePath)?.merge === true).map((f) => {
|
|
5087
|
+
const hash = mergedHashMap.get(f.relativePath) ?? f.hash;
|
|
5088
|
+
return new GeneratedFile({ relativePath: f.relativePath, content: "", hash });
|
|
5089
|
+
});
|
|
5090
|
+
manifest.addTool(toolId, version, [...nonMergedFinal, ...keptFiles, ...mergedFiles]);
|
|
5091
|
+
}
|
|
5092
|
+
buildTotals(toolResults, docsResult, dryRun) {
|
|
4797
5093
|
const totalWritten = toolResults.reduce((s, t) => s + t.written.length, 0) + (docsResult?.written.length ?? 0);
|
|
4798
5094
|
const totalDeleted = toolResults.reduce((s, t) => s + t.deleted.length, 0) + (docsResult?.deleted.length ?? 0);
|
|
4799
5095
|
const toolCount = toolResults.filter((t) => !t.alreadyUpToDate).length;
|
|
@@ -4822,21 +5118,11 @@ var UpdateUseCase = class {
|
|
|
4822
5118
|
const manifestFiles = manifest.getDocsFiles();
|
|
4823
5119
|
const manifestMap = new Map(manifestFiles.map((f) => [f.relativePath, f.hash]));
|
|
4824
5120
|
const diff = await this.computeDiff(newDistribution, newDistMap, manifestMap, projectRoot);
|
|
4825
|
-
let result =
|
|
4826
|
-
kept: [],
|
|
4827
|
-
written: [],
|
|
4828
|
-
deleted: [],
|
|
4829
|
-
backedUp: [],
|
|
4830
|
-
userFileConflicts: []
|
|
4831
|
-
};
|
|
5121
|
+
let result = this.emptyApplyDiffResult();
|
|
4832
5122
|
if (!dryRun) {
|
|
4833
5123
|
const conflictDecisions = await this.resolveConflicts(diff, force, conflictResolution);
|
|
4834
5124
|
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
|
-
}
|
|
5125
|
+
this.warnUserFileConflicts(result.userFileConflicts);
|
|
4840
5126
|
const finalFiles = newDistribution.filter(
|
|
4841
5127
|
(f) => !result.deleted.includes(f.relativePath) && !result.kept.includes(f.relativePath) && !result.userFileConflicts.includes(f.relativePath)
|
|
4842
5128
|
);
|
|
@@ -4965,10 +5251,7 @@ var SetupUseCase = class {
|
|
|
4965
5251
|
}
|
|
4966
5252
|
frameworkResolver;
|
|
4967
5253
|
async execute(options) {
|
|
4968
|
-
const state = await
|
|
4969
|
-
this.manifestRepo,
|
|
4970
|
-
this.fs,
|
|
4971
|
-
this.resolver,
|
|
5254
|
+
const state = await new SetupStateDetector(this.manifestRepo, this.fs, this.resolver).detect(
|
|
4972
5255
|
options.projectRoot
|
|
4973
5256
|
);
|
|
4974
5257
|
switch (state.kind) {
|
|
@@ -4985,75 +5268,23 @@ var SetupUseCase = class {
|
|
|
4985
5268
|
}
|
|
4986
5269
|
}
|
|
4987
5270
|
async handleInit(options) {
|
|
4988
|
-
const { projectRoot,
|
|
4989
|
-
|
|
4990
|
-
|
|
4991
|
-
|
|
4992
|
-
docsDir = options.docsDir;
|
|
4993
|
-
explicitDocsDir = options.docsDir;
|
|
4994
|
-
Manifest.validateDocsDir(docsDir);
|
|
4995
|
-
} else {
|
|
4996
|
-
const docsDirInput = await this.prompter.input(
|
|
4997
|
-
"Documentation directory name:",
|
|
4998
|
-
Manifest.DEFAULT_DOCS_DIR
|
|
4999
|
-
);
|
|
5000
|
-
docsDir = docsDirInput || Manifest.DEFAULT_DOCS_DIR;
|
|
5001
|
-
explicitDocsDir = docsDirInput;
|
|
5002
|
-
Manifest.validateDocsDir(docsDir);
|
|
5003
|
-
}
|
|
5004
|
-
let frameworkPath;
|
|
5005
|
-
let frameworkRepo;
|
|
5006
|
-
if (options.path !== void 0) {
|
|
5007
|
-
if (options.path) {
|
|
5008
|
-
if (isLocalPath(options.path)) {
|
|
5009
|
-
frameworkPath = options.path;
|
|
5010
|
-
} else {
|
|
5011
|
-
frameworkRepo = options.path;
|
|
5012
|
-
}
|
|
5013
|
-
}
|
|
5014
|
-
} else {
|
|
5015
|
-
const existingManifest = await this.manifestRepo.load();
|
|
5016
|
-
const sourceDefault = existingManifest?.repo ?? this.resolver.getDefaultRepo() ?? "";
|
|
5017
|
-
const sourceInput = await this.prompter.input(
|
|
5018
|
-
"Framework source (owner/repo or local path):",
|
|
5019
|
-
sourceDefault
|
|
5020
|
-
);
|
|
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
|
-
const latest = await this.resolver.fetchLatestVersion(frameworkRepo).catch(() => "");
|
|
5032
|
-
resolvedRelease = await this.prompter.input(
|
|
5033
|
-
latest ? `Framework release tag (latest: ${latest}):` : "Framework release tag:",
|
|
5034
|
-
latest
|
|
5035
|
-
) || latest || void 0;
|
|
5036
|
-
}
|
|
5271
|
+
const { projectRoot, repo } = options;
|
|
5272
|
+
const { docsDir, explicitDocsDir } = await this.resolveDocsDir(options);
|
|
5273
|
+
const { frameworkPath, frameworkRepo } = await this.resolveFrameworkSource(options);
|
|
5274
|
+
const resolvedRelease = await this.resolveRelease(frameworkRepo, options);
|
|
5037
5275
|
const resolved = await this.frameworkResolver.execute({
|
|
5038
5276
|
path: frameworkPath,
|
|
5039
5277
|
release: resolvedRelease
|
|
5040
5278
|
});
|
|
5041
5279
|
const repoForManifest = frameworkRepo ?? repo;
|
|
5042
|
-
const initResult = await
|
|
5043
|
-
|
|
5044
|
-
|
|
5045
|
-
this.loader,
|
|
5046
|
-
this.hasher,
|
|
5047
|
-
this.logger
|
|
5048
|
-
).execute({
|
|
5049
|
-
frameworkPath: resolved.path,
|
|
5050
|
-
version: resolved.version,
|
|
5280
|
+
const initResult = await this.runInit(
|
|
5281
|
+
resolved.path,
|
|
5282
|
+
resolved.version,
|
|
5051
5283
|
docsDir,
|
|
5052
5284
|
explicitDocsDir,
|
|
5053
5285
|
projectRoot,
|
|
5054
|
-
|
|
5055
|
-
|
|
5056
|
-
});
|
|
5286
|
+
repoForManifest
|
|
5287
|
+
);
|
|
5057
5288
|
const installResults = await this.runInstall(
|
|
5058
5289
|
resolved.path,
|
|
5059
5290
|
resolved.version,
|
|
@@ -5069,32 +5300,94 @@ var SetupUseCase = class {
|
|
|
5069
5300
|
install: { results: installResults }
|
|
5070
5301
|
};
|
|
5071
5302
|
}
|
|
5072
|
-
async
|
|
5073
|
-
|
|
5074
|
-
|
|
5075
|
-
|
|
5076
|
-
|
|
5077
|
-
|
|
5078
|
-
|
|
5079
|
-
|
|
5080
|
-
|
|
5081
|
-
|
|
5303
|
+
async runInit(frameworkPath, version, docsDir, explicitDocsDir, projectRoot, repo) {
|
|
5304
|
+
return new InitUseCase(
|
|
5305
|
+
this.fs,
|
|
5306
|
+
this.manifestRepo,
|
|
5307
|
+
this.loader,
|
|
5308
|
+
this.hasher,
|
|
5309
|
+
this.logger
|
|
5310
|
+
).execute({
|
|
5311
|
+
frameworkPath,
|
|
5312
|
+
version,
|
|
5313
|
+
docsDir,
|
|
5314
|
+
explicitDocsDir,
|
|
5315
|
+
projectRoot,
|
|
5316
|
+
force: false,
|
|
5317
|
+
repo
|
|
5318
|
+
});
|
|
5319
|
+
}
|
|
5320
|
+
async resolveDocsDir(options) {
|
|
5321
|
+
if (options.docsDir !== void 0) {
|
|
5322
|
+
Manifest.validateDocsDir(options.docsDir);
|
|
5323
|
+
return { docsDir: options.docsDir, explicitDocsDir: options.docsDir };
|
|
5082
5324
|
}
|
|
5083
|
-
|
|
5084
|
-
|
|
5085
|
-
|
|
5086
|
-
|
|
5087
|
-
|
|
5088
|
-
|
|
5089
|
-
|
|
5090
|
-
|
|
5091
|
-
|
|
5092
|
-
|
|
5325
|
+
if (!options.interactive) {
|
|
5326
|
+
return { docsDir: Manifest.DEFAULT_DOCS_DIR, explicitDocsDir: "" };
|
|
5327
|
+
}
|
|
5328
|
+
const docsDirInput = await this.prompter.input(
|
|
5329
|
+
"Documentation directory name:",
|
|
5330
|
+
Manifest.DEFAULT_DOCS_DIR
|
|
5331
|
+
);
|
|
5332
|
+
const docsDir = docsDirInput || Manifest.DEFAULT_DOCS_DIR;
|
|
5333
|
+
Manifest.validateDocsDir(docsDir);
|
|
5334
|
+
return { docsDir, explicitDocsDir: docsDirInput };
|
|
5335
|
+
}
|
|
5336
|
+
async resolveFrameworkSource(options) {
|
|
5337
|
+
if (options.path !== void 0) {
|
|
5338
|
+
if (!options.path) return {};
|
|
5339
|
+
if (isLocalPath(options.path)) return { frameworkPath: options.path };
|
|
5340
|
+
return { frameworkRepo: options.path };
|
|
5341
|
+
}
|
|
5342
|
+
const existingManifest = await this.manifestRepo.load();
|
|
5343
|
+
const sourceDefault = existingManifest?.repo ?? this.resolver.getDefaultRepo() ?? "";
|
|
5344
|
+
const sourceInput = options.interactive ? await this.prompter.input("Framework source (owner/repo or local path):", sourceDefault) : sourceDefault;
|
|
5345
|
+
if (!sourceInput) return {};
|
|
5346
|
+
if (isLocalPath(sourceInput)) return { frameworkPath: sourceInput };
|
|
5347
|
+
return { frameworkRepo: sourceInput };
|
|
5348
|
+
}
|
|
5349
|
+
async resolveRelease(frameworkRepo, options) {
|
|
5350
|
+
if (options.path && isLocalPath(options.path)) return options.release;
|
|
5351
|
+
if (options.release) return options.release;
|
|
5352
|
+
if (options.interactive) {
|
|
5353
|
+
const latest = await this.resolver.fetchLatestVersion(frameworkRepo).catch(() => "");
|
|
5354
|
+
const label = latest ? `Framework release tag (latest: ${latest}):` : "Framework release tag:";
|
|
5355
|
+
return await this.prompter.input(label, latest) || latest || void 0;
|
|
5093
5356
|
}
|
|
5357
|
+
return this.resolver.fetchLatestVersion(frameworkRepo).catch(() => void 0);
|
|
5358
|
+
}
|
|
5359
|
+
async handleAdopt(options) {
|
|
5360
|
+
const { projectRoot, repo } = options;
|
|
5361
|
+
this.validateAdoptNonInteractive(options, repo);
|
|
5362
|
+
const selected = await this.resolveAdoptTools(options);
|
|
5363
|
+
const fromInput = await this.resolveAdoptFrom(options, repo);
|
|
5094
5364
|
const { path: frameworkPath, version } = await this.frameworkResolver.execute({
|
|
5095
5365
|
from: fromInput
|
|
5096
5366
|
});
|
|
5097
|
-
const adoptResult = await
|
|
5367
|
+
const adoptResult = await this.runAdopt(
|
|
5368
|
+
selected,
|
|
5369
|
+
frameworkPath,
|
|
5370
|
+
projectRoot,
|
|
5371
|
+
version
|
|
5372
|
+
);
|
|
5373
|
+
return {
|
|
5374
|
+
kind: "adopted",
|
|
5375
|
+
version,
|
|
5376
|
+
toolCount: adoptResult.tools.length,
|
|
5377
|
+
totalRegistered: adoptResult.totalRegistered,
|
|
5378
|
+
docsRegistered: adoptResult.docsRegistered
|
|
5379
|
+
};
|
|
5380
|
+
}
|
|
5381
|
+
validateAdoptNonInteractive(options, repo) {
|
|
5382
|
+
if (!options.interactive) {
|
|
5383
|
+
if (!options.toolIds || options.toolIds.length === 0) {
|
|
5384
|
+
throw new Error("--tools <ids> is required for adopt in non-interactive mode.");
|
|
5385
|
+
}
|
|
5386
|
+
if (options.from === void 0) throw new AdoptRequiresVersionError(repo);
|
|
5387
|
+
}
|
|
5388
|
+
}
|
|
5389
|
+
async runAdopt(toolIds, frameworkPath, projectRoot, version) {
|
|
5390
|
+
return new AdoptUseCase(
|
|
5098
5391
|
this.fs,
|
|
5099
5392
|
this.manifestRepo,
|
|
5100
5393
|
this.loader,
|
|
@@ -5102,19 +5395,33 @@ var SetupUseCase = class {
|
|
|
5102
5395
|
this.logger,
|
|
5103
5396
|
this.platform
|
|
5104
5397
|
).execute({
|
|
5105
|
-
toolIds
|
|
5398
|
+
toolIds,
|
|
5106
5399
|
frameworkPath,
|
|
5107
5400
|
docsDir: Manifest.DEFAULT_DOCS_DIR,
|
|
5108
5401
|
projectRoot,
|
|
5109
5402
|
version
|
|
5110
5403
|
});
|
|
5111
|
-
|
|
5112
|
-
|
|
5113
|
-
|
|
5114
|
-
|
|
5115
|
-
|
|
5116
|
-
|
|
5117
|
-
|
|
5404
|
+
}
|
|
5405
|
+
async resolveAdoptTools(options) {
|
|
5406
|
+
if (options.toolIds !== void 0 && options.toolIds.length > 0) {
|
|
5407
|
+
return options.toolIds;
|
|
5408
|
+
}
|
|
5409
|
+
const choices = VALID_TOOL_IDS.map((id) => ({ name: id, value: id, checked: false }));
|
|
5410
|
+
const checkedIds = await this.prompter.checkbox("Which tools do you want to adopt?", choices);
|
|
5411
|
+
if (checkedIds.length === 0) throw new Error("No tools selected.");
|
|
5412
|
+
return checkedIds;
|
|
5413
|
+
}
|
|
5414
|
+
async resolveAdoptFrom(options, repo) {
|
|
5415
|
+
if (options.from !== void 0) {
|
|
5416
|
+
if (!options.from) throw new AdoptRequiresVersionError(repo);
|
|
5417
|
+
return options.from;
|
|
5418
|
+
}
|
|
5419
|
+
const fromInput = await this.prompter.input(
|
|
5420
|
+
"Which version of the framework do you already have installed? (e.g. v1.2.3 or local path):",
|
|
5421
|
+
""
|
|
5422
|
+
);
|
|
5423
|
+
if (!fromInput) throw new AdoptRequiresVersionError(repo);
|
|
5424
|
+
return fromInput;
|
|
5118
5425
|
}
|
|
5119
5426
|
async handleInstall(options) {
|
|
5120
5427
|
const { projectRoot, path, release, repo } = options;
|
|
@@ -5153,12 +5460,20 @@ var SetupUseCase = class {
|
|
|
5153
5460
|
frameworkPath,
|
|
5154
5461
|
version,
|
|
5155
5462
|
projectRoot,
|
|
5156
|
-
interactive:
|
|
5463
|
+
interactive: options.interactive ?? false,
|
|
5157
5464
|
repo
|
|
5158
5465
|
});
|
|
5159
|
-
if (updateResult.cancelled) {
|
|
5160
|
-
|
|
5161
|
-
|
|
5466
|
+
if (updateResult.cancelled) return { kind: "update-cancelled" };
|
|
5467
|
+
return this.buildUpdateResult(
|
|
5468
|
+
updateResult,
|
|
5469
|
+
frameworkPath,
|
|
5470
|
+
version,
|
|
5471
|
+
projectRoot,
|
|
5472
|
+
repo,
|
|
5473
|
+
options.interactive
|
|
5474
|
+
);
|
|
5475
|
+
}
|
|
5476
|
+
async buildUpdateResult(updateResult, frameworkPath, version, projectRoot, repo, interactive) {
|
|
5162
5477
|
const updatedManifest = await this.manifestRepo.load();
|
|
5163
5478
|
const updatedInstalledIds = updatedManifest?.getInstalledToolIds() ?? [];
|
|
5164
5479
|
const missingTools = VALID_TOOL_IDS.filter((id) => !updatedInstalledIds.includes(id));
|
|
@@ -5167,7 +5482,8 @@ var SetupUseCase = class {
|
|
|
5167
5482
|
frameworkPath,
|
|
5168
5483
|
version,
|
|
5169
5484
|
projectRoot,
|
|
5170
|
-
repo
|
|
5485
|
+
repo,
|
|
5486
|
+
interactive
|
|
5171
5487
|
);
|
|
5172
5488
|
return {
|
|
5173
5489
|
kind: "updated",
|
|
@@ -5183,13 +5499,13 @@ var SetupUseCase = class {
|
|
|
5183
5499
|
const manifest = await this.manifestRepo.load();
|
|
5184
5500
|
const installedIds = manifest?.getInstalledToolIds() ?? [];
|
|
5185
5501
|
const missingTools = VALID_TOOL_IDS.filter((id) => !installedIds.includes(id));
|
|
5186
|
-
if (missingTools.length === 0) {
|
|
5187
|
-
|
|
5188
|
-
}
|
|
5502
|
+
if (missingTools.length === 0) return { kind: "up-to-date", hasAdditionalTools: false };
|
|
5503
|
+
if (!options.interactive) return { kind: "up-to-date", hasAdditionalTools: true };
|
|
5189
5504
|
const wantsMore = await this.prompter.confirm("Install additional tools?");
|
|
5190
|
-
if (!wantsMore) {
|
|
5191
|
-
|
|
5192
|
-
|
|
5505
|
+
if (!wantsMore) return { kind: "up-to-date", hasAdditionalTools: true };
|
|
5506
|
+
return this.installAdditionalTools(path, release, repo, projectRoot);
|
|
5507
|
+
}
|
|
5508
|
+
async installAdditionalTools(path, release, repo, projectRoot) {
|
|
5193
5509
|
const { path: frameworkPath, version } = await this.frameworkResolver.execute({
|
|
5194
5510
|
path,
|
|
5195
5511
|
release,
|
|
@@ -5209,8 +5525,9 @@ var SetupUseCase = class {
|
|
|
5209
5525
|
additionalInstall: { results: installResults }
|
|
5210
5526
|
};
|
|
5211
5527
|
}
|
|
5212
|
-
async offerAdditionalInstall(hasMissing, frameworkPath, version, projectRoot, repo) {
|
|
5528
|
+
async offerAdditionalInstall(hasMissing, frameworkPath, version, projectRoot, repo, interactive) {
|
|
5213
5529
|
if (!hasMissing) return void 0;
|
|
5530
|
+
if (!interactive) return void 0;
|
|
5214
5531
|
const wantsMore = await this.prompter.confirm("Install additional tools?");
|
|
5215
5532
|
if (!wantsMore) return void 0;
|
|
5216
5533
|
const installResults = await this.runInstall(
|
|
@@ -5247,30 +5564,6 @@ var SetupUseCase = class {
|
|
|
5247
5564
|
});
|
|
5248
5565
|
}
|
|
5249
5566
|
};
|
|
5250
|
-
async function detectSetupState(manifestRepo, fs, resolver, projectRoot) {
|
|
5251
|
-
const manifest = await manifestRepo.load();
|
|
5252
|
-
if (manifest === null) {
|
|
5253
|
-
for (const tool of getAllRegisteredTools().values()) {
|
|
5254
|
-
if (await hasToolSignals(fs, tool, projectRoot)) return { kind: "needs-adopt" };
|
|
5255
|
-
}
|
|
5256
|
-
return { kind: "needs-init" };
|
|
5257
|
-
}
|
|
5258
|
-
const installedIds = manifest.getInstalledToolIds();
|
|
5259
|
-
if (installedIds.length === 0) {
|
|
5260
|
-
return { kind: "needs-install" };
|
|
5261
|
-
}
|
|
5262
|
-
try {
|
|
5263
|
-
const latestVersion = await resolver.fetchLatestVersion(manifest.repo);
|
|
5264
|
-
const installedVersions = installedIds.map((id) => manifest.getToolVersion(id)).filter((v) => v !== void 0);
|
|
5265
|
-
const currentVersion2 = installedVersions[0] ?? "unknown";
|
|
5266
|
-
const needsUpdate = isSemver(latestVersion) && installedVersions.some((v) => !isSemver(v) || compareSemver(v, latestVersion) < 0);
|
|
5267
|
-
if (needsUpdate) {
|
|
5268
|
-
return { kind: "needs-update", currentVersion: currentVersion2, latestVersion };
|
|
5269
|
-
}
|
|
5270
|
-
} catch {
|
|
5271
|
-
}
|
|
5272
|
-
return { kind: "up-to-date" };
|
|
5273
|
-
}
|
|
5274
5567
|
|
|
5275
5568
|
// src/application/commands/setup.ts
|
|
5276
5569
|
function displayInstall(output, results, verbose) {
|
|
@@ -5292,78 +5585,92 @@ function displayInstall(output, results, verbose) {
|
|
|
5292
5585
|
}
|
|
5293
5586
|
}
|
|
5294
5587
|
function registerSetupCommand(program2) {
|
|
5295
|
-
program2.command("setup").description("
|
|
5296
|
-
|
|
5297
|
-
|
|
5298
|
-
|
|
5299
|
-
|
|
5300
|
-
|
|
5301
|
-
|
|
5302
|
-
|
|
5303
|
-
|
|
5304
|
-
|
|
5305
|
-
|
|
5306
|
-
|
|
5307
|
-
|
|
5308
|
-
deps.hasher,
|
|
5309
|
-
deps.logger,
|
|
5310
|
-
deps.git,
|
|
5311
|
-
deps.platform,
|
|
5312
|
-
deps.prompter,
|
|
5313
|
-
deps.resolver,
|
|
5314
|
-
deps.authReader
|
|
5315
|
-
).execute({
|
|
5316
|
-
projectRoot,
|
|
5317
|
-
path: cmdOptions.path,
|
|
5318
|
-
release: cmdOptions.release,
|
|
5319
|
-
repo,
|
|
5320
|
-
interactive: true
|
|
5321
|
-
});
|
|
5322
|
-
switch (result.kind) {
|
|
5323
|
-
case "initialized": {
|
|
5324
|
-
output.success(`Initialized docs in ${result.docsDir}/ (${result.fileCount} files)`);
|
|
5325
|
-
displayInstall(output, result.install.results, verbose);
|
|
5326
|
-
break;
|
|
5327
|
-
}
|
|
5328
|
-
case "adopted": {
|
|
5329
|
-
output.success(
|
|
5330
|
-
`Adopted ${result.toolCount} tool(s) at version ${result.version}: ${result.totalRegistered} files registered, ${result.docsRegistered} docs registered`
|
|
5331
|
-
);
|
|
5332
|
-
break;
|
|
5333
|
-
}
|
|
5334
|
-
case "installed": {
|
|
5335
|
-
displayInstall(output, result.install.results, verbose);
|
|
5336
|
-
break;
|
|
5337
|
-
}
|
|
5338
|
-
case "update-cancelled": {
|
|
5339
|
-
output.info("Update cancelled.");
|
|
5340
|
-
break;
|
|
5588
|
+
program2.command("setup").description("Set up or update the project to a correct state").option("--path <path>", "Path to a local framework directory or tarball").option("--release <tag>", "Specific framework release tag to install (e.g., v3.2.0)").option("--docs-dir <dir>", "Documentation directory name (default: aidd_docs)").option("--tools <ids>", "Comma-separated tool IDs to install (e.g., claude,cursor)").option("--all-tools", "Install all available tools").option(
|
|
5589
|
+
"--from <version>",
|
|
5590
|
+
"Framework version already installed, required for adopt (e.g., v3.2.0)"
|
|
5591
|
+
).action(
|
|
5592
|
+
async (cmdOptions) => {
|
|
5593
|
+
const { verbose, repo, output, projectRoot } = parseGlobalOptions(program2);
|
|
5594
|
+
const rawToolIds = cmdOptions.allTools ? [...VALID_TOOL_IDS] : cmdOptions.tools ? cmdOptions.tools.split(",").map((s) => s.trim()) : void 0;
|
|
5595
|
+
if (rawToolIds && rawToolIds.length > 0) {
|
|
5596
|
+
try {
|
|
5597
|
+
assertValidToolIds(rawToolIds);
|
|
5598
|
+
} catch (e) {
|
|
5599
|
+
output.error(e instanceof Error ? e.message : String(e));
|
|
5600
|
+
process.exit(1);
|
|
5341
5601
|
}
|
|
5342
|
-
|
|
5343
|
-
|
|
5344
|
-
|
|
5345
|
-
|
|
5346
|
-
|
|
5347
|
-
|
|
5602
|
+
}
|
|
5603
|
+
try {
|
|
5604
|
+
const hasScriptingFlags = !!(cmdOptions.allTools || cmdOptions.tools);
|
|
5605
|
+
const interactive = process.stdout.isTTY && !hasScriptingFlags;
|
|
5606
|
+
const deps = await createDeps(projectRoot, { verbose, repo }, output);
|
|
5607
|
+
const result = await new SetupUseCase(
|
|
5608
|
+
deps.fs,
|
|
5609
|
+
deps.manifestRepo,
|
|
5610
|
+
deps.loader,
|
|
5611
|
+
deps.hasher,
|
|
5612
|
+
deps.logger,
|
|
5613
|
+
deps.git,
|
|
5614
|
+
deps.platform,
|
|
5615
|
+
deps.prompter,
|
|
5616
|
+
deps.resolver,
|
|
5617
|
+
deps.authReader
|
|
5618
|
+
).execute({
|
|
5619
|
+
projectRoot,
|
|
5620
|
+
path: cmdOptions.path,
|
|
5621
|
+
release: cmdOptions.release,
|
|
5622
|
+
repo,
|
|
5623
|
+
interactive,
|
|
5624
|
+
docsDir: cmdOptions.docsDir,
|
|
5625
|
+
toolIds: rawToolIds,
|
|
5626
|
+
from: cmdOptions.from
|
|
5627
|
+
});
|
|
5628
|
+
switch (result.kind) {
|
|
5629
|
+
case "initialized": {
|
|
5630
|
+
output.success(`Initialized docs in ${result.docsDir}/ (${result.fileCount} files)`);
|
|
5631
|
+
displayInstall(output, result.install.results, verbose);
|
|
5632
|
+
break;
|
|
5348
5633
|
}
|
|
5349
|
-
|
|
5350
|
-
|
|
5351
|
-
|
|
5352
|
-
|
|
5353
|
-
|
|
5354
|
-
} else {
|
|
5355
|
-
output.info("Project is up to date.");
|
|
5634
|
+
case "adopted": {
|
|
5635
|
+
output.success(
|
|
5636
|
+
`Adopted ${result.toolCount} tool(s) at version ${result.version}: ${result.totalRegistered} files registered, ${result.docsRegistered} docs registered`
|
|
5637
|
+
);
|
|
5638
|
+
break;
|
|
5356
5639
|
}
|
|
5357
|
-
|
|
5358
|
-
displayInstall(output, result.
|
|
5640
|
+
case "installed": {
|
|
5641
|
+
displayInstall(output, result.install.results, verbose);
|
|
5642
|
+
break;
|
|
5643
|
+
}
|
|
5644
|
+
case "update-cancelled": {
|
|
5645
|
+
output.info("Update cancelled.");
|
|
5646
|
+
break;
|
|
5647
|
+
}
|
|
5648
|
+
case "updated": {
|
|
5649
|
+
output.success(
|
|
5650
|
+
`Updated ${result.totalWritten} files, deleted ${result.totalDeleted} files across ${result.toolCount} tool(s)`
|
|
5651
|
+
);
|
|
5652
|
+
if (result.additionalInstall) {
|
|
5653
|
+
displayInstall(output, result.additionalInstall.results, verbose);
|
|
5654
|
+
}
|
|
5655
|
+
break;
|
|
5656
|
+
}
|
|
5657
|
+
case "up-to-date": {
|
|
5658
|
+
if (result.hasAdditionalTools) {
|
|
5659
|
+
output.info("All installed tools are up to date.");
|
|
5660
|
+
} else {
|
|
5661
|
+
output.info("Project is up to date.");
|
|
5662
|
+
}
|
|
5663
|
+
if (result.additionalInstall) {
|
|
5664
|
+
displayInstall(output, result.additionalInstall.results, verbose);
|
|
5665
|
+
}
|
|
5666
|
+
break;
|
|
5359
5667
|
}
|
|
5360
|
-
break;
|
|
5361
5668
|
}
|
|
5669
|
+
} catch (error) {
|
|
5670
|
+
output.exit(error);
|
|
5362
5671
|
}
|
|
5363
|
-
} catch (error) {
|
|
5364
|
-
output.exit(error);
|
|
5365
5672
|
}
|
|
5366
|
-
|
|
5673
|
+
);
|
|
5367
5674
|
}
|
|
5368
5675
|
|
|
5369
5676
|
// src/application/commands/status.ts
|
|
@@ -5396,7 +5703,7 @@ function registerStatusCommand(program2) {
|
|
|
5396
5703
|
filterDocs,
|
|
5397
5704
|
repo
|
|
5398
5705
|
});
|
|
5399
|
-
if (report.tools.length === 0 && !filterToolId) {
|
|
5706
|
+
if (report.tools.length === 0 && !filterToolId && !filterDocs) {
|
|
5400
5707
|
output.print("No tools installed. Run `aidd install <tool>` to get started.");
|
|
5401
5708
|
if (report.inSync) return;
|
|
5402
5709
|
} else if (report.inSync) {
|
|
@@ -5429,6 +5736,27 @@ docs (v${report.docs.version}):`);
|
|
|
5429
5736
|
|
|
5430
5737
|
// src/application/use-cases/sync-use-case.ts
|
|
5431
5738
|
import { join as join26 } from "path";
|
|
5739
|
+
|
|
5740
|
+
// src/domain/models/sync-exclusions.ts
|
|
5741
|
+
var SYNC_EXCLUDED_FILES = /* @__PURE__ */ new Set([
|
|
5742
|
+
"CLAUDE.md",
|
|
5743
|
+
"AGENTS.md",
|
|
5744
|
+
".github/copilot-instructions.md",
|
|
5745
|
+
".mcp.json",
|
|
5746
|
+
".cursor/mcp.json",
|
|
5747
|
+
".vscode/mcp.json",
|
|
5748
|
+
"opencode.json",
|
|
5749
|
+
"opencode.jsonc"
|
|
5750
|
+
]);
|
|
5751
|
+
function isSyncExcluded(relativePath, docsDir) {
|
|
5752
|
+
if (SYNC_EXCLUDED_FILES.has(relativePath)) return true;
|
|
5753
|
+
if (relativePath.startsWith(".vscode/")) return true;
|
|
5754
|
+
if (relativePath.startsWith(`${docsDir}/`)) return true;
|
|
5755
|
+
if (relativePath.startsWith(".aidd/")) return true;
|
|
5756
|
+
return false;
|
|
5757
|
+
}
|
|
5758
|
+
|
|
5759
|
+
// src/application/use-cases/sync-use-case.ts
|
|
5432
5760
|
function getSectionKeyFromFrameworkPath(frameworkPath) {
|
|
5433
5761
|
if (frameworkPath.startsWith("agents/"))
|
|
5434
5762
|
return { section: "agents", key: frameworkPath.slice("agents/".length) };
|
|
@@ -5451,23 +5779,6 @@ function transformContent(content, sourceConfig, targetConfig, sectionKey, docsD
|
|
|
5451
5779
|
const targetBody = targetConfig.rewriteContent(canonicalBody, docsDir);
|
|
5452
5780
|
return serializeFrontmatter(targetFrontmatter, targetBody);
|
|
5453
5781
|
}
|
|
5454
|
-
var EXCLUDED_FILES = /* @__PURE__ */ new Set([
|
|
5455
|
-
"CLAUDE.md",
|
|
5456
|
-
"AGENTS.md",
|
|
5457
|
-
".github/copilot-instructions.md",
|
|
5458
|
-
".mcp.json",
|
|
5459
|
-
".cursor/mcp.json",
|
|
5460
|
-
".vscode/mcp.json",
|
|
5461
|
-
"opencode.json",
|
|
5462
|
-
"opencode.jsonc"
|
|
5463
|
-
]);
|
|
5464
|
-
function isExcluded(relativePath, docsDir) {
|
|
5465
|
-
if (EXCLUDED_FILES.has(relativePath)) return true;
|
|
5466
|
-
if (relativePath.startsWith(".vscode/")) return true;
|
|
5467
|
-
if (relativePath.startsWith(`${docsDir}/`)) return true;
|
|
5468
|
-
if (relativePath.startsWith(".aidd/")) return true;
|
|
5469
|
-
return false;
|
|
5470
|
-
}
|
|
5471
5782
|
function buildTargetPath(targetConfig, sectionKey) {
|
|
5472
5783
|
return targetConfig[sectionKey.section]().buildFilePath(sectionKey.key);
|
|
5473
5784
|
}
|
|
@@ -5481,143 +5792,166 @@ var SyncUseCase = class {
|
|
|
5481
5792
|
}
|
|
5482
5793
|
async execute(options) {
|
|
5483
5794
|
const { projectRoot, force = false, includeUserFiles = false, repo } = options;
|
|
5484
|
-
const interactive = options.interactive ?? false;
|
|
5485
5795
|
const manifest = await this.manifestRepo.load();
|
|
5486
|
-
if (manifest === null)
|
|
5487
|
-
throw new NoManifestError(repo);
|
|
5488
|
-
}
|
|
5796
|
+
if (manifest === null) throw new NoManifestError(repo);
|
|
5489
5797
|
const docsDir = options.docsDir ?? manifest.docsDir;
|
|
5490
|
-
|
|
5491
|
-
|
|
5492
|
-
|
|
5493
|
-
|
|
5494
|
-
|
|
5495
|
-
}
|
|
5496
|
-
const { SyncStatusUseCase: SyncStatusUseCase2 } = await Promise.resolve().then(() => (init_sync_status_use_case(), sync_status_use_case_exports));
|
|
5497
|
-
const installedIds = manifest.getInstalledToolIds();
|
|
5498
|
-
if (installedIds.length < 2) {
|
|
5499
|
-
throw new Error("Sync requires at least 2 installed tools.");
|
|
5500
|
-
}
|
|
5501
|
-
const modCounts = await new SyncStatusUseCase2(this.fs).execute(
|
|
5502
|
-
manifest,
|
|
5503
|
-
installedIds,
|
|
5504
|
-
projectRoot
|
|
5505
|
-
);
|
|
5506
|
-
const hasAnyChanges = installedIds.some((id) => {
|
|
5507
|
-
const { modified, deleted } = modCounts[id] ?? { modified: 0, deleted: 0 };
|
|
5508
|
-
return modified > 0 || deleted > 0;
|
|
5509
|
-
});
|
|
5510
|
-
if (!hasAnyChanges) {
|
|
5511
|
-
return {
|
|
5512
|
-
sourceTool: installedIds[0],
|
|
5513
|
-
tools: [],
|
|
5514
|
-
totalWritten: 0,
|
|
5515
|
-
totalDeleted: 0,
|
|
5516
|
-
totalConflicts: 0,
|
|
5517
|
-
totalSkipped: 0
|
|
5518
|
-
};
|
|
5519
|
-
}
|
|
5520
|
-
const sourceChoices = installedIds.map((id) => {
|
|
5521
|
-
const { modified, deleted } = modCounts[id] ?? { modified: 0, deleted: 0 };
|
|
5522
|
-
const hasChanges = modified > 0 || deleted > 0;
|
|
5523
|
-
const parts = [];
|
|
5524
|
-
if (modified > 0) parts.push(`${modified} modified`);
|
|
5525
|
-
if (deleted > 0) parts.push(`${deleted} deleted`);
|
|
5526
|
-
const label = hasChanges ? ` (${parts.join(", ")})` : "";
|
|
5527
|
-
return {
|
|
5528
|
-
name: `${id}${label}`,
|
|
5529
|
-
value: id,
|
|
5530
|
-
disabled: hasChanges ? false : "(no changes)"
|
|
5531
|
-
};
|
|
5532
|
-
});
|
|
5533
|
-
sourceTool = await this.prompter.select("Source tool to sync from?", sourceChoices);
|
|
5534
|
-
const targetChoices = installedIds.filter((id) => id !== sourceTool).map((id) => ({ name: id, value: id }));
|
|
5535
|
-
targetTools = await this.prompter.checkbox("Target tools?", targetChoices);
|
|
5536
|
-
if (targetTools.length === 0) throw new Error("No target tools selected.");
|
|
5537
|
-
} else {
|
|
5538
|
-
sourceTool = options.sourceTool;
|
|
5539
|
-
if (!manifest.hasTool(sourceTool)) {
|
|
5540
|
-
throw new Error(`Source tool '${sourceTool}' is not installed.`);
|
|
5541
|
-
}
|
|
5542
|
-
const installedToolIds = manifest.getInstalledToolIds();
|
|
5543
|
-
if (installedToolIds.length < 2) {
|
|
5544
|
-
throw new Error("Sync requires at least 2 installed tools.");
|
|
5545
|
-
}
|
|
5546
|
-
targetTools = targetTools && targetTools.length > 0 ? targetTools : installedToolIds.filter((id) => id !== sourceTool);
|
|
5547
|
-
for (const target of targetTools) {
|
|
5548
|
-
if (target === sourceTool) {
|
|
5549
|
-
throw new Error("Source and target cannot be the same tool.");
|
|
5550
|
-
}
|
|
5551
|
-
if (!manifest.hasTool(target)) {
|
|
5552
|
-
throw new Error(`Target tool '${target}' is not installed.`);
|
|
5553
|
-
}
|
|
5554
|
-
}
|
|
5555
|
-
}
|
|
5798
|
+
const { sourceTool, targetTools } = await this.selectSyncScope(
|
|
5799
|
+
options,
|
|
5800
|
+
manifest,
|
|
5801
|
+
options.interactive ?? false
|
|
5802
|
+
);
|
|
5556
5803
|
const sourceConfig = getToolConfig(sourceTool);
|
|
5557
5804
|
const sourceManifestFiles = manifest.getToolFiles(sourceTool);
|
|
5558
5805
|
const sourceManifestMap = new Map(sourceManifestFiles.map((f) => [f.relativePath, f.hash]));
|
|
5806
|
+
const toolResults = await this.syncAllTargets(
|
|
5807
|
+
targetTools,
|
|
5808
|
+
sourceTool,
|
|
5809
|
+
sourceConfig,
|
|
5810
|
+
sourceManifestFiles,
|
|
5811
|
+
sourceManifestMap,
|
|
5812
|
+
manifest,
|
|
5813
|
+
projectRoot,
|
|
5814
|
+
docsDir,
|
|
5815
|
+
force,
|
|
5816
|
+
includeUserFiles
|
|
5817
|
+
);
|
|
5818
|
+
return this.buildSyncTotals(sourceTool, toolResults);
|
|
5819
|
+
}
|
|
5820
|
+
async syncAllTargets(targetTools, sourceTool, sourceConfig, sourceManifestFiles, sourceManifestMap, manifest, projectRoot, docsDir, force, includeUserFiles) {
|
|
5559
5821
|
const toolResults = [];
|
|
5560
|
-
for (const targetToolId of targetTools
|
|
5822
|
+
for (const targetToolId of targetTools) {
|
|
5561
5823
|
this.logger.info(`Syncing ${sourceTool} \u2192 ${targetToolId}...`);
|
|
5562
|
-
const
|
|
5563
|
-
|
|
5564
|
-
const targetManifestMap = new Map(targetManifestFiles.map((f) => [f.relativePath, f.hash]));
|
|
5565
|
-
const targetByFrameworkPath = new Map(
|
|
5566
|
-
targetManifestFiles.filter((f) => f.frameworkPath !== void 0).map((f) => [f.frameworkPath, f.relativePath])
|
|
5567
|
-
);
|
|
5568
|
-
const fileResults = [];
|
|
5569
|
-
await this.propagateModified({
|
|
5570
|
-
sourceManifestFiles,
|
|
5824
|
+
const result = await this.syncOneTool(
|
|
5825
|
+
targetToolId,
|
|
5571
5826
|
sourceConfig,
|
|
5572
|
-
|
|
5573
|
-
targetManifestMap,
|
|
5574
|
-
targetByFrameworkPath,
|
|
5575
|
-
fileResults,
|
|
5576
|
-
projectRoot,
|
|
5577
|
-
docsDir,
|
|
5578
|
-
force
|
|
5579
|
-
});
|
|
5580
|
-
await this.propagateAdded({
|
|
5827
|
+
sourceManifestFiles,
|
|
5581
5828
|
sourceManifestMap,
|
|
5582
|
-
|
|
5583
|
-
targetConfig,
|
|
5584
|
-
fileResults,
|
|
5829
|
+
manifest,
|
|
5585
5830
|
projectRoot,
|
|
5586
5831
|
docsDir,
|
|
5832
|
+
force,
|
|
5587
5833
|
includeUserFiles
|
|
5588
|
-
|
|
5589
|
-
|
|
5590
|
-
sourceManifestFiles,
|
|
5591
|
-
targetByFrameworkPath,
|
|
5592
|
-
fileResults,
|
|
5593
|
-
projectRoot,
|
|
5594
|
-
docsDir
|
|
5595
|
-
});
|
|
5596
|
-
toolResults.push({ targetToolId, files: fileResults });
|
|
5834
|
+
);
|
|
5835
|
+
toolResults.push(result);
|
|
5597
5836
|
}
|
|
5598
|
-
|
|
5599
|
-
|
|
5600
|
-
|
|
5601
|
-
);
|
|
5602
|
-
const
|
|
5603
|
-
|
|
5604
|
-
|
|
5605
|
-
|
|
5606
|
-
const totalConflicts = toolResults.reduce(
|
|
5607
|
-
(s, t) => s + t.files.filter((f) => f.conflict && !f.written).length,
|
|
5608
|
-
0
|
|
5837
|
+
return toolResults;
|
|
5838
|
+
}
|
|
5839
|
+
async syncOneTool(targetToolId, sourceConfig, sourceManifestFiles, sourceManifestMap, manifest, projectRoot, docsDir, force, includeUserFiles) {
|
|
5840
|
+
const targetConfig = getToolConfig(targetToolId);
|
|
5841
|
+
const targetManifestFiles = manifest.getToolFiles(targetToolId);
|
|
5842
|
+
const targetManifestMap = new Map(targetManifestFiles.map((f) => [f.relativePath, f.hash]));
|
|
5843
|
+
const targetByFrameworkPath = new Map(
|
|
5844
|
+
targetManifestFiles.filter((f) => f.frameworkPath !== void 0).map((f) => [f.frameworkPath, f.relativePath])
|
|
5609
5845
|
);
|
|
5610
|
-
const
|
|
5611
|
-
|
|
5612
|
-
|
|
5846
|
+
const fileResults = [];
|
|
5847
|
+
await this.propagateModified({
|
|
5848
|
+
sourceManifestFiles,
|
|
5849
|
+
sourceConfig,
|
|
5850
|
+
targetConfig,
|
|
5851
|
+
targetManifestMap,
|
|
5852
|
+
targetByFrameworkPath,
|
|
5853
|
+
fileResults,
|
|
5854
|
+
projectRoot,
|
|
5855
|
+
docsDir,
|
|
5856
|
+
force
|
|
5857
|
+
});
|
|
5858
|
+
await this.propagateAdded({
|
|
5859
|
+
sourceManifestMap,
|
|
5860
|
+
sourceConfig,
|
|
5861
|
+
targetConfig,
|
|
5862
|
+
fileResults,
|
|
5863
|
+
projectRoot,
|
|
5864
|
+
docsDir,
|
|
5865
|
+
includeUserFiles
|
|
5866
|
+
});
|
|
5867
|
+
await this.propagateDeleted({
|
|
5868
|
+
sourceManifestFiles,
|
|
5869
|
+
targetByFrameworkPath,
|
|
5870
|
+
fileResults,
|
|
5871
|
+
projectRoot,
|
|
5872
|
+
docsDir
|
|
5873
|
+
});
|
|
5874
|
+
return { targetToolId, files: fileResults };
|
|
5875
|
+
}
|
|
5876
|
+
/** Resolves sourceTool and targetTools from options — prompts if interactive. */
|
|
5877
|
+
async selectSyncScope(options, manifest, interactive) {
|
|
5878
|
+
if (options.sourceTool !== void 0) {
|
|
5879
|
+
return this.resolveExplicitScope(options, manifest);
|
|
5880
|
+
}
|
|
5881
|
+
return this.resolveInteractiveScope(manifest, interactive, options.projectRoot);
|
|
5882
|
+
}
|
|
5883
|
+
resolveExplicitScope(options, manifest) {
|
|
5884
|
+
const sourceTool = options.sourceTool;
|
|
5885
|
+
if (!manifest.hasTool(sourceTool)) {
|
|
5886
|
+
throw new Error(`Source tool '${sourceTool}' is not installed.`);
|
|
5887
|
+
}
|
|
5888
|
+
const installedToolIds = manifest.getInstalledToolIds();
|
|
5889
|
+
if (installedToolIds.length < 2) {
|
|
5890
|
+
throw new Error("Sync requires at least 2 installed tools.");
|
|
5891
|
+
}
|
|
5892
|
+
const targetTools = options.targetTools && options.targetTools.length > 0 ? options.targetTools : installedToolIds.filter((id) => id !== sourceTool);
|
|
5893
|
+
for (const target of targetTools) {
|
|
5894
|
+
if (target === sourceTool) {
|
|
5895
|
+
throw new Error("Source and target cannot be the same tool.");
|
|
5896
|
+
}
|
|
5897
|
+
if (!manifest.hasTool(target)) {
|
|
5898
|
+
throw new Error(`Target tool '${target}' is not installed.`);
|
|
5899
|
+
}
|
|
5900
|
+
}
|
|
5901
|
+
return { sourceTool, targetTools };
|
|
5902
|
+
}
|
|
5903
|
+
async resolveInteractiveScope(manifest, interactive, projectRoot) {
|
|
5904
|
+
if (!interactive || this.prompter === void 0) {
|
|
5905
|
+
throw new Error("Source tool required in non-interactive mode.");
|
|
5906
|
+
}
|
|
5907
|
+
const { SyncStatusUseCase: SyncStatusUseCase2 } = await Promise.resolve().then(() => (init_sync_status_use_case(), sync_status_use_case_exports));
|
|
5908
|
+
const installedIds = manifest.getInstalledToolIds();
|
|
5909
|
+
if (installedIds.length < 2) {
|
|
5910
|
+
throw new Error("Sync requires at least 2 installed tools.");
|
|
5911
|
+
}
|
|
5912
|
+
const modCounts = await new SyncStatusUseCase2(this.fs).execute(
|
|
5913
|
+
manifest,
|
|
5914
|
+
installedIds,
|
|
5915
|
+
projectRoot
|
|
5613
5916
|
);
|
|
5917
|
+
const hasAnyChanges = installedIds.some((id) => {
|
|
5918
|
+
const { modified, deleted } = modCounts[id] ?? { modified: 0, deleted: 0 };
|
|
5919
|
+
return modified > 0 || deleted > 0;
|
|
5920
|
+
});
|
|
5921
|
+
if (!hasAnyChanges) {
|
|
5922
|
+
return { sourceTool: installedIds[0], targetTools: [] };
|
|
5923
|
+
}
|
|
5924
|
+
return this.promptSyncScope(installedIds, modCounts, this.prompter);
|
|
5925
|
+
}
|
|
5926
|
+
async promptSyncScope(installedIds, modCounts, prompter) {
|
|
5927
|
+
const sourceChoices = installedIds.map((id) => {
|
|
5928
|
+
const { modified, deleted } = modCounts[id] ?? { modified: 0, deleted: 0 };
|
|
5929
|
+
const hasChanges = modified > 0 || deleted > 0;
|
|
5930
|
+
const parts = [];
|
|
5931
|
+
if (modified > 0) parts.push(`${modified} modified`);
|
|
5932
|
+
if (deleted > 0) parts.push(`${deleted} deleted`);
|
|
5933
|
+
const label = hasChanges ? ` (${parts.join(", ")})` : "";
|
|
5934
|
+
return {
|
|
5935
|
+
name: `${id}${label}`,
|
|
5936
|
+
value: id,
|
|
5937
|
+
disabled: hasChanges ? false : "(no changes)"
|
|
5938
|
+
};
|
|
5939
|
+
});
|
|
5940
|
+
const sourceTool = await prompter.select("Source tool to sync from?", sourceChoices);
|
|
5941
|
+
const targetChoices = installedIds.filter((id) => id !== sourceTool).map((id) => ({ name: id, value: id }));
|
|
5942
|
+
const targetTools = await prompter.checkbox("Target tools?", targetChoices);
|
|
5943
|
+
if (targetTools.length === 0) throw new Error("No target tools selected.");
|
|
5944
|
+
return { sourceTool, targetTools };
|
|
5945
|
+
}
|
|
5946
|
+
buildSyncTotals(sourceTool, toolResults) {
|
|
5947
|
+
const count = (pred) => toolResults.reduce((s, t) => s + t.files.filter(pred).length, 0);
|
|
5614
5948
|
return {
|
|
5615
5949
|
sourceTool,
|
|
5616
5950
|
tools: toolResults,
|
|
5617
|
-
totalWritten,
|
|
5618
|
-
totalDeleted,
|
|
5619
|
-
totalConflicts,
|
|
5620
|
-
totalSkipped
|
|
5951
|
+
totalWritten: count((f) => f.written),
|
|
5952
|
+
totalDeleted: count((f) => Boolean(f.deleted)),
|
|
5953
|
+
totalConflicts: count((f) => f.conflict && !f.written),
|
|
5954
|
+
totalSkipped: count((f) => f.skipped)
|
|
5621
5955
|
};
|
|
5622
5956
|
}
|
|
5623
5957
|
async propagateModified(ctx) {
|
|
@@ -5633,64 +5967,71 @@ var SyncUseCase = class {
|
|
|
5633
5967
|
force
|
|
5634
5968
|
} = ctx;
|
|
5635
5969
|
for (const sourceManifestFile of sourceManifestFiles) {
|
|
5636
|
-
|
|
5637
|
-
|
|
5638
|
-
if (frameworkPath === void 0) continue;
|
|
5639
|
-
const diskSourcePath = join26(projectRoot, relativePath);
|
|
5640
|
-
if (!await this.fs.fileExists(diskSourcePath)) continue;
|
|
5641
|
-
const diskSourceHash = await this.fs.readFileHash(diskSourcePath);
|
|
5642
|
-
if (diskSourceHash.value === manifestHash.value) continue;
|
|
5643
|
-
const sectionKey = getSectionKeyFromFrameworkPath(frameworkPath);
|
|
5644
|
-
if (sectionKey === null) continue;
|
|
5645
|
-
const targetRelativePath = targetByFrameworkPath.get(frameworkPath);
|
|
5646
|
-
if (targetRelativePath === void 0) continue;
|
|
5647
|
-
const diskSourceContent = await this.fs.readFile(diskSourcePath);
|
|
5648
|
-
const targetContent = transformContent(
|
|
5649
|
-
diskSourceContent,
|
|
5970
|
+
await this.propagateOneModified(
|
|
5971
|
+
sourceManifestFile,
|
|
5650
5972
|
sourceConfig,
|
|
5651
5973
|
targetConfig,
|
|
5652
|
-
|
|
5653
|
-
|
|
5974
|
+
targetManifestMap,
|
|
5975
|
+
targetByFrameworkPath,
|
|
5976
|
+
fileResults,
|
|
5977
|
+
projectRoot,
|
|
5978
|
+
docsDir,
|
|
5979
|
+
force
|
|
5654
5980
|
);
|
|
5655
|
-
|
|
5656
|
-
|
|
5657
|
-
|
|
5658
|
-
|
|
5659
|
-
|
|
5660
|
-
|
|
5661
|
-
|
|
5662
|
-
|
|
5663
|
-
|
|
5664
|
-
|
|
5665
|
-
|
|
5666
|
-
|
|
5667
|
-
|
|
5668
|
-
|
|
5669
|
-
|
|
5670
|
-
|
|
5671
|
-
|
|
5672
|
-
|
|
5673
|
-
|
|
5674
|
-
|
|
5675
|
-
|
|
5676
|
-
|
|
5677
|
-
|
|
5678
|
-
|
|
5679
|
-
|
|
5680
|
-
|
|
5681
|
-
|
|
5682
|
-
|
|
5683
|
-
|
|
5684
|
-
|
|
5685
|
-
|
|
5686
|
-
|
|
5981
|
+
}
|
|
5982
|
+
}
|
|
5983
|
+
async propagateOneModified(sourceManifestFile, sourceConfig, targetConfig, targetManifestMap, targetByFrameworkPath, fileResults, projectRoot, docsDir, force) {
|
|
5984
|
+
const { relativePath, hash: manifestHash, frameworkPath } = sourceManifestFile;
|
|
5985
|
+
if (isSyncExcluded(relativePath, docsDir) || frameworkPath === void 0) return;
|
|
5986
|
+
const diskSourcePath = join26(projectRoot, relativePath);
|
|
5987
|
+
if (!await this.fs.fileExists(diskSourcePath)) return;
|
|
5988
|
+
const diskSourceHash = await this.fs.readFileHash(diskSourcePath);
|
|
5989
|
+
if (diskSourceHash.value === manifestHash.value) return;
|
|
5990
|
+
const sectionKey = getSectionKeyFromFrameworkPath(frameworkPath);
|
|
5991
|
+
const targetRelativePath = targetByFrameworkPath.get(frameworkPath);
|
|
5992
|
+
if (sectionKey === null || targetRelativePath === void 0) return;
|
|
5993
|
+
const diskSourceContent = await this.fs.readFile(diskSourcePath);
|
|
5994
|
+
const targetContent = transformContent(
|
|
5995
|
+
diskSourceContent,
|
|
5996
|
+
sourceConfig,
|
|
5997
|
+
targetConfig,
|
|
5998
|
+
sectionKey,
|
|
5999
|
+
docsDir
|
|
6000
|
+
);
|
|
6001
|
+
const diskTargetPath = join26(projectRoot, targetRelativePath);
|
|
6002
|
+
const diskTargetExists = await this.fs.fileExists(diskTargetPath);
|
|
6003
|
+
const conflict = await this.detectTargetConflict(
|
|
6004
|
+
diskTargetExists,
|
|
6005
|
+
diskTargetPath,
|
|
6006
|
+
targetRelativePath,
|
|
6007
|
+
targetManifestMap
|
|
6008
|
+
);
|
|
6009
|
+
if (diskTargetExists && await this.fs.readFile(diskTargetPath) === targetContent) {
|
|
6010
|
+
fileResults.push({
|
|
6011
|
+
relativePath: targetRelativePath,
|
|
6012
|
+
conflict: false,
|
|
6013
|
+
skipped: true,
|
|
6014
|
+
written: false
|
|
6015
|
+
});
|
|
6016
|
+
return;
|
|
6017
|
+
}
|
|
6018
|
+
if (conflict && !force) {
|
|
5687
6019
|
fileResults.push({
|
|
5688
6020
|
relativePath: targetRelativePath,
|
|
5689
|
-
conflict,
|
|
6021
|
+
conflict: true,
|
|
5690
6022
|
skipped: false,
|
|
5691
|
-
written:
|
|
6023
|
+
written: false
|
|
5692
6024
|
});
|
|
6025
|
+
return;
|
|
5693
6026
|
}
|
|
6027
|
+
await this.fs.writeFile(diskTargetPath, targetContent);
|
|
6028
|
+
fileResults.push({ relativePath: targetRelativePath, conflict, skipped: false, written: true });
|
|
6029
|
+
}
|
|
6030
|
+
async detectTargetConflict(diskTargetExists, diskTargetPath, targetRelativePath, targetManifestMap) {
|
|
6031
|
+
if (!diskTargetExists) return false;
|
|
6032
|
+
const diskTargetHash = await this.fs.readFileHash(diskTargetPath);
|
|
6033
|
+
const targetManifestHash = targetManifestMap.get(targetRelativePath);
|
|
6034
|
+
return targetManifestHash !== void 0 && diskTargetHash.value !== targetManifestHash.value;
|
|
5694
6035
|
}
|
|
5695
6036
|
async propagateAdded(ctx) {
|
|
5696
6037
|
const {
|
|
@@ -5707,65 +6048,80 @@ var SyncUseCase = class {
|
|
|
5707
6048
|
if (!sourceDirExists) return;
|
|
5708
6049
|
const sourceDiskFiles = await this.fs.listDirectory(join26(projectRoot, sourceConfig.directory));
|
|
5709
6050
|
for (const diskRelative of sourceDiskFiles) {
|
|
5710
|
-
|
|
5711
|
-
|
|
5712
|
-
|
|
5713
|
-
const sectionKey = sourceConfig.detectUserFileSectionKey(sourceRelativePath);
|
|
5714
|
-
if (sectionKey === null) continue;
|
|
5715
|
-
const targetRelativePath = buildTargetPath(targetConfig, sectionKey);
|
|
5716
|
-
if (targetRelativePath === null) continue;
|
|
5717
|
-
const diskSourceContent = await this.fs.readFile(join26(projectRoot, sourceRelativePath));
|
|
5718
|
-
const targetContent = transformContent(
|
|
5719
|
-
diskSourceContent,
|
|
6051
|
+
await this.propagateOneAdded(
|
|
6052
|
+
diskRelative,
|
|
6053
|
+
sourceManifestMap,
|
|
5720
6054
|
sourceConfig,
|
|
5721
6055
|
targetConfig,
|
|
5722
|
-
|
|
6056
|
+
fileResults,
|
|
6057
|
+
projectRoot,
|
|
5723
6058
|
docsDir
|
|
5724
6059
|
);
|
|
5725
|
-
|
|
5726
|
-
|
|
5727
|
-
|
|
5728
|
-
|
|
5729
|
-
|
|
5730
|
-
|
|
5731
|
-
|
|
5732
|
-
|
|
5733
|
-
|
|
5734
|
-
|
|
5735
|
-
|
|
5736
|
-
|
|
5737
|
-
|
|
5738
|
-
|
|
5739
|
-
|
|
6060
|
+
}
|
|
6061
|
+
}
|
|
6062
|
+
async propagateOneAdded(diskRelative, sourceManifestMap, sourceConfig, targetConfig, fileResults, projectRoot, docsDir) {
|
|
6063
|
+
const sourceRelativePath = `${sourceConfig.directory}${diskRelative}`;
|
|
6064
|
+
if (isSyncExcluded(sourceRelativePath, docsDir) || sourceManifestMap.has(sourceRelativePath))
|
|
6065
|
+
return;
|
|
6066
|
+
const sectionKey = sourceConfig.detectUserFileSectionKey(sourceRelativePath);
|
|
6067
|
+
if (sectionKey === null) return;
|
|
6068
|
+
const targetRelativePath = buildTargetPath(targetConfig, sectionKey);
|
|
6069
|
+
if (targetRelativePath === null) return;
|
|
6070
|
+
const diskSourceContent = await this.fs.readFile(join26(projectRoot, sourceRelativePath));
|
|
6071
|
+
const targetContent = transformContent(
|
|
6072
|
+
diskSourceContent,
|
|
6073
|
+
sourceConfig,
|
|
6074
|
+
targetConfig,
|
|
6075
|
+
sectionKey,
|
|
6076
|
+
docsDir
|
|
6077
|
+
);
|
|
6078
|
+
const diskTargetPath = join26(projectRoot, targetRelativePath);
|
|
6079
|
+
if (await this.fs.fileExists(diskTargetPath) && await this.fs.readFile(diskTargetPath) === targetContent) {
|
|
5740
6080
|
fileResults.push({
|
|
5741
6081
|
relativePath: targetRelativePath,
|
|
5742
6082
|
conflict: false,
|
|
5743
|
-
skipped:
|
|
5744
|
-
written:
|
|
6083
|
+
skipped: true,
|
|
6084
|
+
written: false
|
|
5745
6085
|
});
|
|
6086
|
+
return;
|
|
5746
6087
|
}
|
|
6088
|
+
await this.fs.writeFile(diskTargetPath, targetContent);
|
|
6089
|
+
fileResults.push({
|
|
6090
|
+
relativePath: targetRelativePath,
|
|
6091
|
+
conflict: false,
|
|
6092
|
+
skipped: false,
|
|
6093
|
+
written: true
|
|
6094
|
+
});
|
|
5747
6095
|
}
|
|
5748
6096
|
async propagateDeleted(ctx) {
|
|
5749
6097
|
const { sourceManifestFiles, targetByFrameworkPath, fileResults, projectRoot, docsDir } = ctx;
|
|
5750
6098
|
for (const sourceManifestFile of sourceManifestFiles) {
|
|
5751
|
-
|
|
5752
|
-
|
|
5753
|
-
|
|
5754
|
-
|
|
5755
|
-
|
|
5756
|
-
|
|
5757
|
-
|
|
5758
|
-
if (!await this.fs.fileExists(diskTargetPath)) continue;
|
|
5759
|
-
await this.fs.deleteFile(diskTargetPath);
|
|
5760
|
-
fileResults.push({
|
|
5761
|
-
relativePath: targetRelativePath,
|
|
5762
|
-
conflict: false,
|
|
5763
|
-
skipped: false,
|
|
5764
|
-
written: false,
|
|
5765
|
-
deleted: true
|
|
5766
|
-
});
|
|
6099
|
+
await this.propagateOneDeleted(
|
|
6100
|
+
sourceManifestFile,
|
|
6101
|
+
targetByFrameworkPath,
|
|
6102
|
+
fileResults,
|
|
6103
|
+
projectRoot,
|
|
6104
|
+
docsDir
|
|
6105
|
+
);
|
|
5767
6106
|
}
|
|
5768
6107
|
}
|
|
6108
|
+
async propagateOneDeleted(sourceManifestFile, targetByFrameworkPath, fileResults, projectRoot, docsDir) {
|
|
6109
|
+
const { relativePath, frameworkPath } = sourceManifestFile;
|
|
6110
|
+
if (isSyncExcluded(relativePath, docsDir) || frameworkPath === void 0) return;
|
|
6111
|
+
if (await this.fs.fileExists(join26(projectRoot, relativePath))) return;
|
|
6112
|
+
const targetRelativePath = targetByFrameworkPath.get(frameworkPath);
|
|
6113
|
+
if (targetRelativePath === void 0) return;
|
|
6114
|
+
const diskTargetPath = join26(projectRoot, targetRelativePath);
|
|
6115
|
+
if (!await this.fs.fileExists(diskTargetPath)) return;
|
|
6116
|
+
await this.fs.deleteFile(diskTargetPath);
|
|
6117
|
+
fileResults.push({
|
|
6118
|
+
relativePath: targetRelativePath,
|
|
6119
|
+
conflict: false,
|
|
6120
|
+
skipped: false,
|
|
6121
|
+
written: false,
|
|
6122
|
+
deleted: true
|
|
6123
|
+
});
|
|
6124
|
+
}
|
|
5769
6125
|
};
|
|
5770
6126
|
|
|
5771
6127
|
// src/application/commands/sync.ts
|