Skip to content

Commit 0f29c04

Browse files
committed
feat: Add initial banner for initialization
1 parent d43ef46 commit 0f29c04

File tree

15 files changed

+511
-125
lines changed

15 files changed

+511
-125
lines changed

package-lock.json

Lines changed: 281 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@
55
},
66
"dependencies": {
77
"@codifycli/ink-form": "0.0.11",
8-
"ink-select-input": "^6.0.0",
98
"@homebridge/node-pty-prebuilt-multiarch": "^0.12.0-beta.5",
109
"@inkjs/ui": "^2",
1110
"@oclif/core": "^4.0.8",
11+
"@oclif/plugin-autocomplete": "^3.2.24",
1212
"@oclif/plugin-help": "^6.2.4",
1313
"@oclif/plugin-update": "^4.6.13",
1414
"ajv": "^8.12.0",
@@ -19,6 +19,10 @@
1919
"detect-indent": "^7.0.1",
2020
"diff": "^7.0.0",
2121
"ink": "^5.1.0",
22+
"ink-big-text": "^2.0.0",
23+
"ink-gradient": "^3.0.0",
24+
"ink-select-input": "^6.0.0",
25+
"@fforres/ink-quicksearch-input": "^1.0.1",
2226
"jotai": "^2.11.1",
2327
"js-yaml": "^4.1.0",
2428
"js-yaml-source-map": "^0.2.2",
@@ -32,6 +36,7 @@
3236
},
3337
"description": "Codify allows users to configure settings, install new packages, and automate their systems using code instead of the GUI. Get set up on a new laptop in one click, maintain a Codify file within your project so anyone can get started and never lose your cool apps or favourite settings again.",
3438
"devDependencies": {
39+
"@memlab/core": "^1.1.39",
3540
"@oclif/prettier-config": "^0.2.1",
3641
"@types/chalk": "^2.2.0",
3742
"@types/debug": "^4.1.12",
@@ -44,7 +49,6 @@
4449
"@types/strip-ansi": "^5.2.1",
4550
"@typescript-eslint/eslint-plugin": "^8.16.0",
4651
"codify-plugin-lib": "^1.0.151",
47-
"react-devtools-core": "4.28.5",
4852
"esbuild": "^0.24.0",
4953
"esbuild-plugin-copy": "^2.1.1",
5054
"eslint": "^8.51.0",
@@ -54,8 +58,8 @@
5458
"ink-testing-library": "^4.0.0",
5559
"memfs": "^4.14.0",
5660
"mocha": "^10",
57-
"@memlab/core": "^1.1.39",
5861
"oclif": "^4.15.29",
62+
"react-devtools-core": "4.28.5",
5963
"shx": "^0.3.3",
6064
"strip-ansi": "^7.1.0",
6165
"tsx": "^4.7.3",

src/commands/init.ts

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import fs from 'node:fs/promises';
2+
3+
import { BaseCommand } from '../common/base-command.js';
4+
import { ShellUtils } from '../utils/shell.js';
5+
6+
export default class Init extends BaseCommand {
7+
static strict = false;
8+
static override description =
9+
`Generate codify configs from already installed packages. Use a list of space separated arguments to specify the resource types to import. Leave blank to import all resource in an existing *.codify.json file.
10+
11+
Modes:
12+
1. No args: if no args are specified and an *.codify.json already exists. Then codify will update the existing file with any new changes to the resources specified in the file/files.
13+
14+
Command: codify import
15+
16+
2. With args: specify specific resources to import using arguments. Wild card matching is supported using '*' and ? (Note: in zsh * expands to the current dir and needs to be escaped using \\* or '*'). A prompt will be shown if more information is required to complete the import.
17+
18+
Example: codify import nvm asdf\\*, codify import \\* (for importing all supported resources)
19+
20+
The results can then be saved:
21+
a. To an existing *.codify.json file
22+
b. To a new file
23+
c. Or only printed to console
24+
25+
Codify will try to smartly insert new configs by following existing spacing and formatting.
26+
`
27+
28+
static override examples = [
29+
'<%= config.bin %> <%= command.id %> homebrew nvm asdf\\*',
30+
'<%= config.bin %> <%= command.id %>',
31+
'<%= config.bin %> <%= command.id %> git-clone --path ../my/other/folder',
32+
'<%= config.bin %> <%= command.id %> \\*'
33+
]
34+
35+
public async run(): Promise<void> {
36+
const { raw, flags } = await this.parse(Init)
37+
38+
this.reporter.displayInitBanner()
39+
40+
// if (flags.path) {
41+
// this.log(`Applying Codify from: ${flags.path}`);
42+
// }
43+
//
44+
// const resolvedPath = path.resolve(flags.path ?? '.');
45+
//
46+
// const args = raw
47+
// .filter((r) => r.type === 'arg')
48+
// .map((r) => r.input);
49+
//
50+
// const cleanedArgs = await this.cleanupZshStarExpansion(args);
51+
//
52+
// await ImportOrchestrator.run({
53+
// typeIds: cleanedArgs,
54+
// path: resolvedPath,
55+
// secureMode: flags.secure,
56+
// }, this.reporter)
57+
//
58+
// process.exit(0)
59+
}
60+
61+
private async cleanupZshStarExpansion(args: string[]): Promise<string[]> {
62+
const combinedArgs = args.join(' ');
63+
const zshStarExpansion = (await ShellUtils.isZshShell())
64+
? (await fs.readdir(process.cwd())).filter((name) => !name.startsWith('.')).join(' ')
65+
: ''
66+
67+
return combinedArgs
68+
.replaceAll(zshStarExpansion, '*')
69+
.split(' ')
70+
}
71+
}

src/orchestrators/destroy.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { ProcessName, SubProcessName, ctx } from '../events/context.js';
55
import { DependencyMap, PluginManager } from '../plugins/plugin-manager.js';
66
import { Reporter } from '../ui/reporters/reporter.js';
77
import { getTypeAndNameFromId } from '../utils/index.js';
8-
import { InitializeOrchestrator } from './initialize.js';
8+
import { InitializeOrchestrator } from './initialize-plugins.js';
99

1010
export interface DestroyArgs {
1111
ids: string[];

src/orchestrators/import.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,7 @@ import { FileUtils } from '../utils/file.js';
1212
import { FileModificationCalculator, ModificationType } from '../utils/file-modification-calculator.js';
1313
import { groupBy, sleep } from '../utils/index.js';
1414
import { wildCardMatch } from '../utils/wild-card-match.js';
15-
import { InitializationResult, InitializeOrchestrator } from './initialize.js';
16-
import { ValidateOrchestrator } from './validate.js';
15+
import { InitializationResult, InitializeOrchestrator } from './initialize-plugins.js';
1716

1817
export type ImportResult = { result: ResourceConfig[], errors: string[] }
1918

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import * as fs from 'node:fs/promises'
2+
import * as os from 'node:os'
3+
import * as path from 'node:path'
4+
5+
import { Project } from '../entities/project.js';
6+
import { SubProcessName, ctx } from '../events/context.js';
7+
import { CODIFY_FILE_REGEX, CodifyParser } from '../parser/index.js';
8+
import { DependencyMap, PluginManager } from '../plugins/plugin-manager.js';
9+
import { Reporter } from '../ui/reporters/reporter.js';
10+
11+
export interface InitializeArgs {
12+
path?: string;
13+
secure?: boolean;
14+
transformProject?: (project: Project) => Project | Promise<Project>;
15+
allowEmptyProject?: boolean;
16+
}
17+
18+
export interface InitializationResult {
19+
typeIdsToDependenciesMap: DependencyMap
20+
pluginManager: PluginManager,
21+
project: Project,
22+
}
23+
24+
export class InitializeOrchestrator {
25+
static async run(
26+
args: InitializeArgs,
27+
reporter: Reporter,
28+
): Promise<InitializationResult> {
29+
let project = await InitializeOrchestrator.parse(
30+
args.path,
31+
args.allowEmptyProject ?? false,
32+
reporter
33+
)
34+
if (args.transformProject) {
35+
project = await args.transformProject(project);
36+
}
37+
38+
ctx.subprocessStarted(SubProcessName.INITIALIZE_PLUGINS)
39+
const pluginManager = new PluginManager();
40+
const typeIdsToDependenciesMap = await pluginManager.initialize(project, args.secure);
41+
ctx.subprocessFinished(SubProcessName.INITIALIZE_PLUGINS)
42+
43+
return { typeIdsToDependenciesMap, pluginManager, project };
44+
}
45+
46+
private static async parse(
47+
fileOrDir: string | undefined,
48+
allowEmptyProject: boolean,
49+
reporter: Reporter
50+
): Promise<Project> {
51+
ctx.subprocessStarted(SubProcessName.PARSE);
52+
53+
const pathToParse = (fileOrDir === undefined)
54+
? await InitializeOrchestrator.findCodifyJson()
55+
: fileOrDir
56+
57+
if (!pathToParse && !allowEmptyProject) {
58+
ctx.subprocessFinished(SubProcessName.PARSE);
59+
ctx.subprocessStarted(SubProcessName.CREATE_ROOT_FILE)
60+
const createRootCodifyFile = await reporter.promptConfirmation('\nNo codify file found. Do you want to create a root file at ~/codify.json?');
61+
62+
if (createRootCodifyFile) {
63+
await fs.writeFile(
64+
path.resolve(os.homedir(), 'codify.json'),
65+
'[]',
66+
{ encoding: 'utf8', flag: 'wx' }
67+
); // flag: 'wx' prevents overwrites if the file exists
68+
}
69+
70+
ctx.subprocessFinished(SubProcessName.CREATE_ROOT_FILE)
71+
72+
console.log('Created ~/codify.json file')
73+
process.exit(0);
74+
}
75+
76+
const project = pathToParse
77+
? await CodifyParser.parse(pathToParse)
78+
: Project.empty()
79+
80+
ctx.subprocessFinished(SubProcessName.PARSE);
81+
82+
return project
83+
}
84+
85+
private static async findCodifyJson(dir?: string): Promise<null | string> {
86+
dir = dir ?? process.cwd();
87+
88+
const filesInDir = await fs.readdir(dir);
89+
if (filesInDir.some((f) => CODIFY_FILE_REGEX.test(f))) {
90+
return dir;
91+
}
92+
93+
if (dir.includes(os.homedir()) && dir !== os.homedir()) {
94+
return this.findCodifyJson(path.dirname(dir))
95+
}
96+
97+
return null;
98+
}
99+
100+
}

src/orchestrators/initialize.ts

Lines changed: 0 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -1,100 +0,0 @@
1-
import * as fs from 'node:fs/promises'
2-
import * as os from 'node:os'
3-
import * as path from 'node:path'
4-
5-
import { Project } from '../entities/project.js';
6-
import { SubProcessName, ctx } from '../events/context.js';
7-
import { CODIFY_FILE_REGEX, CodifyParser } from '../parser/index.js';
8-
import { DependencyMap, PluginManager } from '../plugins/plugin-manager.js';
9-
import { Reporter } from '../ui/reporters/reporter.js';
10-
11-
export interface InitializeArgs {
12-
path?: string;
13-
secure?: boolean;
14-
transformProject?: (project: Project) => Project | Promise<Project>;
15-
allowEmptyProject?: boolean;
16-
}
17-
18-
export interface InitializationResult {
19-
typeIdsToDependenciesMap: DependencyMap
20-
pluginManager: PluginManager,
21-
project: Project,
22-
}
23-
24-
export class InitializeOrchestrator {
25-
static async run(
26-
args: InitializeArgs,
27-
reporter: Reporter,
28-
): Promise<InitializationResult> {
29-
let project = await InitializeOrchestrator.parse(
30-
args.path,
31-
args.allowEmptyProject ?? false,
32-
reporter
33-
)
34-
if (args.transformProject) {
35-
project = await args.transformProject(project);
36-
}
37-
38-
ctx.subprocessStarted(SubProcessName.INITIALIZE_PLUGINS)
39-
const pluginManager = new PluginManager();
40-
const typeIdsToDependenciesMap = await pluginManager.initialize(project, args.secure);
41-
ctx.subprocessFinished(SubProcessName.INITIALIZE_PLUGINS)
42-
43-
return { typeIdsToDependenciesMap, pluginManager, project };
44-
}
45-
46-
private static async parse(
47-
fileOrDir: string | undefined,
48-
allowEmptyProject: boolean,
49-
reporter: Reporter
50-
): Promise<Project> {
51-
ctx.subprocessStarted(SubProcessName.PARSE);
52-
53-
const pathToParse = (fileOrDir === undefined)
54-
? await InitializeOrchestrator.findCodifyJson()
55-
: fileOrDir
56-
57-
if (!pathToParse && !allowEmptyProject) {
58-
ctx.subprocessFinished(SubProcessName.PARSE);
59-
ctx.subprocessStarted(SubProcessName.CREATE_ROOT_FILE)
60-
const createRootCodifyFile = await reporter.promptConfirmation('\nNo codify file found. Do you want to create a root file at ~/codify.json?');
61-
62-
if (createRootCodifyFile) {
63-
await fs.writeFile(
64-
path.resolve(os.homedir(), 'codify.json'),
65-
'[]',
66-
{ encoding: 'utf8', flag: 'wx' }
67-
); // flag: 'wx' prevents overwrites if the file exists
68-
}
69-
70-
ctx.subprocessFinished(SubProcessName.CREATE_ROOT_FILE)
71-
72-
console.log('Created ~/codify.json file')
73-
process.exit(0);
74-
}
75-
76-
const project = pathToParse
77-
? await CodifyParser.parse(pathToParse)
78-
: Project.empty()
79-
80-
ctx.subprocessFinished(SubProcessName.PARSE);
81-
82-
return project
83-
}
84-
85-
private static async findCodifyJson(dir?: string): Promise<null | string> {
86-
dir = dir ?? process.cwd();
87-
88-
const filesInDir = await fs.readdir(dir);
89-
if (filesInDir.some((f) => CODIFY_FILE_REGEX.test(f))) {
90-
return dir;
91-
}
92-
93-
if (dir.includes(os.homedir()) && dir !== os.homedir()) {
94-
return this.findCodifyJson(path.dirname(dir))
95-
}
96-
97-
return null;
98-
}
99-
100-
}

src/orchestrators/plan.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { ProcessName, SubProcessName, ctx } from '../events/context.js';
44
import { DependencyMap, PluginManager } from '../plugins/plugin-manager.js';
55
import { Reporter } from '../ui/reporters/reporter.js';
66
import { createStartupShellScriptsIfNotExists } from '../utils/file.js';
7-
import { InitializeOrchestrator } from './initialize.js';
7+
import { InitializeOrchestrator } from './initialize-plugins.js';
88
import { ValidateOrchestrator } from './validate.js';
99

1010
export interface PlanArgs {

src/orchestrators/validate.ts

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,15 @@
1-
import { ctx, SubProcessName } from '../events/context.js';
2-
import { DependencyMap, PluginManager } from '../plugins/plugin-manager.js';
3-
import { Project } from '../entities/project.js';
4-
import { InitializationResult, InitializeOrchestrator } from './initialize.js';
1+
import { SubProcessName, ctx } from '../events/context.js';
52
import { Reporter } from '../ui/reporters/reporter.js';
3+
import { InitializationResult, InitializeOrchestrator } from './initialize-plugins.js';
64

75
export interface ValidateArgs {
86
existing?: InitializationResult;
97
path?: string;
108
}
119

12-
export class ValidateOrchestrator {
10+
export const ValidateOrchestrator = {
1311

14-
static async run(
12+
async run(
1513
args: ValidateArgs,
1614
reporter: Reporter
1715
): Promise<void> {
@@ -36,5 +34,5 @@ export class ValidateOrchestrator {
3634
} else {
3735
ctx.processFinished(SubProcessName.VALIDATE)
3836
}
39-
}
40-
}
37+
},
38+
};

src/ui/components/default-component.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { RenderStatus, store } from '../store/index.js';
1616
import { FileModificationDisplay } from './file-modification/FileModification.js';
1717
import { ImportResultComponent } from './import/import-result.js';
1818
import { ImportWarning } from './import/import-warning.js';
19+
import { InitBanner } from './init/InitBanner.js';
1920
import { PlanComponent } from './plan/plan.js';
2021
import { ProgressDisplay } from './progress/progress-display.js';
2122

@@ -130,5 +131,10 @@ export function DefaultComponent(props: {
130131
<ImportWarning emitter={emitter} renderData={renderData as any} />
131132
)
132133
}
134+
{
135+
renderStatus === RenderStatus.DISPLAY_INIT_BANNER && (
136+
<InitBanner/>
137+
)
138+
}
133139
</Box>
134140
}

0 commit comments

Comments
 (0)