Skip to content

Commit

Permalink
Merge pull request #291 from palmaresHQ:create-db-command-line
Browse files Browse the repository at this point in the history
Create Command Line and Templates for quick starting
  • Loading branch information
nicolasmelo1 authored Nov 16, 2024
2 parents df14a6a + 3698f53 commit eface05
Show file tree
Hide file tree
Showing 113 changed files with 2,699 additions and 524 deletions.
5 changes: 0 additions & 5 deletions .changeset/lucky-wolves-yawn.md

This file was deleted.

2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -115,3 +115,5 @@ next-env.d.ts

# macos files
.DS_Store

/bin/templates/
File renamed without changes.
29 changes: 26 additions & 3 deletions bin/package.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,26 @@
{
"name": "create-palmares-app",
"name": "create-palmares",
"version": "0.0.0",
"description": "Scaffolds a new palmares application for you. Palmares is a framework that helps you to write and run javascript on the server",
"main": "./dist/src/index.cjs",
"module": "./dist/src/index.js",
"types": "./dist/src/index.d.ts",
"bin": {
"create-palmares-app": "./dist/src/cli.js"
"please-palmares": "./dist/src/app.js",
"pp": "./dist/src/app.js",
"palmares": "./dist/src/app.js"
},
"scripts": {
"clear": "rimraf ./dist && rimraf ./templates && mkdir ./templates",
"build:all": "pnpm run clear && pnpm -w run copy:templates",
"build:types": "tsc --project tsconfig.types.json",
"build:cjs:esm": "tsup ./src --out-dir ./dist/src --format cjs,esm --silent --no-splitting",
"build": "pnpm run clear && pnpm run build:cjs:esm && pnpm run build:types",
"build:types:watch": "tsc --project tsconfig.types.json --watch --preserveWatchOutput",
"build:cjs:esm:watch": "tsup ./src --out-dir ./dist/src --format cjs,esm --watch --silent --no-splitting",
"build:watch": "pnpm run build:cjs:esm:watch & pnpm run build:types:watch",
"test:app": "tsx ./src/app.ts",
"test:db": "tsx ./src/db.ts"
},
"files": [
"dist",
Expand Down Expand Up @@ -38,5 +52,14 @@
"bugs": {
"url": "https://github.com/palmaresHQ/palmares/issues"
},
"homepage": "https://github.com/palmaresHQ/palmares#readme"
"homepage": "https://github.com/palmaresHQ/palmares#readme",
"dependencies": {
"@palmares/core": "workspace:*",
"@palmares/node-std": "workspace:*",
"@palmares/console-logging": "workspace:*",
"@palmares/logging": "workspace:*"
},
"devDependencies": {
"tsx": "^4.19.2"
}
}
233 changes: 233 additions & 0 deletions bin/src/app.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
#!/usr/bin/env node

import { ConsoleLogging } from '@palmares/console-logging';
import { Commands, CoreDomain, defineSettings, domain, getDefaultStd } from '@palmares/core';
import { Logger, loggingDomain } from '@palmares/logging';
import { NodeStd } from '@palmares/node-std';

import { recursivelyCopyFilesFromTemplate } from './utils';

import type { ExtractCommandsType } from '@palmares/core';

type TemplatesJson = {
name: string;
templates: {
name: string;
location: string;
messages: {
onStart: string;
onDoing: string;
onFinish: {
message: string;
commands: string[];
};
};
}[];
};

const logger = new Logger({ domainName: 'palmares' });
const cpaDomain = domain('palmares', '', {
commands: {
new: {
description: 'Create a new Palmares app',
positionalArgs: {
appType: {
description: 'The type of app you want to create',
type: 'string',
required: true
},
name: {
description: 'The name of the app you are creating',
type: 'string',
required: true
}
},
keywordArgs: {
template: {
description: 'The template to use for the database app',
type: 'string',
required: true
}
},
handler: async (args) => {
const std = getDefaultStd();
// @ts-ignore Trust me bro
const basePath = import.meta.dirname;
const fullPath = await std.files.join(basePath, '..', 'templates');
const allApps = await std.files.readDirectory(fullPath);
const commandLineArgs = args.commandLineArgs as ExtractCommandsType<typeof cpaDomain, 'new'>;
let appType = commandLineArgs.positionalArgs.appType;
let name = commandLineArgs.positionalArgs.name;
if (!appType) {
const appNameOptions = allApps.reduce(
(accumulator, app, index) => {
accumulator[index] = app;
return accumulator;
},
{} as Record<string, string>
);
const appTypeOption = await std.asker.askClearingScreen(
[
`\x1b[1mWhich app you want to create?\x1b[0m`,
...Object.entries(appNameOptions).map(([key, value]) => `${key}. ${value}`)
],
(answer) => `\x1b[1mWhich app you want to create?\x1b[0m ` + `\x1b[32m${appNameOptions[answer]}\x1b[0m`
);
appType = appNameOptions[appTypeOption];
}
if (!appType)
throw new Error(
'You must provide a type for the app.\n\nOptions:\n\n' + allApps.map((app) => `- ${app}`).join('\n')
);

if (!name) {
name = await std.asker.askClearingScreen(
[`\x1b[1mWhat is the name of your project?\x1b[0m`],
(answer) => `\x1b[1mWhat is the name of your project?\x1b[0m \x1b[32m${answer}\x1b[0m`
);
}

const packageManagerOptions = {
'1': 'npm',
'2': 'yarn',
'3': 'pnpm',
'4': 'bun'
};
const packageManagerOption = await std.asker.askClearingScreen(
[
`\x1b[1mWhich package manager would you like to use?\x1b[0m`,
...Object.entries(packageManagerOptions).map(([key, value]) => `${key}. ${value}`)
],
(answer) =>
`\x1b[1mWhich package manager would you like to use?\x1b[0m ` +
`\x1b[32m${packageManagerOptions[answer as keyof typeof packageManagerOptions]}\x1b[0m`
);
const packageManager = packageManagerOptions[packageManagerOption as keyof typeof packageManagerOptions];
const path = await std.files.join(fullPath, appType);
try {
const templatesJson = JSON.parse(await std.files.readFile(await std.files.join(path, 'templates.json'))) as
| TemplatesJson
| undefined;

if (!templatesJson) throw new Error('Could not find templates.json in the template directory');

const templateToUse = commandLineArgs.keywordArgs.template;
let template = templateToUse
? templatesJson.templates.find((template) => template.name === templateToUse)
: undefined;

if (!template) {
const question =
typeof templateToUse === 'string'
? `Template '${templateToUse}' not found. Choose one from the list`
: 'Which template would you like to use?';
const templateOptions = templatesJson.templates.reduce(
(accumulator, template, index) => {
accumulator[index + 1] = template.name;
return accumulator;
},
{} as Record<string, string>
);
const answer = await std.asker.askClearingScreen(
[
`\x1b[1m${question}\x1b[0m`,
...Object.entries(templateOptions).map(([key, value]) => `${key}. ${value}`)
],
(answer) => `\x1b[1m${question}\x1b[0m ` + `\x1b[32m${templateOptions[answer]}\x1b[0m`
);

template = templatesJson.templates.find((template) => template.name === templateOptions[answer]);
}

if (template?.messages['onStart'])
logger.log(
template.messages['onStart']
.replaceAll('${appName}', name)
.replaceAll('${packageManager}', packageManager)
);

const templatePath = await std.files.join(path, ...(template?.location.split('/') || []));
await recursivelyCopyFilesFromTemplate(std, name, templatePath);

logger.log(`\x1b[1mInstalling dependencies on '${name}' using '${packageManager}'...\x1b[0m`);

const done = {
done: false
};

const message1 = setTimeout(() => {
if (!done.done) logger.log(`It might take a few minutes, hang in there...`);
}, 10000);

const message2 = setTimeout(() => {
if (!done.done) logger.log(`Just a little more time...`);
}, 30000);

const message3 = setTimeout(() => {
if (!done.done)
logger.log(
`Use the millions you will earn with your '${name}' application to buy ` +
`yourself a really good internet connection...\n`
);
}, 60000);

const message4 = setTimeout(() => {
if (!done.done) logger.log(`Probably using Bun will speed it up, you should check it out!\n`);
}, 60000 * 2);

const message5 = setTimeout(() => {
if (!done.done)
logger.log(`You are still here huh? I like your perseverance, but you should give up, it's over\n`);
}, 60000 * 5);

std.childProcess.executeAndOutput(`cd ${name} && ${packageManager} i`).then(async () => {
done.done = true;
clearTimeout(message1);
clearTimeout(message2);
clearTimeout(message3);
clearTimeout(message4);
clearTimeout(message5);

if (template?.messages['onFinish']) {
for (const command of template.messages['onFinish'].commands)
console.log(
await std.childProcess.executeAndOutput(
`cd ${name} && ${command.replaceAll('${packageManager}', packageManager)}`
)
);
logger.log(
template.messages['onFinish'].message
.replaceAll('${appName}', name)
.replaceAll('${packageManager}', packageManager)
);
}
});
} catch (e) {
console.log(e);
throw new Error('Could not find templates.json in the template directory');
}
}
}
}
});

Commands.handleCommands(
defineSettings({
basePath: '',
settingsLocation: '',
std: NodeStd,
installedDomains: [
[CoreDomain, {}],
[
loggingDomain,
{
logger: ConsoleLogging
}
],
cpaDomain
]
}),
process.argv.slice(2)
);
54 changes: 54 additions & 0 deletions bin/src/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import type { Std } from '@palmares/core';

export async function recursivelyCopyFilesFromTemplate(std: Std, projectName: string, templateDirectoryPath: string) {
const recursivelyCopyFiles = async (directoryToCreate: string, path: string) => {
const templateFiles = await std.files.readDirectory(path);
await std.files.makeDirectory(directoryToCreate);

await Promise.all(
templateFiles.map(async (fileOrFolder) => {
const fileNameRenamed = fileOrFolder.replace(/^\$/g, '.').replace(/^_/g, '');
const locationPathToCopyFrom = await std.files.join(path, fileOrFolder);
try {
await std.files.readDirectory(locationPathToCopyFrom);
const newDirectoryPath = await std.files.join(directoryToCreate, fileNameRenamed);
recursivelyCopyFiles(newDirectoryPath, locationPathToCopyFrom);
} catch (e) {
//console.log(locationPathToCopyFrom);
let fileContent = await std.files.readFile(locationPathToCopyFrom);
const fileName = await std.files.join(directoryToCreate, fileNameRenamed);
//console.log('Creating file from', locationPathToCopyFrom, 'to', fileName);
const isPackageJson = fileNameRenamed === 'package.json';
if (isPackageJson) {
const fileContentAsJson = JSON.parse(fileContent);
fileContentAsJson.name = projectName;
const dependenciesAsEntries = Object.entries(fileContentAsJson.dependencies).concat(
Object.entries(fileContentAsJson.devDependencies)
);
await Promise.all(
dependenciesAsEntries.map(async ([key, value]) => {
if (value === '${version}') {
const allVersions = await std.childProcess.executeAndOutput(`npm view ${key} versions --json`);
const allVersionsAsArray = JSON.parse(allVersions) as string[];
let latestVersion = allVersionsAsArray.pop();
while (allVersionsAsArray.length > 0 && latestVersion && latestVersion.includes('-')) {
latestVersion = allVersionsAsArray.pop();
}
if (fileContentAsJson.dependencies[key]) fileContentAsJson.dependencies[key] = `^${latestVersion}`;
else fileContentAsJson.devDependencies[key] = `^${latestVersion}`;
}
})
);
fileContent = JSON.stringify(fileContentAsJson, null, 2);
}
await std.files.writeFile(
fileName,
fileContent.replace(`// eslint-disable-next-line ts/ban-ts-comment\n// @ts-nocheck\n`, '')
);
}
})
);
};

await recursivelyCopyFiles(projectName, templateDirectoryPath);
}
File renamed without changes.
15 changes: 15 additions & 0 deletions bin/test/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# @palmares/drizzle-template

To install dependencies:

```bash
bun install
```

To run:

```bash
bun run index.ts
```

This project was created using `bun init` in bun v1.1.33. [Bun](https://bun.sh) is a fast all-in-one JavaScript runtime.
34 changes: 34 additions & 0 deletions bin/test/database.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { setDatabaseConfig } from '@palmares/databases';
import { DrizzleDatabaseAdapter } from '@palmares/drizzle-engine';
import { drizzle as drizzleBetterSqlite3 } from '@palmares/drizzle-engine/better-sqlite3';
import { NodeStd } from '@palmares/node-std';
import Database from 'better-sqlite3';

import { Company, User } from './src/models';

const database = new Database('sqlite.db');

const newEngine = DrizzleDatabaseAdapter.new({
output: './.drizzle/schema.ts',
type: 'better-sqlite3',
drizzle: drizzleBetterSqlite3(database)
});

export const db = newEngine[1]().instance.instance;

export default setDatabaseConfig({
databases: {
default: {
engine: newEngine
}
},
locations: [
{
name: 'default',
path: import.meta.dirname,
getMigrations: () => [],
getModels: () => [Company, User]
}
],
std: new NodeStd()
});
Loading

0 comments on commit eface05

Please sign in to comment.