Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ $ npm install -g codify
$ codify COMMAND
running command...
$ codify (--version)
codify/0.0.0 darwin-arm64 node-v18.15.0
codify/0.0.0 darwin-arm64 node-v18.20.2
$ codify --help [COMMAND]
USAGE
$ codify COMMAND
Expand Down Expand Up @@ -48,14 +48,12 @@ describe the command here

```
USAGE
$ codify apply [FILE] [-f] [-n <value>] [-p <value>]
$ codify apply [FILE] [-p <value>]

ARGUMENTS
FILE file to read

FLAGS
-f, --force
-n, --name=<value> name to print
-p, --path=<value> path to project

DESCRIPTION
Expand Down
3 changes: 3 additions & 0 deletions bin/run.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
#!/usr/bin/env node

// This removes any Node Experimental warnings from being printed to the CLI
process.removeAllListeners('warning')

import { flush, handle, run } from '@oclif/core'

await run(process.argv.slice(2), import.meta.url)
Expand Down
22 changes: 22 additions & 0 deletions codify.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
[
{
"type": "project",
"plugins": {
"default": "/Users/kevinwang/Projects/codify2/homebrew-plugin/src/index.ts"
}
},
{
"type": "nvm",
"global": "18.20",
"nodeVersions": [
"18.20.2"
]
},
{
"type": "homebrew",
"formulae": [
"cirruslabs/cli/cirrus",
"cirruslabs/cli/tart"
]
}
]
11 changes: 9 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,12 @@
"codify-schemas": "1.0.32",
"ajv": "^8.12.0",
"ajv-formats": "^3.0.1",
"tsx": "^4.7.3"
"tsx": "^4.7.3",
"ink": "^4.4.1",
"@inkjs/ui": "^1.0.0",
"react": "^18.3.1",
"chalk": "^5.3.0",
"parse-json": "^8.1.0"
},
"description": "Codify is a set up as code tool for developers",
"devDependencies": {
Expand All @@ -23,6 +28,8 @@
"@types/mock-fs": "^4.13.3",
"@types/node": "^18",
"@types/semver": "^7.5.4",
"@types/react": "^18.3.1",
"@types/chalk": "^2.2.0",
"eslint-config-prettier": "^9.0.0",
"chai": "^4",
"chai-as-promised": "^7.1.1",
Expand Down Expand Up @@ -77,7 +84,7 @@
"build": "shx rm -rf dist && tsc -b",
"lint": "eslint . --ext .ts",
"postpack": "shx rm -f oclif.manifest.json",
"build:release:macos": "oclif pack macos -r .",
"pkg": "oclif pack macos -r .",
"posttest": "npm run lint",
"prepack": "npm run build && oclif manifest && oclif readme",
"test": "mocha --forbid-only \"test/**/*.test.ts\"",
Expand Down
File renamed without changes.
File renamed without changes.
51 changes: 33 additions & 18 deletions src/commands/apply/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { Args, Command, Flags } from '@oclif/core'
import { ResourceOperation } from 'codify-schemas';
import path from 'node:path';

import { ApplyOrchestrator } from '../../orchestrators/apply.js';
import { PlanOrchestrator } from '../../orchestrators/plan.js';
import { DefaultReporter } from '../../ui/reporters/default-reporter.js';

export default class Apply extends Command {
static args = {
Expand All @@ -14,30 +18,41 @@ export default class Apply extends Command {
]

static flags = {
// flag with no value (-f, --force)
force: Flags.boolean({ char: 'f' }),
// flag with a value (-n, --name=VALUE)
name: Flags.string({ char: 'n', description: 'name to print' }),
// flag with a value (-p, --path=VALUE)
path: Flags.string({ char: 'p', description: 'path to project' }),
}

public async run(): Promise<void> {
const { args, flags } = await this.parse(Apply)

const name = flags.name ?? 'world'
this.log(`hello ${name} from /Users/kevinwang/Projects/codify/codify-core/src/commands/apply.ts`)
if (args.file && flags.force) {
this.log(`you input --force and --file: ${args.file}`)
const { flags } = await this.parse(Apply)
const reporter = new DefaultReporter()

try {
if (flags.path) {
this.log(`Applying Codify from: ${flags.path}`);
}

const resolvedPath = path.resolve(flags.path ?? '.');

const planResult = await PlanOrchestrator.run(resolvedPath, false);
reporter.displayPlan(planResult.plan);

// Short circuit and exit if every change is NOOP
if (planResult.plan.every((p) => p.operation === ResourceOperation.NOOP)) {
console.log('No changes necessary. Exiting');
await planResult.pluginCollection.destroy();
return process.exit(0);
}

const confirm = await reporter.promptApplyConfirmation()
if (!confirm) {
return process.exit(0);
}

await ApplyOrchestrator.run(planResult);
} catch (error: unknown) {
console.error(error);
}

if (flags.path) {
this.log(`Applying Codify from: ${flags.path}`);
}

const resolvedPath = path.resolve(flags.path ?? '.');
await ApplyOrchestrator.run(resolvedPath);

this.exit(0);
process.exit(0);
}
}
27 changes: 15 additions & 12 deletions src/commands/plan/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { Args, Command, Flags } from '@oclif/core'
import * as path from 'node:path';

import { PlanOrchestrator } from '../../orchestrators/plan.js';
import { DefaultReporter } from '../../ui/reporters/default-reporter.js';

export default class Plan extends Command {
static args = {
Expand All @@ -23,22 +25,23 @@ export default class Plan extends Command {
}

public async run(): Promise<void> {
const { args, flags } = await this.parse(Plan)
const { flags } = await this.parse(Plan)
const reporter = new DefaultReporter()

const name = flags.name ?? 'world'
this.log(`hello ${name} from /Users/kevinwang/Projects/codify/codify-core/src/commands/plan.ts`)
if (args.file && flags.force) {
this.log(`you input --force and --file: ${args.file}`)
}
try {
if (flags.path) {
this.log(`Applying Codify from: ${flags.path}`);
}

if (flags.path) {
this.log(`Applying Codify from: ${flags.path}`);
}
const resolvedPath = path.resolve(flags.path ?? '.');

const resolvedPath = path.resolve(flags.path ?? '.');
const { plan } = await PlanOrchestrator.run(resolvedPath);
reporter.displayPlan(plan);

await PlanOrchestrator.run(resolvedPath);
} catch (error) {
console.error(error);
}

this.exit(0);
process.exit(0);
}
}
97 changes: 97 additions & 0 deletions src/events/context.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import { EventEmitter } from 'node:events';

export enum Event {
STDOUT = 'stdout',
STDERR = 'stderr',
PLUGIN_STDOUT = 'plugin_stdout',
PLUGIN_STDERR = 'plugin_stderr',
DEBUG = 'debug',
OUTPUT = 'output',
PROCESS_START = 'process_start',
PROCESS_FINISH = 'process_finish',
SUB_PROCESS_START = 'sub_process_start',
SUB_PROCESS_FINISH = 'sub_process_finish',
}

export enum ProcessName {
PLAN = 'plan',
APPLY = 'apply'
}

export enum SubProcessName {
PARSE = 'parse',
INITIALIZE_PLUGINS = 'initialize_plugins',
VALIDATE = 'validate',
GENERATE_PLAN = 'generate_plan',
APPLYING_RESOURCE = 'apply_resource',
}

export const ctx = new class {
emitter: EventEmitter;

constructor() {
this.emitter = new EventEmitter();
this.attachOutputEmitters();
}

on(eventName: string | symbol, listener: (...args: any[]) => void): EventEmitter {
return this.emitter.on(eventName, listener);
}

log(...args: unknown[]) {
this.emitter.emit(Event.STDOUT, ...args);
}

pluginStdout(...args: unknown[]) {
this.emitter.emit(Event.PLUGIN_STDOUT, ...args);
}

pluginStderr(...args: unknown[]) {
this.emitter.emit(Event.PLUGIN_STDERR, ...args);
}

debug(...args: unknown[]) {
const debug = process.env.DEBUG;
if (!debug?.toLowerCase().includes('codify') && !debug?.includes('*')) {
return;
}

this.emitter.emit(Event.DEBUG, ...args);
}


processStarted(name: string) {
this.emitter.emit(Event.PROCESS_START, name);
}

processFinished(name: string) {
this.emitter.emit(Event.PROCESS_FINISH, name);
}

subprocessStarted(name: string, additionalName?: string) {
this.emitter.emit(Event.SUB_PROCESS_START, name, additionalName);
}

subprocessFinished(name: string, additionalName?: string) {
this.emitter.emit(Event.SUB_PROCESS_FINISH, name, additionalName);
}

async subprocess<T>(name: string, run: () => Promise<T>): Promise<T> {
this.emitter.emit(Event.SUB_PROCESS_START, name);
const result = await run();
this.emitter.emit(Event.SUB_PROCESS_FINISH, name);
return result;
}

attachOutputEmitters() {
this.emitter.prependListener(Event.STDOUT, (...args) => this.onOutputEvent(...args));
this.emitter.prependListener(Event.STDERR, (...args) => this.onOutputEvent(...args));
this.emitter.prependListener(Event.PLUGIN_STDOUT, (...args) => this.onOutputEvent(...args));
this.emitter.prependListener(Event.PLUGIN_STDERR, (...args) => this.onOutputEvent(...args));
this.emitter.prependListener(Event.DEBUG, (...args) => this.onOutputEvent(...args));
}

onOutputEvent(...args: unknown[]) {
this.emitter.emit(Event.OUTPUT, ...args);
}
}
30 changes: 9 additions & 21 deletions src/orchestrators/apply.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,17 @@
import { ResourceOperation } from 'codify-schemas';
import readline from 'node:readline';

import { PlanOrchestrator } from './plan.js';

const rl = readline.createInterface(process.stdin, process.stdout);
import { ctx, ProcessName } from '../events/context.js';
import { PlanOrchestratorResponse } from './plan.js';

export const ApplyOrchestrator = {
async run(rootDirectory: string): Promise<void> {
const { plan, pluginCollection } = await PlanOrchestrator.run(rootDirectory, false);

// Short circuit and exit if every change is NOOP
if (plan.every((p) => p.operation === ResourceOperation.NOOP)) {
console.log('No changes necessary. Exiting');
await pluginCollection.destroy();
return;
}

const response = await new Promise((resolve) => {
rl.question('Is this okay?\n', (answer) => resolve(answer));
});
if (response !== 'yes') {
return;
}
async run(planResult: PlanOrchestratorResponse): Promise<void> {
const { plan, pluginCollection } = planResult;
const filteredPlan = plan
.filter((p) => p.operation !== ResourceOperation.NOOP)

await pluginCollection.apply(plan);
ctx.processStarted(ProcessName.APPLY);
await pluginCollection.apply(filteredPlan);
await pluginCollection.destroy();
ctx.processFinished(ProcessName.APPLY);
},
};
20 changes: 17 additions & 3 deletions src/orchestrators/plan.ts
Original file line number Diff line number Diff line change
@@ -1,35 +1,49 @@
import { PlanResponseData } from 'codify-schemas';

import { Project } from '../entities/project.js';
import { ctx, ProcessName, SubProcessName } from '../events/context.js';
import { Parser } from '../parser/index.js';
import { PluginCollection } from '../plugins/plugin-collection.js';

interface PlanOchestratorResponse {
export interface PlanOrchestratorResponse {
plan: PlanResponseData[],
pluginCollection: PluginCollection;
project: Project;
}

export const PlanOrchestrator = {
async run(path: string, destroyPlugins = true): Promise<PlanOchestratorResponse> {
async run(path: string, destroyPlugins = true): Promise<PlanOrchestratorResponse> {
ctx.processStarted(ProcessName.PLAN)

ctx.subprocessStarted(SubProcessName.PARSE);
const project = await Parser.parseProject(path);
ctx.subprocessFinished(SubProcessName.PARSE);

ctx.subprocessStarted(SubProcessName.INITIALIZE_PLUGINS)
const pluginCollection = new PluginCollection();
const dependencyMap = await pluginCollection.initialize(project);
ctx.subprocessFinished(SubProcessName.INITIALIZE_PLUGINS)

ctx.subprocessStarted(SubProcessName.VALIDATE)
project.validateWithResourceMap(dependencyMap);
project.resolveResourceDependencies(dependencyMap);

const validationResults = await pluginCollection.validate(project);
project.handlePluginResourceValidationResults(validationResults);
project.calculateEvaluationOrder();
ctx.subprocessFinished(SubProcessName.VALIDATE)


ctx.subprocessStarted(SubProcessName.GENERATE_PLAN)
const plan = await pluginCollection.getPlan(project);
console.log(JSON.stringify(plan, null, 2));
ctx.subprocessFinished(SubProcessName.GENERATE_PLAN)

if (destroyPlugins) {
await pluginCollection.destroy();
}

ctx.processFinished(ProcessName.PLAN)

return {
plan,
pluginCollection,
Expand Down
Loading