@rebasepro/cli 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -28
- package/dist/commands/init.d.ts +3 -0
- package/dist/index.cjs +56 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.es.js +56 -0
- package/dist/index.es.js.map +1 -1
- package/package.json +5 -5
- package/templates/template/.env.example +1 -0
- package/templates/template/backend/drizzle.config.ts +8 -5
- package/templates/template/backend/src/env.ts +13 -2
- package/templates/template/backend/src/index.ts +39 -3
- package/templates/template/config/collections/index.ts +1 -2
- package/templates/template/config/collections/posts.ts +1 -1
- package/templates/template/config/collections/presets/blank/index.ts +3 -0
- package/templates/template/config/collections/presets/ecommerce/categories.ts +38 -0
- package/templates/template/config/collections/presets/ecommerce/index.ts +6 -0
- package/templates/template/config/collections/presets/ecommerce/orders.ts +54 -0
- package/templates/template/config/collections/presets/ecommerce/products.ts +56 -0
- package/templates/template/config/collections/users.ts +20 -15
- package/templates/template/docker-compose.yml +1 -1
- package/templates/template/package.json +6 -0
- package/templates/template/config/collections/roles.ts +0 -62
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rebasepro/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Developer tools for Rebase projects",
|
|
5
5
|
"main": "./dist/index.cjs",
|
|
6
6
|
"module": "./dist/index.es.js",
|
|
@@ -31,10 +31,10 @@
|
|
|
31
31
|
"execa": "^9.6.1",
|
|
32
32
|
"inquirer": "12.11.1",
|
|
33
33
|
"jiti": "^2.7.0",
|
|
34
|
-
"@rebasepro/
|
|
35
|
-
"@rebasepro/
|
|
36
|
-
"@rebasepro/server-postgresql": "0.
|
|
37
|
-
"@rebasepro/
|
|
34
|
+
"@rebasepro/sdk-generator": "0.5.0",
|
|
35
|
+
"@rebasepro/types": "0.5.0",
|
|
36
|
+
"@rebasepro/server-postgresql": "0.5.0",
|
|
37
|
+
"@rebasepro/server-core": "0.5.0"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
40
|
"@types/node": "^20.19.41",
|
|
@@ -16,15 +16,18 @@ if (!process.env.DATABASE_URL) {
|
|
|
16
16
|
throw new Error("DATABASE_URL is not set. Make sure .env file exists in the project root and contains DATABASE_URL");
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
// Extract table names from the generated schema.
|
|
20
|
-
// This ensures drizzle-kit ONLY manages tables defined in the schema
|
|
21
|
-
//
|
|
22
|
-
const tableNames = Object.values(tables)
|
|
19
|
+
// Extract table names from the generated schema, excluding system tables in the 'rebase' schema.
|
|
20
|
+
// This ensures drizzle-kit ONLY manages tables defined in the schema, and ignores the system tables
|
|
21
|
+
// managed by Rebase's own bootstrapper.
|
|
22
|
+
const tableNames = Object.values(tables)
|
|
23
|
+
.filter(table => getTableConfig(table as PgTable).schema !== "rebase")
|
|
24
|
+
.map(table => getTableName(table as PgTable));
|
|
23
25
|
|
|
24
|
-
// Dynamically extract all schemas defined in the generated tables to ensure Drizzle Kit manages them.
|
|
26
|
+
// Dynamically extract all schemas defined in the generated tables (excluding 'rebase') to ensure Drizzle Kit manages them.
|
|
25
27
|
const schemas = Array.from(new Set(
|
|
26
28
|
Object.values(tables)
|
|
27
29
|
.map(table => getTableConfig(table as PgTable).schema || "public")
|
|
30
|
+
.filter(schema => schema !== "rebase")
|
|
28
31
|
));
|
|
29
32
|
|
|
30
33
|
export default defineConfig({
|
|
@@ -1,11 +1,22 @@
|
|
|
1
1
|
import * as dotenv from "dotenv";
|
|
2
2
|
import path from "path";
|
|
3
3
|
import { fileURLToPath } from "url";
|
|
4
|
-
import { loadEnv } from "@rebasepro/server-core";
|
|
4
|
+
import { loadEnv, z } from "@rebasepro/server-core";
|
|
5
5
|
|
|
6
6
|
const __filename = fileURLToPath(import.meta.url);
|
|
7
7
|
const __dirname = path.dirname(__filename);
|
|
8
8
|
|
|
9
9
|
dotenv.config({ path: path.resolve(__dirname, "../../.env") });
|
|
10
10
|
|
|
11
|
-
export const env = loadEnv(
|
|
11
|
+
export const env = loadEnv({
|
|
12
|
+
extend: z.object({
|
|
13
|
+
SMTP_HOST: z.string().optional(),
|
|
14
|
+
SMTP_PORT: z.string().default("587").transform(Number),
|
|
15
|
+
SMTP_SECURE: z.enum(["true", "false", ""]).default("false").transform(v => v === "true"),
|
|
16
|
+
SMTP_USER: z.string().optional(),
|
|
17
|
+
SMTP_PASS: z.string().optional(),
|
|
18
|
+
SMTP_FROM: z.string().optional(),
|
|
19
|
+
SMTP_NAME: z.string().optional(),
|
|
20
|
+
APP_NAME: z.string().default("Rebase"),
|
|
21
|
+
})
|
|
22
|
+
});
|
|
@@ -13,9 +13,11 @@ import {
|
|
|
13
13
|
cleanupDevPortFile,
|
|
14
14
|
logger
|
|
15
15
|
} from "@rebasepro/server-core";
|
|
16
|
+
import type { SecurityRule } from "@rebasepro/types";
|
|
16
17
|
import { createPostgresDatabaseConnection, createPostgresAdapter } from "@rebasepro/server-postgresql";
|
|
17
18
|
import { enums, relations, tables } from "./schema.generated.js";
|
|
18
19
|
import { env } from "./env.js";
|
|
20
|
+
import usersCollection from "../../config/collections/users.js";
|
|
19
21
|
|
|
20
22
|
const __filename = fileURLToPath(import.meta.url);
|
|
21
23
|
const __dirname = path.dirname(__filename);
|
|
@@ -25,7 +27,16 @@ const app: Hono<HonoEnv> = new Hono<HonoEnv>();
|
|
|
25
27
|
|
|
26
28
|
const isProduction = env.NODE_ENV === "production";
|
|
27
29
|
const allowedOrigins = isProduction
|
|
28
|
-
? (
|
|
30
|
+
? (() => {
|
|
31
|
+
const origins = env.CORS_ORIGINS || env.FRONTEND_URL;
|
|
32
|
+
if (!origins) {
|
|
33
|
+
throw new Error(
|
|
34
|
+
"CORS_ORIGINS or FRONTEND_URL must be set in production. " +
|
|
35
|
+
"Example: CORS_ORIGINS=https://yourdomain.com"
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
return origins.split(",").map(s => s.trim());
|
|
39
|
+
})()
|
|
29
40
|
: [];
|
|
30
41
|
|
|
31
42
|
app.use("/*", cors({
|
|
@@ -49,6 +60,13 @@ async function startServer() {
|
|
|
49
60
|
const PORT = env.PORT;
|
|
50
61
|
const server = createServer(getRequestListener(app.fetch));
|
|
51
62
|
|
|
63
|
+
// Default security rules for collections that don't define their own.
|
|
64
|
+
// Authenticated users can read all rows; only admins can write.
|
|
65
|
+
const defaultSecurityRules: SecurityRule[] = [
|
|
66
|
+
{ operation: "select", access: "public" },
|
|
67
|
+
{ operations: ["insert", "update", "delete"], roles: ["admin"] }
|
|
68
|
+
];
|
|
69
|
+
|
|
52
70
|
const backend = await initializeRebaseBackend({
|
|
53
71
|
collectionsDir: path.resolve(__dirname, "../../config/collections"),
|
|
54
72
|
functionsDir: path.resolve(__dirname, "../functions"),
|
|
@@ -63,6 +81,7 @@ relations },
|
|
|
63
81
|
connectionString
|
|
64
82
|
}),
|
|
65
83
|
auth: {
|
|
84
|
+
collection: usersCollection,
|
|
66
85
|
jwtSecret,
|
|
67
86
|
accessExpiresIn: env.JWT_ACCESS_EXPIRES_IN,
|
|
68
87
|
refreshExpiresIn: env.JWT_REFRESH_EXPIRES_IN,
|
|
@@ -71,7 +90,23 @@ relations },
|
|
|
71
90
|
? { clientId: env.GOOGLE_CLIENT_ID }
|
|
72
91
|
: undefined,
|
|
73
92
|
seedDefaultRoles: true,
|
|
74
|
-
allowRegistration: env.ALLOW_REGISTRATION
|
|
93
|
+
allowRegistration: env.ALLOW_REGISTRATION,
|
|
94
|
+
email: env.SMTP_HOST
|
|
95
|
+
? {
|
|
96
|
+
from: env.SMTP_FROM || `${env.APP_NAME} <noreply@rebase.pro>`,
|
|
97
|
+
smtp: {
|
|
98
|
+
host: env.SMTP_HOST,
|
|
99
|
+
port: env.SMTP_PORT,
|
|
100
|
+
secure: env.SMTP_SECURE,
|
|
101
|
+
auth: env.SMTP_USER
|
|
102
|
+
? { user: env.SMTP_USER, pass: env.SMTP_PASS! }
|
|
103
|
+
: undefined,
|
|
104
|
+
name: env.SMTP_NAME,
|
|
105
|
+
},
|
|
106
|
+
appName: env.APP_NAME,
|
|
107
|
+
resetPasswordUrl: env.FRONTEND_URL,
|
|
108
|
+
}
|
|
109
|
+
: undefined,
|
|
75
110
|
},
|
|
76
111
|
storage: env.STORAGE_TYPE === "s3"
|
|
77
112
|
? {
|
|
@@ -87,7 +122,8 @@ relations },
|
|
|
87
122
|
type: "local",
|
|
88
123
|
basePath: env.STORAGE_PATH || path.resolve(__dirname, "../../uploads")
|
|
89
124
|
},
|
|
90
|
-
history: true
|
|
125
|
+
history: true,
|
|
126
|
+
defaultSecurityRules
|
|
91
127
|
});
|
|
92
128
|
|
|
93
129
|
// ─── Health check ─────────────────────────────────────────────
|
|
@@ -2,6 +2,5 @@ import postsCollection from "./posts.js";
|
|
|
2
2
|
import authorsCollection from "./authors.js";
|
|
3
3
|
import tagsCollection from "./tags.js";
|
|
4
4
|
import usersCollection from "./users.js";
|
|
5
|
-
import rolesCollection from "./roles.js";
|
|
6
5
|
|
|
7
|
-
export const collections = [postsCollection, authorsCollection, tagsCollection, usersCollection
|
|
6
|
+
export const collections = [postsCollection, authorsCollection, tagsCollection, usersCollection];
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { EntityCollection } from "@rebasepro/types";
|
|
2
|
+
|
|
3
|
+
const categoriesCollection: EntityCollection = {
|
|
4
|
+
name: "Categories",
|
|
5
|
+
singularName: "Category",
|
|
6
|
+
slug: "categories",
|
|
7
|
+
table: "categories",
|
|
8
|
+
icon: "Category",
|
|
9
|
+
properties: {
|
|
10
|
+
id: {
|
|
11
|
+
name: "ID",
|
|
12
|
+
type: "number",
|
|
13
|
+
isId: "increment"
|
|
14
|
+
},
|
|
15
|
+
name: {
|
|
16
|
+
name: "Name",
|
|
17
|
+
type: "string",
|
|
18
|
+
validation: { required: true }
|
|
19
|
+
},
|
|
20
|
+
slug: {
|
|
21
|
+
name: "Slug",
|
|
22
|
+
type: "string",
|
|
23
|
+
validation: { required: true }
|
|
24
|
+
},
|
|
25
|
+
description: {
|
|
26
|
+
name: "Description",
|
|
27
|
+
type: "string",
|
|
28
|
+
multiline: true
|
|
29
|
+
},
|
|
30
|
+
icon: {
|
|
31
|
+
name: "Icon",
|
|
32
|
+
type: "string"
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
propertiesOrder: ["id", "name", "slug", "description", "icon"]
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export default categoriesCollection;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import productsCollection from "./products.js";
|
|
2
|
+
import categoriesCollection from "./categories.js";
|
|
3
|
+
import ordersCollection from "./orders.js";
|
|
4
|
+
import usersCollection from "../../users.js";
|
|
5
|
+
|
|
6
|
+
export const collections = [productsCollection, categoriesCollection, ordersCollection, usersCollection];
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { EntityCollection } from "@rebasepro/types";
|
|
2
|
+
|
|
3
|
+
const ordersCollection: EntityCollection = {
|
|
4
|
+
name: "Orders",
|
|
5
|
+
singularName: "Order",
|
|
6
|
+
slug: "orders",
|
|
7
|
+
table: "orders",
|
|
8
|
+
icon: "Receipt",
|
|
9
|
+
properties: {
|
|
10
|
+
id: {
|
|
11
|
+
name: "ID",
|
|
12
|
+
type: "number",
|
|
13
|
+
isId: "increment"
|
|
14
|
+
},
|
|
15
|
+
customerEmail: {
|
|
16
|
+
name: "Customer Email",
|
|
17
|
+
type: "string",
|
|
18
|
+
columnName: "customer_email",
|
|
19
|
+
validation: { required: true }
|
|
20
|
+
},
|
|
21
|
+
status: {
|
|
22
|
+
name: "Status",
|
|
23
|
+
type: "string",
|
|
24
|
+
enum: [
|
|
25
|
+
{ id: "pending", label: "Pending", color: "gray" },
|
|
26
|
+
{ id: "processing", label: "Processing", color: "blue" },
|
|
27
|
+
{ id: "shipped", label: "Shipped", color: "orange" },
|
|
28
|
+
{ id: "delivered", label: "Delivered", color: "green" },
|
|
29
|
+
{ id: "cancelled", label: "Cancelled", color: "red" }
|
|
30
|
+
]
|
|
31
|
+
},
|
|
32
|
+
total: {
|
|
33
|
+
name: "Total",
|
|
34
|
+
type: "number",
|
|
35
|
+
validation: { required: true }
|
|
36
|
+
},
|
|
37
|
+
notes: {
|
|
38
|
+
name: "Notes",
|
|
39
|
+
type: "string",
|
|
40
|
+
multiline: true
|
|
41
|
+
},
|
|
42
|
+
createdAt: {
|
|
43
|
+
name: "Created At",
|
|
44
|
+
type: "date",
|
|
45
|
+
columnName: "created_at",
|
|
46
|
+
autoValue: "on_create",
|
|
47
|
+
ui: {
|
|
48
|
+
readOnly: true
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export default ordersCollection;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { EntityCollection } from "@rebasepro/types";
|
|
2
|
+
import categoriesCollection from "./categories.js";
|
|
3
|
+
|
|
4
|
+
const productsCollection: EntityCollection = {
|
|
5
|
+
name: "Products",
|
|
6
|
+
singularName: "Product",
|
|
7
|
+
slug: "products",
|
|
8
|
+
table: "products",
|
|
9
|
+
icon: "ShoppingCart",
|
|
10
|
+
properties: {
|
|
11
|
+
id: {
|
|
12
|
+
name: "ID",
|
|
13
|
+
type: "number",
|
|
14
|
+
isId: "increment"
|
|
15
|
+
},
|
|
16
|
+
name: {
|
|
17
|
+
name: "Name",
|
|
18
|
+
type: "string",
|
|
19
|
+
validation: { required: true }
|
|
20
|
+
},
|
|
21
|
+
description: {
|
|
22
|
+
name: "Description",
|
|
23
|
+
type: "string",
|
|
24
|
+
markdown: true
|
|
25
|
+
},
|
|
26
|
+
price: {
|
|
27
|
+
name: "Price",
|
|
28
|
+
type: "number",
|
|
29
|
+
validation: { required: true }
|
|
30
|
+
},
|
|
31
|
+
image: {
|
|
32
|
+
name: "Image",
|
|
33
|
+
type: "string",
|
|
34
|
+
storage: { storagePath: "product_images/" }
|
|
35
|
+
},
|
|
36
|
+
status: {
|
|
37
|
+
name: "Status",
|
|
38
|
+
type: "string",
|
|
39
|
+
enum: [
|
|
40
|
+
{ id: "draft", label: "Draft", color: "gray" },
|
|
41
|
+
{ id: "active", label: "Active", color: "green" },
|
|
42
|
+
{ id: "archived", label: "Archived", color: "orange" }
|
|
43
|
+
]
|
|
44
|
+
},
|
|
45
|
+
category: {
|
|
46
|
+
name: "Category",
|
|
47
|
+
type: "relation",
|
|
48
|
+
relationName: "category",
|
|
49
|
+
target: () => categoriesCollection,
|
|
50
|
+
cardinality: "one",
|
|
51
|
+
direction: "owning"
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export default productsCollection;
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { EntityCollection } from "@rebasepro/types";
|
|
2
|
-
import { resetPasswordAction, deleteEntityAction
|
|
3
|
-
import rolesCollection from "./roles.js";
|
|
1
|
+
import type { EntityCollection } from "@rebasepro/types";
|
|
2
|
+
import { resetPasswordAction, deleteEntityAction } from "@rebasepro/admin";
|
|
4
3
|
|
|
5
4
|
const usersCollection: EntityCollection = {
|
|
6
5
|
name: "Users",
|
|
@@ -12,7 +11,10 @@ const usersCollection: EntityCollection = {
|
|
|
12
11
|
group: "Settings",
|
|
13
12
|
openEntityMode: "dialog",
|
|
14
13
|
disableDefaultActions: ["copy"],
|
|
15
|
-
|
|
14
|
+
securityRules: [
|
|
15
|
+
{ operation: "select", roles: ["admin"] },
|
|
16
|
+
{ operations: ["insert", "update", "delete"], roles: ["admin"] }
|
|
17
|
+
],
|
|
16
18
|
entityActions: [
|
|
17
19
|
resetPasswordAction,
|
|
18
20
|
{
|
|
@@ -41,6 +43,7 @@ const usersCollection: EntityCollection = {
|
|
|
41
43
|
displayName: {
|
|
42
44
|
name: "Name",
|
|
43
45
|
type: "string",
|
|
46
|
+
columnName: "display_name",
|
|
44
47
|
validation: {
|
|
45
48
|
required: true
|
|
46
49
|
}
|
|
@@ -48,21 +51,21 @@ const usersCollection: EntityCollection = {
|
|
|
48
51
|
photoURL: {
|
|
49
52
|
name: "Photo URL",
|
|
50
53
|
type: "string",
|
|
54
|
+
columnName: "photo_url",
|
|
51
55
|
url: "image"
|
|
52
56
|
},
|
|
53
57
|
roles: {
|
|
54
58
|
name: "Roles",
|
|
55
|
-
type: "
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
Field: UserRolesSelectField
|
|
59
|
+
type: "array",
|
|
60
|
+
columnType: "text[]",
|
|
61
|
+
of: {
|
|
62
|
+
name: "Role",
|
|
63
|
+
type: "string",
|
|
64
|
+
enum: {
|
|
65
|
+
admin: "Admin",
|
|
66
|
+
editor: "Editor",
|
|
67
|
+
viewer: "Viewer"
|
|
68
|
+
}
|
|
66
69
|
}
|
|
67
70
|
},
|
|
68
71
|
passwordHash: {
|
|
@@ -114,6 +117,8 @@ const usersCollection: EntityCollection = {
|
|
|
114
117
|
createdAt: {
|
|
115
118
|
name: "Created At",
|
|
116
119
|
type: "date",
|
|
120
|
+
columnName: "created_at",
|
|
121
|
+
autoValue: "on_create",
|
|
117
122
|
ui: {
|
|
118
123
|
readOnly: true
|
|
119
124
|
}
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
import { PostgresCollection, EntityCollection } from "@rebasepro/types";
|
|
2
|
-
|
|
3
|
-
const rolesCollection: PostgresCollection = {
|
|
4
|
-
name: "Roles",
|
|
5
|
-
singularName: "Role",
|
|
6
|
-
slug: "roles",
|
|
7
|
-
table: "roles",
|
|
8
|
-
schema: "rebase",
|
|
9
|
-
icon: "Shield",
|
|
10
|
-
group: "Settings",
|
|
11
|
-
properties: {
|
|
12
|
-
id: {
|
|
13
|
-
name: "ID",
|
|
14
|
-
type: "string",
|
|
15
|
-
isId: "manual",
|
|
16
|
-
validation: {
|
|
17
|
-
required: true
|
|
18
|
-
}
|
|
19
|
-
},
|
|
20
|
-
name: {
|
|
21
|
-
name: "Name",
|
|
22
|
-
type: "string",
|
|
23
|
-
validation: {
|
|
24
|
-
required: true
|
|
25
|
-
}
|
|
26
|
-
},
|
|
27
|
-
isAdmin: {
|
|
28
|
-
name: "Is Admin",
|
|
29
|
-
type: "boolean",
|
|
30
|
-
columnName: "is_admin",
|
|
31
|
-
defaultValue: false
|
|
32
|
-
},
|
|
33
|
-
defaultPermissions: {
|
|
34
|
-
name: "Default Permissions",
|
|
35
|
-
type: "map",
|
|
36
|
-
columnName: "default_permissions",
|
|
37
|
-
properties: {
|
|
38
|
-
read: { name: "Read", type: "boolean" },
|
|
39
|
-
create: { name: "Create", type: "boolean" },
|
|
40
|
-
edit: { name: "Edit", type: "boolean" },
|
|
41
|
-
delete: { name: "Delete", type: "boolean" }
|
|
42
|
-
}
|
|
43
|
-
},
|
|
44
|
-
collectionPermissions: {
|
|
45
|
-
name: "Collection Permissions",
|
|
46
|
-
type: "map",
|
|
47
|
-
columnName: "collection_permissions"
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
rolesCollection.securityRules = [
|
|
53
|
-
{
|
|
54
|
-
name: "roles_public_access",
|
|
55
|
-
mode: "permissive",
|
|
56
|
-
operation: "all",
|
|
57
|
-
pgRoles: ["public"],
|
|
58
|
-
using: "true"
|
|
59
|
-
}
|
|
60
|
-
];
|
|
61
|
-
|
|
62
|
-
export default rolesCollection;
|