11import process from "node:process" ;
2- import { cancel , intro , outro , spinner } from "@clack/prompts" ;
2+ import { intro , outro , spinner } from "@clack/prompts" ;
33
44import {
55 applyInstallationActions ,
@@ -9,57 +9,130 @@ import {
99} from "../installer.js" ;
1010import { createGitClient } from "../git.js" ;
1111import type { InstallAction } from "../installer.js" ;
12- import { noteWrapped , promptConfirm } from "./ui.js" ;
12+ import {
13+ formatActions ,
14+ planMigration ,
15+ type FrontmatterAction ,
16+ type ReferenceUpdateAction ,
17+ type RenameAction ,
18+ } from "../migrator.js" ;
19+ import { buildDefaultInstallActions } from "./install-helpers.js" ;
20+ import { abort , getErrorMessage , hasInteractiveTty } from "./flow.js" ;
21+ import {
22+ MAX_STEP_FILE_PREVIEW_LINES ,
23+ createScanProgressBarReporter ,
24+ limitLines ,
25+ noteWrapped ,
26+ promptConfirm ,
27+ } from "./ui.js" ;
28+ import { runMigrate } from "./migrate.js" ;
1329import { runInstallSteps } from "./steps/install.js" ;
1430
1531type InstallOptions = {
1632 dryRun : boolean ;
1733 yes : boolean ;
1834} ;
1935
20- function abort ( message = "Aborted." ) : void {
21- cancel ( message ) ;
22- process . exitCode = 1 ;
23- }
24-
25- function getErrorMessage ( err : unknown ) : string {
26- if ( err instanceof Error ) return err . message ;
27- return String ( err ) ;
28- }
29-
3036function printPreview ( actions : InstallAction [ ] ) : void {
3137 process . stdout . write ( "Planned changes:\n" ) ;
3238 const preview = formatInstallActions ( actions ) . trim ( ) ;
3339 if ( preview ) process . stdout . write ( `\n${ preview } \n` ) ;
3440}
3541
42+ type MigrationInfo = {
43+ renames : RenameAction [ ] ;
44+ frontmatters : FrontmatterAction [ ] ;
45+ references : ReferenceUpdateAction [ ] ;
46+ summaryLines : string [ ] ;
47+ preview : string ;
48+ hasIssues : boolean ;
49+ } ;
50+
51+ function buildMigrationInfo ( plan : {
52+ actions : Array < RenameAction | FrontmatterAction | ReferenceUpdateAction > ;
53+ } ) : MigrationInfo {
54+ const renames = plan . actions . filter (
55+ ( a ) : a is RenameAction => a . type === "rename" ,
56+ ) ;
57+ const frontmatters = plan . actions . filter (
58+ ( a ) : a is FrontmatterAction => a . type === "frontmatter" ,
59+ ) ;
60+ const references = plan . actions . filter (
61+ ( a ) : a is ReferenceUpdateAction => a . type === "references" ,
62+ ) ;
63+
64+ const summaryLines : string [ ] = [ ] ;
65+ if ( renames . length > 0 )
66+ summaryLines . push (
67+ `- Rename/move Markdown files: ${ renames . length } file${ renames . length === 1 ? "" : "s" } ` ,
68+ ) ;
69+ if ( frontmatters . length > 0 )
70+ summaryLines . push (
71+ `- Insert YAML frontmatter: ${ frontmatters . length } file${ frontmatters . length === 1 ? "" : "s" } ` ,
72+ ) ;
73+ if ( references . length > 0 )
74+ summaryLines . push (
75+ `- Update references to renamed docs: ${ references . length } file${ references . length === 1 ? "" : "s" } ` ,
76+ ) ;
77+
78+ const previewLines : string [ ] = [ ] ;
79+ if ( renames . length > 0 ) previewLines . push ( formatActions ( renames ) ) ;
80+ if ( frontmatters . length > 0 ) previewLines . push ( formatActions ( frontmatters ) ) ;
81+ if ( references . length > 0 ) previewLines . push ( formatActions ( references ) ) ;
82+ const preview = previewLines . filter ( Boolean ) . join ( "\n" ) ;
83+
84+ return {
85+ renames,
86+ frontmatters,
87+ references,
88+ summaryLines,
89+ preview,
90+ hasIssues : renames . length + frontmatters . length + references . length > 0 ,
91+ } ;
92+ }
93+
94+ function printMigrationSummary ( info : MigrationInfo , includePreview : boolean ) {
95+ if ( ! info . hasIssues ) return ;
96+ process . stdout . write ( "SimpleDoc check failed.\n\n" ) ;
97+ if ( info . summaryLines . length > 0 ) {
98+ process . stdout . write ( `${ info . summaryLines . join ( "\n" ) } \n\n` ) ;
99+ }
100+ if ( includePreview && info . preview ) {
101+ const limited = limitLines ( info . preview , MAX_STEP_FILE_PREVIEW_LINES ) ;
102+ process . stdout . write ( `${ limited } \n\n` ) ;
103+ }
104+ }
105+
36106export async function runInstall ( options : InstallOptions ) : Promise < void > {
37107 try {
38108 const git = createGitClient ( ) ;
39109 const repoRootAbs = await git . getRepoRoot ( process . cwd ( ) ) ;
40110 const installStatus = await getInstallationStatus ( repoRootAbs ) ;
41111
42- const installActionsAll = await buildInstallationActions ( {
43- createAgentsFile : ! installStatus . agentsExists ,
44- addAttentionLine :
45- installStatus . agentsExists && ! installStatus . agentsHasAttentionLine ,
46- addSkill : ! installStatus . skillExists ,
112+ const hasTty = hasInteractiveTty ( ) ;
113+ const scanProgress = createScanProgressBarReporter ( hasTty ) ;
114+ const migrationPlan = await planMigration ( {
115+ cwd : repoRootAbs ,
116+ onProgress : scanProgress ,
47117 } ) ;
118+ const migrationInfo = buildMigrationInfo ( migrationPlan ) ;
48119
49- if ( installActionsAll . length === 0 ) {
120+ const installActionsAll = await buildDefaultInstallActions ( installStatus ) ;
121+
122+ if ( installActionsAll . length === 0 && ! migrationInfo . hasIssues ) {
50123 process . stdout . write ( "No installation needed.\n" ) ;
51124 return ;
52125 }
53126
54- const hasTty = Boolean ( process . stdin . isTTY && process . stdout . isTTY ) ;
55-
56127 if ( options . dryRun ) {
57- printPreview ( installActionsAll ) ;
128+ if ( installActionsAll . length > 0 ) printPreview ( installActionsAll ) ;
129+ if ( migrationInfo . hasIssues ) printMigrationSummary ( migrationInfo , true ) ;
58130 return ;
59131 }
60132
61133 if ( ! hasTty && ! options . yes ) {
62- printPreview ( installActionsAll ) ;
134+ if ( installActionsAll . length > 0 ) printPreview ( installActionsAll ) ;
135+ if ( migrationInfo . hasIssues ) printMigrationSummary ( migrationInfo , true ) ;
63136 process . stderr . write (
64137 "\nRefusing to apply changes without a TTY. Re-run with --yes.\n" ,
65138 ) ;
@@ -68,39 +141,73 @@ export async function runInstall(options: InstallOptions): Promise<void> {
68141 }
69142
70143 if ( options . yes ) {
71- printPreview ( installActionsAll ) ;
72- process . stderr . write ( "Applying changes...\n" ) ;
73- await applyInstallationActions ( repoRootAbs , installActionsAll ) ;
74- process . stdout . write (
75- "Done. Review with `git status` / `git diff` and commit when ready.\n" ,
76- ) ;
144+ if ( installActionsAll . length > 0 ) {
145+ printPreview ( installActionsAll ) ;
146+ process . stderr . write ( "Applying changes...\n" ) ;
147+ await applyInstallationActions ( repoRootAbs , installActionsAll ) ;
148+ }
149+ if ( migrationInfo . hasIssues ) {
150+ printMigrationSummary ( migrationInfo , false ) ;
151+ process . stdout . write ( "Run `simpledoc migrate` to fix.\n" ) ;
152+ } else {
153+ process . stdout . write (
154+ "Done. Review with `git status` / `git diff` and commit when ready.\n" ,
155+ ) ;
156+ }
77157 return ;
78158 }
79159
80160 intro ( "simpledoc install" ) ;
81161
82- const installSel = await runInstallSteps ( installStatus ) ;
83- if ( installSel === null ) return abort ( "Operation cancelled." ) ;
84-
85- const selectedInstallActions = await buildInstallationActions ( installSel ) ;
86- if ( selectedInstallActions . length === 0 ) {
87- outro ( "Nothing selected." ) ;
88- return ;
162+ if ( installActionsAll . length > 0 ) {
163+ const installSel = await runInstallSteps ( installStatus ) ;
164+ if ( installSel === null ) return abort ( "Operation cancelled." ) ;
165+
166+ const selectedInstallActions = await buildInstallationActions ( installSel ) ;
167+ if ( selectedInstallActions . length > 0 ) {
168+ noteWrapped (
169+ formatInstallActions ( selectedInstallActions ) ,
170+ "Summary of selected changes" ,
171+ ) ;
172+
173+ const apply = await promptConfirm ( "Apply these changes now?" , true ) ;
174+ if ( apply === null ) return abort ( "Operation cancelled." ) ;
175+ if ( ! apply ) return abort ( ) ;
176+
177+ const s = spinner ( ) ;
178+ s . start ( "Applying changes..." ) ;
179+ await applyInstallationActions ( repoRootAbs , selectedInstallActions ) ;
180+ s . stop ( "Done." ) ;
181+ }
89182 }
90183
91- noteWrapped (
92- formatInstallActions ( selectedInstallActions ) ,
93- "Summary of selected changes" ,
94- ) ;
95-
96- const apply = await promptConfirm ( "Apply these changes now?" , true ) ;
97- if ( apply === null ) return abort ( "Operation cancelled." ) ;
98- if ( ! apply ) return abort ( ) ;
184+ if ( migrationInfo . hasIssues ) {
185+ noteWrapped (
186+ migrationInfo . summaryLines . join ( "\n" ) ,
187+ "SimpleDoc check failed" ,
188+ ) ;
189+ if ( migrationInfo . preview ) {
190+ const limited = limitLines (
191+ migrationInfo . preview ,
192+ MAX_STEP_FILE_PREVIEW_LINES ,
193+ ) ;
194+ process . stdout . write ( `${ limited } \n\n` ) ;
195+ }
196+ const migrateNow = await promptConfirm (
197+ "Run `simpledoc migrate` now?" ,
198+ true ,
199+ ) ;
200+ if ( migrateNow === null ) return abort ( "Operation cancelled." ) ;
201+ if ( migrateNow ) {
202+ await runMigrate ( {
203+ dryRun : false ,
204+ yes : false ,
205+ force : false ,
206+ } ) ;
207+ return ;
208+ }
209+ }
99210
100- const s = spinner ( ) ;
101- s . start ( "Applying changes..." ) ;
102- await applyInstallationActions ( repoRootAbs , selectedInstallActions ) ;
103- s . stop ( "Done." ) ;
104211 outro ( "Review with `git status` / `git diff` and commit when ready." ) ;
105212 } catch ( err ) {
106213 process . stderr . write ( `${ getErrorMessage ( err ) } \n` ) ;
0 commit comments