Skip to content

Commit 52b576f

Browse files
committed
Merge branch 'main' of github.com:devforth/adminforth
2 parents 2b19b4a + 9573079 commit 52b576f

54 files changed

Lines changed: 3052 additions & 1756 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
name: Deploy Documentation
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
paths:
8+
- 'adminforth/documentation/**'
9+
- '.github/workflows/deploy_documentation.yaml'
10+
11+
jobs:
12+
deploy:
13+
runs-on: ubuntu-latest
14+
defaults:
15+
run:
16+
working-directory: adminforth
17+
steps:
18+
- name: Checkout repository
19+
uses: actions/checkout@v4
20+
- name: Install pnpm
21+
uses: pnpm/action-setup@v4
22+
with:
23+
version: 9
24+
- name: Setup Node.js
25+
uses: actions/setup-node@v4
26+
with:
27+
node-version: '20'
28+
cache: 'pnpm'
29+
cache-dependency-path: adminforth/pnpm-lock.yaml
30+
31+
- name: Configure Git
32+
run: |
33+
git config --global user.name "${{ github.actor }}"
34+
git config --global user.email "${{ github.actor }}@users.noreply.github.com"
35+
36+
- name: Install dependencies
37+
run: |
38+
pnpm i
39+
cd documentation
40+
npm ci
41+
42+
- name: Build and deploy docs
43+
run: GIT_USER=${{ github.actor }} GIT_PASS=${{ secrets.DOCUSAURUS_DEPLOY_KEY }} pnpm rollout-doc

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77

88
* [Try live demo](https://demo.adminforth.dev/)
99

10+
* [YouTube video: build agentic admin panel in a minutes](https://www.youtube.com/watch?v=4tB8uzY__uk)
11+
1012
* [Hello world in 5 minutes](https://adminforth.dev/docs/tutorial/gettingStarted) with AdminForth
1113

1214
* [Tutorial](https://adminforth.dev/docs/tutorial/Customization/branding/)
@@ -16,8 +18,10 @@
1618
<div align="center">
1719
<img src="https://github.com/user-attachments/assets/e643caad-1daa-4085-b125-cc940557a2ec"
1820
alt="AdminForth Dashboard" width="90%">
21+
1922
</div>
2023

24+
2125
<br/>
2226

2327
Why AdminForth:

adminforth/commands/callTsProxy.js

Lines changed: 39 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,27 @@ import dotenv from "dotenv";
88
const currentFilePath = import.meta.url;
99
const currentFileFolder = path.dirname(currentFilePath).replace("file:", "");
1010

11+
function getLocalBinPath(currentDirectory) {
12+
return path.join(currentDirectory, "node_modules", ".bin");
13+
}
14+
15+
function getLocalBinExecutable(currentDirectory, command) {
16+
const extension = process.platform === "win32" ? ".cmd" : "";
17+
const executablePath = path.join(getLocalBinPath(currentDirectory), `${command}${extension}`);
18+
return fs.existsSync(executablePath) ? executablePath : command;
19+
}
20+
21+
function getEnvWithLocalBin(currentDirectory) {
22+
const pathKey = process.platform === "win32" ? "Path" : "PATH";
23+
const localBinPath = getLocalBinPath(currentDirectory);
24+
const currentPath = process.env[pathKey] || "";
25+
26+
return {
27+
...process.env,
28+
[pathKey]: [localBinPath, currentPath].filter(Boolean).join(path.delimiter),
29+
};
30+
}
31+
1132
export function callTsProxy(tsCode, silent=false) {
1233

1334
const currentDirectory = process.cwd();
@@ -22,28 +43,37 @@ export function callTsProxy(tsCode, silent=false) {
2243

2344
process.env.HEAVY_DEBUG && console.log("🌐 Calling tsproxy with code:", path.join(currentFileFolder, "proxy.ts"));
2445
return new Promise((resolve, reject) => {
25-
const child = spawn("tsx", [path.join(currentFileFolder, "proxy.ts")], {
26-
env: process.env,
46+
const child = spawn(getLocalBinExecutable(currentDirectory, "tsx"), [path.join(currentFileFolder, "proxy.ts")], {
47+
env: getEnvWithLocalBin(currentDirectory),
2748
});
2849
let stderr = "";
29-
let stdoutLogs = [];
50+
let stdout = "";
3051

3152
child.stdout.on("data", (data) => {
32-
stdoutLogs.push(data.toString());
53+
stdout += data.toString();
3354
});
3455

3556
child.stderr.on("data", (data) => {
3657
stderr += data;
3758
});
3859

60+
child.on("error", (error) => {
61+
reject(error);
62+
});
63+
3964
child.on("close", (code) => {
40-
const tsProxyResult = stdoutLogs.find(log => log.includes('>>>>>>>'));
41-
const preparedStdout = tsProxyResult.slice(tsProxyResult.indexOf('>>>>>>>') + 7, tsProxyResult.lastIndexOf('<<<<<<<'));
42-
const preparedStdoutLogs = stdoutLogs.filter(log => !log.includes('>>>>>>>'));
65+
const resultStart = stdout.indexOf('>>>>>>>');
66+
const resultEnd = stdout.lastIndexOf('<<<<<<<');
67+
if (resultStart === -1 || resultEnd === -1 || resultEnd < resultStart) {
68+
reject(new Error(`Invalid JSON from tsproxy. stdout: ${stdout}, stderr: ${stderr}`));
69+
return;
70+
}
71+
const preparedStdout = stdout.slice(resultStart + 7, resultEnd);
72+
const preparedStdoutLogs = stdout.slice(0, resultStart);
4373
if (code === 0) {
4474
try {
45-
for (const log of preparedStdoutLogs) {
46-
console.log(log);
75+
if (preparedStdoutLogs) {
76+
process.stdout.write(preparedStdoutLogs);
4777
}
4878
const parsed = JSON.parse(preparedStdout);
4979
if (!silent) {

adminforth/commands/createApp/templates/Dockerfile.hbs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,5 @@ ADD package.json {{dockerAdditionalManifestFiles}} /code/
55
RUN {{packageManager}} {{dockerPackageInstallSubcommand}}
66
ADD . /code/
77
RUN {{packageManagerExec}} adminforth bundle
8+
RUN {{ packageManager }} run build
89
CMD ["sh", "-c", "{{packageManagerRun}} migrate:prod && {{packageManagerRun}} prod"]

adminforth/commands/createApp/templates/index.ts.hbs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ export const admin = new AdminForth({
1919
loginBackgroundImage: 'https://images.unsplash.com/photo-1534239697798-120952b76f2b?q=80&w=3389&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D',
2020
loginBackgroundPosition: '1/2',
2121
loginPromptHTML: async () => {
22-
const adminforthUserExists = await admin.resource("adminuser").count(Filters.EQ('email', 'adminforth')) > 0;
22+
const adminforthUserExists = (await admin.resource("adminuser").count(Filters.EQ('email', 'adminforth'))) > 0;
2323
if (adminforthUserExists) {
2424
return "Please use <b>adminforth</b> as username and <b>adminforth</b> as password"
2525
}
@@ -55,15 +55,15 @@ export const admin = new AdminForth({
5555
},
5656
],
5757
resources: [
58-
usersResource
58+
usersResource,
5959
],
6060
menu: [
6161
{ type: 'heading', label: 'SYSTEM' },
6262
{
6363
label: 'Users',
6464
icon: 'flowbite:user-solid',
6565
resourceId: 'adminuser'
66-
}
66+
},
6767
],
6868
});
6969

@@ -82,7 +82,7 @@ if (fileURLToPath(import.meta.url) === path.resolve(process.argv[1])) {
8282
admin.express.serve(app);
8383

8484
admin.discoverDatabases().then(async () => {
85-
if (await admin.resource('adminuser').count() === 0) {
85+
if ((await admin.resource('adminuser').count()) === 0) {
8686
await admin.resource('adminuser').create({
8787
email: 'adminforth',
8888
password_hash: await AdminForth.Utils.generatePasswordHash('adminforth'),

adminforth/commands/createApp/templates/package.json.hbs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@
99
"description": "",
1010
"scripts": {
1111
"dev": "{{packageManagerEnvDev}} tsx watch index.ts",
12-
"prod": "{{packageManagerEnvProd}} tsx index.ts",
12+
"build": "{{packageManagerEnvProd}} tsup index.ts --format esm --clean",
13+
"prod": "{{packageManagerEnvProd}} node dist/index.js",
1314
"start": "{{packageManagerRun}} dev",
1415
{{#if includePrismaMigrations}}
1516
"makemigration": "{{packageManagerEnvDev}} npx --yes prisma migrate dev --create-only",
@@ -30,10 +31,12 @@
3031
},
3132
"devDependencies": {
3233
"typescript": "6.0.3",
34+
"tsup": "^8.5.1",
3335
"tsx": "4.11.2",
3436
"@types/express": "^4.17.21",
35-
"@types/node": "latest",
37+
"@types/node": "latest"{{#if includePrismaMigrations}},
3638
"@prisma/client": "latest",
3739
"prisma": "^7.0.0"
40+
{{/if}}
3841
}
39-
}
42+
}

adminforth/commands/createApp/utils.js

Lines changed: 40 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ function detectAdminforthVersion() {
3737
}
3838

3939
const adminforthVersion = detectAdminforthVersion();
40+
const SUPPORTED_DB_URL_SCHEMES = ['sqlite://', 'postgresql://', 'mongodb://', 'mysql://', 'clickhouse://'];
41+
const PRISMA_MIGRATION_DB_PROTOCOLS = ['sqlite', 'postgres', 'postgresql', 'mysql'];
4042

4143

4244
export function parseArgumentsIntoOptions(rawArgs) {
@@ -56,7 +58,6 @@ export function parseArgumentsIntoOptions(rawArgs) {
5658
appName: args['--app-name'],
5759
db: args['--db'],
5860
useNpm: args['--use-npm'],
59-
includePrismaMigrations: args['--include-prisma-migrations'],
6061
};
6162
}
6263

@@ -94,8 +95,19 @@ export async function promptForMissingOptions(options) {
9495
});
9596
}
9697

97-
if (!options.includePrismaMigrations) {
98-
questions.push({
98+
const answers = await inquirer.prompt(questions);
99+
const resolvedOptions = {
100+
...options,
101+
appName: options.appName || answers.appName,
102+
db: options.db || answers.db,
103+
useNpm: options.useNpm || answers.useNpm,
104+
};
105+
106+
if (
107+
resolvedOptions.includePrismaMigrations === undefined &&
108+
isPrismaMigrationDbUrl(resolvedOptions.db)
109+
) {
110+
const prismaAnswer = await inquirer.prompt([{
99111
type: 'select',
100112
name: 'includePrismaMigrations',
101113
message: 'Include Prisma migrations? >',
@@ -104,18 +116,13 @@ export async function promptForMissingOptions(options) {
104116
{ name: 'No', value: false },
105117
],
106118
default: true,
107-
});
108-
119+
}]);
120+
resolvedOptions.includePrismaMigrations = prismaAnswer.includePrismaMigrations;
121+
} else {
122+
resolvedOptions.includePrismaMigrations = Boolean(resolvedOptions.includePrismaMigrations);
109123
}
110124

111-
const answers = await inquirer.prompt(questions);
112-
return {
113-
...options,
114-
appName: options.appName || answers.appName,
115-
db: options.db || answers.db,
116-
useNpm: options.useNpm || answers.useNpm,
117-
includePrismaMigrations: options.includePrismaMigrations || answers.includePrismaMigrations,
118-
};
125+
return resolvedOptions;
119126
}
120127

121128
function checkNodeVersion(minRequiredVersion = 20) {
@@ -134,6 +141,15 @@ function parseConnectionString(dbUrl) {
134141
return new ConnectionString(dbUrl);
135142
}
136143

144+
function isPrismaMigrationDbUrl(dbUrl) {
145+
try {
146+
const connectionString = parseConnectionString(dbUrl);
147+
return PRISMA_MIGRATION_DB_PROTOCOLS.includes(connectionString.protocol);
148+
} catch {
149+
return false;
150+
}
151+
}
152+
137153
function detectDbProvider(protocol) {
138154
if (protocol.startsWith('sqlite')) {
139155
return 'sqlite';
@@ -143,25 +159,27 @@ function detectDbProvider(protocol) {
143159
return 'mongodb';
144160
} else if (protocol.startsWith('mysql')) {
145161
return 'mysql';
162+
} else if (protocol.startsWith('clickhouse')) {
163+
return 'clickhouse';
146164
}
147165

148-
const message = `Unknown database provider for ${protocol}. Only SQLite, PostgreSQL, and MongoDB are supported now.`;
166+
const message = `Unknown database provider for ${protocol}. Supported database URL schemes: ${SUPPORTED_DB_URL_SCHEMES.join(', ')}.`;
149167
throw new Error(message);
150168
}
151169

152170
function generateDbUrlForPrisma(connectionString) {
171+
if (!PRISMA_MIGRATION_DB_PROTOCOLS.includes(connectionString.protocol))
172+
return null;
153173
if (connectionString.protocol.startsWith('sqlite'))
154174
return `file:${connectionString.host}`;
155-
if (connectionString.protocol.startsWith('mongodb'))
156-
return null;
157175
return connectionString.toString();
158176
}
159177

160178
function generateDbUrlForPrismaProd(connectionString) {
179+
if (!PRISMA_MIGRATION_DB_PROTOCOLS.includes(connectionString.protocol))
180+
return null;
161181
if (connectionString.protocol.startsWith('sqlite'))
162182
return `file:/code/db/${connectionString.host}`;
163-
if (connectionString.protocol.startsWith('mongodb'))
164-
return null;
165183
return connectionString.toString();
166184
}
167185

@@ -400,7 +418,7 @@ async function writeTemplateFiles(dirname, cwd, useNpm, includePrismaMigrations,
400418
data: {
401419
appName,
402420
adminforthVersion: adminforthVersion,
403-
includePrismaMigrations,
421+
includePrismaMigrations: Boolean(resolvedPrismaDbUrl),
404422
},
405423
},
406424
{
@@ -425,7 +443,7 @@ async function writeTemplateFiles(dirname, cwd, useNpm, includePrismaMigrations,
425443
)
426444
}
427445

428-
if (includePrismaMigrations) {
446+
if (resolvedPrismaDbUrl) {
429447
templateTasks.push(
430448
{
431449
src: 'schema.prisma.hbs',
@@ -506,7 +524,7 @@ function generateFinalInstructionsPnpm(skipPrismaSetup, options) {
506524
${chalk.dim('// Go to the project directory')}
507525
${chalk.dim('$')}${chalk.cyan(` cd ${options.appName}`)}\n`;
508526

509-
if (options.includePrismaMigrations)
527+
if (options.includePrismaMigrations && !skipPrismaSetup)
510528
instruction += `
511529
${chalk.dim('// Generate and apply initial migration')}
512530
${chalk.dim('$')}${chalk.cyan(' pnpm makemigration --name init && pnpm migrate:local')}\n`;
@@ -528,7 +546,7 @@ function generateFinalInstructionsNpm(skipPrismaSetup, options) {
528546
${chalk.dim('// Go to the project directory')}
529547
${chalk.dim('$')}${chalk.cyan(` cd ${options.appName}`)}\n`;
530548

531-
if (options.includePrismaMigrations)
549+
if (options.includePrismaMigrations && !skipPrismaSetup)
532550
instruction += `
533551
${chalk.dim('// Generate and apply initial migration')}
534552
${chalk.dim('$')}${chalk.cyan(' npm run makemigration -- --name init && npm run migrate:local')}\n`;

0 commit comments

Comments
 (0)