Skip to content

Commit 3e686ea

Browse files
committed
Merge branch 'main' of github.com:devforth/adminforth into next
2 parents 811af3e + 5c99974 commit 3e686ea

File tree

31 files changed

+1362
-219
lines changed

31 files changed

+1362
-219
lines changed

README.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,3 +92,29 @@ npm run namemigration -- --name desctiption_of_changes
9292
```
9393

9494

95+
### Testing CLI commands during development
96+
97+
98+
Make sure you have not `adminforth` globally installed. If you have it, remove it:
99+
100+
101+
```sh
102+
npm uninstall -g adminforth
103+
```
104+
105+
Then, in the root of the project, run once:
106+
107+
```
108+
cd adminforth/adminforth
109+
npm run build
110+
```
111+
112+
This will automatically make an npm link to the `adminforth` package in the root of the project.
113+
114+
Then, go to testing app, e.g. created with CLI, and use next command:
115+
116+
```
117+
npx -g adminforth <your command under development>
118+
```
119+
120+
This will always run latest version of adminforth package.

adminforth/commands/bundle.js

Lines changed: 15 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,22 @@
1-
import fs from "fs";
2-
import { getInstance } from "./utils.js";
1+
import { callTsProxy, findAdminInstance } from "./callTsProxy.js";
2+
33

44
async function bundle() {
5-
const currentDirectory = process.cwd();
6-
const files = fs.readdirSync(currentDirectory);
7-
let instanceFound = false;
5+
console.log("Bundling admin SPA...");
6+
const instance = await findAdminInstance();
7+
8+
9+
try {
10+
await callTsProxy(`
11+
import { admin } from './${instance.file}.js';
812
9-
for (const file of files) {
10-
if (file.endsWith(".js") || file.endsWith(".ts")) {
11-
try {
12-
const instance = await getInstance(file, currentDirectory);
13-
if (instance) {
14-
await instance.bundleNow({ hotReload: false });
15-
instanceFound = true;
16-
break;
17-
}
18-
} catch (error) {
19-
console.error(`Error: Could not bundle '${file}'`, error);
13+
export async function exec() {
14+
return await admin.bundleNow({ hotReload: false });
2015
}
21-
}
22-
}
23-
if (!instanceFound) {
24-
console.error("Error: No valid instance found to bundle.");
25-
return;
16+
`);
17+
18+
} catch (e) {
19+
console.log(`Running file ${file} failed`, e);
2620
}
2721
}
2822

adminforth/commands/callTsProxy.js

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
// callTsProxy.js
2+
import { spawn } from "child_process";
3+
import path from "path";
4+
import fs from "fs";
5+
import chalk from "chalk";
6+
7+
const currentFilePath = import.meta.url;
8+
const currentFileFolder = path.dirname(currentFilePath).replace("file:", "");
9+
10+
export function callTsProxy(tsCode) {
11+
12+
process.env.HEAVY_DEBUG && console.log("🌐 Calling tsproxy with code:", path.join(currentFileFolder, "proxy.ts"));
13+
return new Promise((resolve, reject) => {
14+
const child = spawn("tsx", [
15+
path.join(currentFileFolder, "proxy.ts")
16+
]);
17+
18+
let stdout = "";
19+
let stderr = "";
20+
21+
child.stdout.on("data", (data) => {
22+
stdout += data;
23+
});
24+
25+
child.stderr.on("data", (data) => {
26+
stderr += data;
27+
});
28+
29+
child.on("close", (code) => {
30+
if (code === 0) {
31+
try {
32+
const parsed = JSON.parse(stdout);
33+
parsed.capturedLogs.forEach((log) => {
34+
console.log(...log);
35+
});
36+
37+
if (parsed.error) {
38+
reject(new Error(`${parsed.error}\n${parsed.stack}`));
39+
}
40+
resolve(parsed.result);
41+
} catch (e) {
42+
reject(new Error("Invalid JSON from tsproxy: " + stdout));
43+
}
44+
} else {
45+
console.error(`tsproxy exited with non-0, this should never happen, stdout: ${stdout}, stderr: ${stderr}`);
46+
reject(new Error(stderr));
47+
}
48+
});
49+
50+
process.env.HEAVY_DEBUG && console.log("🪲 Writing to tsproxy stdin...\n'''", tsCode, "'''");
51+
child.stdin.write(tsCode);
52+
child.stdin.end();
53+
});
54+
}
55+
56+
export async function findAdminInstance() {
57+
process.env.HEAVY_DEBUG && console.log("🌐 Finding admin instance...");
58+
const currentDirectory = process.cwd();
59+
60+
let files = fs.readdirSync(currentDirectory);
61+
let instanceFound = {
62+
file: null,
63+
version: null,
64+
};
65+
// try index.ts first
66+
if (files.includes("index.ts")) {
67+
files = files.filter((file) => file !== "index.ts");
68+
files.unshift("index.ts");
69+
}
70+
71+
for (const file of files) {
72+
if (file.endsWith(".ts")) {
73+
const fileNoTs = file.replace(/\.ts$/, "");
74+
process.env.HEAVY_DEBUG && console.log(`🪲 Trying bundleing ${file}...`);
75+
try {
76+
res = await callTsProxy(`
77+
import { admin } from './${fileNoTs}.js';
78+
79+
export async function exec() {
80+
return admin.formatAdminForth();
81+
}
82+
`);
83+
instanceFound.file = fileNoTs;
84+
instanceFound.version = res;
85+
break;
86+
87+
} catch (e) {
88+
// do our best to guess that this file has a good chance to be admin instance
89+
// and show the error so user can fix it
90+
const fileContent = fs.readFileSync(file, "utf-8");
91+
if (fileContent.includes("export const admin")) {
92+
console.error(chalk.red(`Error running ${file}:`, e));
93+
process.exit(1);
94+
}
95+
process.env.HEAVY_DEBUG && console.log(`🪲 File ${file} failed`, e);
96+
}
97+
}
98+
}
99+
if (!instanceFound.file) {
100+
console.error(
101+
chalk.red(
102+
`Error: No valid instance found to bundle.\n` +
103+
`Make sure you have a file in the current directory with a .ts extension, and it exports an ` +
104+
chalk.cyan.bold('admin') +
105+
` instance like:\n\n` +
106+
chalk.yellow('export const admin = new AdminForth({...})') +
107+
`\n\nFor example, adminforth CLI creates an index.ts file which exports the admin instance.`
108+
)
109+
);
110+
process.exit(1);
111+
}
112+
return instanceFound;
113+
}
114+
115+
// Example usage:
116+
// callTsProxy(`
117+
// import admin from './admin';
118+
// function exec() {
119+
// return admin.doX();
120+
// }
121+
// `).then(console.log).catch(console.error);

adminforth/commands/cli.js

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,41 @@ import bundle from "./bundle.js";
77
import createApp from "./createApp/main.js";
88
import generateModels from "./generateModels.js";
99
import createPlugin from "./createPlugin/main.js";
10+
import createComponent from "./createCustomComponent/main.js";
11+
import chalk from "chalk";
12+
import path from "path";
13+
import fs from "fs";
14+
15+
function showHelp() {
16+
console.log(
17+
chalk.white("Available commands:\n") +
18+
chalk.green(' create-app') + chalk.white(' Create a new AdminForth app\n') +
19+
chalk.green(' create-plugin') + chalk.white(' Create a plugin for your AdminForth app\n') +
20+
chalk.green(' generate-models') + chalk.white(' Generate TypeScript models from your databases\n') +
21+
chalk.green(' bundle') + chalk.white(' Bundles your AdminForth app SPA for production\n') +
22+
chalk.green(' component') + chalk.white(' Scaffold a custom Vue component\n')
23+
);
24+
}
25+
26+
function currentFileDir(importMetaUrl) {
27+
const filePath = importMetaUrl.replace("file://", "");
28+
const fileDir = path.dirname(filePath);
29+
return fileDir;
30+
}
31+
32+
function showVersion() {
33+
const ADMIN_FORTH_ABSOLUTE_PATH = path.join(currentFileDir(import.meta.url), '..');
34+
35+
const package_json = JSON.parse(fs.readFileSync(path.join(ADMIN_FORTH_ABSOLUTE_PATH, 'package.json'), 'utf8'));
36+
37+
const ADMINFORTH_VERSION = package_json.version;
38+
39+
console.log(
40+
chalk.white('AdminForth CLI version: ') +
41+
chalk.cyan.bold(ADMINFORTH_VERSION)
42+
);
43+
}
44+
1045
switch (command) {
1146
case "create-app":
1247
createApp(args);
@@ -20,8 +55,23 @@ switch (command) {
2055
case "bundle":
2156
bundle();
2257
break;
23-
default:
58+
case "component":
59+
createComponent(args);
60+
break;
61+
case "help":
62+
case "--help":
63+
case "-h":
64+
showHelp();
65+
break;
66+
case "--version":
67+
case "version":
68+
case "-v":
69+
showVersion();
70+
break;
71+
default: {
2472
console.log(
25-
"Unknown command. Available commands: create-app, create-plugin, generate-models, bundle"
73+
"Unknown command."
2674
);
75+
showHelp();
76+
}
2777
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
node_modules
2+
{{#if sqliteFile}}
3+
{{ sqliteFile }}
4+
{{/if}}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
NODE_ENV=production
2+
DATABASE_URL=sqlite://.db.sqlite
3+
DATABASE_URL={{dbUrl}}
4+
{{#if prismaDbUrl}}
5+
PRISMA_DATABASE_URL={{prismaDbUrl}}
6+
{{/if}}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
FROM node:{{nodeMajor}}-slim
2+
WORKDIR /code/
3+
ADD package.json package-lock.json /code/
4+
RUN npm ci
5+
ADD . /code/
6+
RUN --mount=type=cache,target=/tmp npx adminforth bundle
7+
CMD ["sh", "-c", "npm run migrate:prod && npm run prod"]

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,9 +60,9 @@ if (import.meta.url === `file://${process.argv[1]}`) {
6060
app.use(express.json());
6161

6262
const port = 3500;
63-
63+
6464
await admin.bundleNow({ hotReload: process.env.NODE_ENV === 'development' });
65-
console.log('Bundling AdminForth done. For faster serving consider calling bundleNow() from a build script.');
65+
console.log('Bundling AdminForth SPA done.');
6666

6767
admin.express.serve(app)
6868

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

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,14 @@
88
"license": "ISC",
99
"description": "",
1010
"scripts": {
11-
"env": "dotenvx run -f .env.local -f .env --overload --",
12-
"start": "npm run env -- tsx watch index.ts",
13-
"migrateLocal": "npm run env -- npx prisma migrate deploy",
14-
"makemigration": "npm run migrateLocal; npm run env -- npx --yes prisma migrate dev",
15-
"test": "echo \"Error: no test specified\" && exit 1"
11+
"dev": "npm run _env:dev -- tsx watch index.ts",
12+
"prod": "npm run _env:prod -- tsx index.ts",
13+
"start": "npm run dev",
14+
"makemigration": "npm run _env:dev -- npx --yes prisma migrate dev --create-only",
15+
"migrate:local": "npm run _env:dev -- npx --yes prisma migrate deploy",
16+
"migrate:prod": "npm run _env:prod -- npx --yes prisma migrate deploy",
17+
"_env:dev": "dotenvx run -f .env -f .env.local --",
18+
"_env:prod": "dotenvx run -f .env.prod --"
1619
},
1720
"engines": {
1821
"node": ">=20"

adminforth/commands/createApp/templates/readme.md.hbs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,13 @@ npm ci
99
Migrate the database:
1010

1111
```bash
12-
npm run migrateLocal
12+
npm run migrate:local
1313
```
1414

1515
Start the server:
1616

1717
```bash
18-
npm start
18+
npm run dev
1919
```
2020

2121
{{#if prismaDbUrl}}
@@ -32,6 +32,18 @@ npm run makemigration -- --name <name_of_changes>
3232
Your colleagues will need to pull the changes and run `npm run migrateLocal` to apply the migration in their local database.
3333
{{/if}}
3434

35+
## Deployment tips
36+
37+
You have Dockerfile ready for production deployment. You can test the build with:
38+
39+
```bash
40+
docker build -t {{appName}}-image .
41+
docker run -p 3500:3500 {{appName}}-image
42+
```
43+
44+
To set non-sensitive environment variables in production, use `.env.prod` file.
45+
For sensitive variables, use direct docker environment variables or secrets from your vault.
46+
3547
## Documentation
3648

3749
- [Customizing AdminForth Branding](https://adminforth.dev/docs/tutorial/Customization/branding/)

0 commit comments

Comments
 (0)