spfn 0.2.0-beta.9 → 0.3.0-beta.2
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/LICENSE +1 -1
- package/README.md +626 -204
- package/bin/spfn.js +53 -4
- package/dist/index.js +4183 -2281
- package/dist/templates/.dockerignore +1 -0
- package/dist/templates/Dockerfile +1 -1
- package/dist/templates/README.md +94 -0
- package/dist/templates/lib/api-client.ts +1 -1
- package/dist/templates/modes/full/src/app/auth/callback/page.tsx +1 -0
- package/dist/templates/modes/full/src/app/login/page.tsx +68 -0
- package/dist/templates/modes/full/src/i18n/catalogs.ts +16 -0
- package/dist/templates/modes/full/src/i18n/server.ts +9 -0
- package/dist/templates/modes/full/src/server/config/env.config.ts +23 -0
- package/dist/templates/modes/full/src/server/mcp.ts +75 -0
- package/dist/templates/modes/full/src/server/router.ts +32 -0
- package/dist/templates/modes/full/src/server/routes/examples.ts +94 -0
- package/dist/templates/modes/full/src/server/routes/health.ts +9 -0
- package/dist/templates/modes/full/src/server/routes/root.ts +15 -0
- package/dist/templates/modes/full/src/server/server.config.ts +14 -0
- package/dist/templates/server/config/env.config.ts +10 -75
- package/dist/templates/server/entities/config.ts +1 -1
- package/dist/templates/server/entities/example.entity.ts +1 -1
- package/dist/templates/server/repositories/example.repository.ts +1 -1
- package/dist/templates/server/router.ts +2 -3
- package/dist/templates/server/routes/examples.ts +2 -12
- package/dist/templates/server/routes/health.ts +1 -1
- package/dist/templates/server/routes/root.ts +1 -2
- package/dist/templates/server/server.config.ts +2 -2
- package/dist/templates/vercel/npmrc +4 -0
- package/dist/templates/vercel/route.ts +53 -0
- package/dist/templates/vercel/vercel.json +6 -0
- package/package.json +35 -23
- package/dist/commands/generate/templates/contract.template +0 -58
- package/dist/commands/generate/templates/entity.template +0 -28
- package/dist/commands/generate/templates/init-migration.template +0 -3
- package/dist/commands/generate/templates/repository.template +0 -56
- package/dist/commands/generate/templates/route.template +0 -65
- package/dist/commands/generate/templates/schema.template +0 -13
- package/dist/templates/Dockerfile.optimized +0 -67
- package/dist/templates/config/index.ts +0 -22
- package/dist/templates/config/schema.ts +0 -42
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
* Server Configuration
|
|
3
3
|
*/
|
|
4
4
|
import { defineServerConfig } from '@spfn/core/server';
|
|
5
|
-
import { appRouter } from
|
|
5
|
+
import { appRouter } from '@/server/router';
|
|
6
6
|
|
|
7
7
|
export default defineServerConfig()
|
|
8
8
|
.port(8790)
|
|
9
9
|
.host('0.0.0.0')
|
|
10
10
|
.routes(appRouter)
|
|
11
|
-
.build();
|
|
11
|
+
.build();
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
@spfn:registry=https://git.superfunction.xyz/api/packages/superfunction/npm/
|
|
2
|
+
@superself:registry=https://git.superfunction.xyz/api/packages/superfunction/npm/
|
|
3
|
+
//git.superfunction.xyz/api/packages/superfunction/npm/:_authToken=${GITEA_NPM_TOKEN}
|
|
4
|
+
//git.superfunction.xyz/api/packages/superfunction/npm/:always-auth=true
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vercel serverless adapter for the SPFN Hono API.
|
|
3
|
+
*
|
|
4
|
+
* The SPFN-specific bootstrap lives in the core: `createServerlessApp()`
|
|
5
|
+
* (from `@spfn/core/server`) returns a listen-free, memoized Hono app with the DB
|
|
6
|
+
* initialized in-handler and the periodic health-check disabled. `hono/vercel`'s
|
|
7
|
+
* `handle()` turns it into Vercel Functions. `startServer()` / `spfn start` (the
|
|
8
|
+
* always-on path) is unaffected.
|
|
9
|
+
*
|
|
10
|
+
* The SPFN app is mounted under `/api/backend` so it coexists with Next.js routes on
|
|
11
|
+
* a single Vercel origin. Point the RPC proxy at the same origin in production:
|
|
12
|
+
* SPFN_API_URL = https://<deployment>/api/backend
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { Hono } from 'hono';
|
|
16
|
+
import { handle } from 'hono/vercel';
|
|
17
|
+
import { createServerlessApp } from '@spfn/core/server';
|
|
18
|
+
import serverConfig from '@/server/server.config';
|
|
19
|
+
|
|
20
|
+
// SPFN needs Node APIs (pg, native bcrypt) — the edge runtime cannot host it.
|
|
21
|
+
export const runtime = 'nodejs';
|
|
22
|
+
export const dynamic = 'force-dynamic';
|
|
23
|
+
|
|
24
|
+
// The Vercel Supabase integration injects POSTGRES_URL (Supavisor transaction pooler)
|
|
25
|
+
// but not DATABASE_URL, which is what SPFN reads. Map it so the app connects.
|
|
26
|
+
process.env.DATABASE_URL ??= process.env.POSTGRES_URL;
|
|
27
|
+
|
|
28
|
+
let handlerPromise: Promise<(req: Request) => Response | Promise<Response>> | undefined;
|
|
29
|
+
|
|
30
|
+
function getHandler()
|
|
31
|
+
{
|
|
32
|
+
if (!handlerPromise)
|
|
33
|
+
{
|
|
34
|
+
handlerPromise = createServerlessApp(serverConfig).then((spfn) =>
|
|
35
|
+
handle(new Hono().route('/api/backend', spfn)),
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return handlerPromise;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async function dispatch(req: Request): Promise<Response>
|
|
43
|
+
{
|
|
44
|
+
return (await getHandler())(req);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export const GET = dispatch;
|
|
48
|
+
export const POST = dispatch;
|
|
49
|
+
export const PUT = dispatch;
|
|
50
|
+
export const PATCH = dispatch;
|
|
51
|
+
export const DELETE = dispatch;
|
|
52
|
+
export const OPTIONS = dispatch;
|
|
53
|
+
export const HEAD = dispatch;
|
package/package.json
CHANGED
|
@@ -1,16 +1,26 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "spfn",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.3.0-beta.2",
|
|
4
|
+
"description": "Scaffold a full-stack TypeScript backend onto a Next.js app built with an AI coding agent: auth, database, typed routes and codegen, one fixed vertical slice per feature",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
|
-
"spfn": "
|
|
7
|
+
"spfn": "bin/spfn.js"
|
|
8
8
|
},
|
|
9
9
|
"types": "./dist/index.d.ts",
|
|
10
10
|
"files": [
|
|
11
11
|
"dist",
|
|
12
12
|
"bin"
|
|
13
13
|
],
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build": "tsup && node scripts/copy-templates.js",
|
|
16
|
+
"test": "vitest run",
|
|
17
|
+
"prepack": "node -e \"require('fs').copyFileSync('../../LICENSE','LICENSE')\"",
|
|
18
|
+
"dev": "tsup --watch",
|
|
19
|
+
"type-check": "tsc --noEmit",
|
|
20
|
+
"publish:alpha": "node ../../scripts/publish-package.mjs alpha",
|
|
21
|
+
"publish:beta": "node ../../scripts/publish-package.mjs beta",
|
|
22
|
+
"publish:latest": "node ../../scripts/publish-package.mjs latest"
|
|
23
|
+
},
|
|
14
24
|
"keywords": [
|
|
15
25
|
"spfn",
|
|
16
26
|
"cli",
|
|
@@ -22,31 +32,32 @@
|
|
|
22
32
|
"scaffolding",
|
|
23
33
|
"boilerplate",
|
|
24
34
|
"dev-tools",
|
|
25
|
-
"development",
|
|
26
|
-
"init",
|
|
27
35
|
"typescript",
|
|
28
|
-
"type-safe"
|
|
36
|
+
"type-safe",
|
|
37
|
+
"full-stack-framework",
|
|
38
|
+
"ai-coding-agent",
|
|
39
|
+
"claude-code"
|
|
29
40
|
],
|
|
30
|
-
"author": "Ray Im <rayim@
|
|
41
|
+
"author": "Ray Im <rayim@fxy.global>",
|
|
31
42
|
"license": "MIT",
|
|
32
43
|
"repository": {
|
|
33
44
|
"type": "git",
|
|
34
|
-
"url": "https://github.com/
|
|
45
|
+
"url": "git+https://github.com/fxylabs/spfn.git",
|
|
35
46
|
"directory": "packages/cli"
|
|
36
47
|
},
|
|
37
48
|
"publishConfig": {
|
|
38
49
|
"access": "public",
|
|
39
|
-
"tag": "
|
|
40
|
-
"registry": "https://registry.npmjs.org"
|
|
50
|
+
"tag": "beta"
|
|
41
51
|
},
|
|
42
52
|
"bugs": {
|
|
43
|
-
"url": "https://github.com/
|
|
53
|
+
"url": "https://github.com/fxylabs/spfn/issues"
|
|
44
54
|
},
|
|
45
|
-
"homepage": "https://github.com/
|
|
55
|
+
"homepage": "https://github.com/fxylabs/spfn/tree/main/packages/cli#readme",
|
|
46
56
|
"engines": {
|
|
47
|
-
"node": ">=
|
|
57
|
+
"node": ">=20.0.0"
|
|
48
58
|
},
|
|
49
59
|
"peerDependencies": {
|
|
60
|
+
"@spfn/core": ">=0.3.0-beta.1 <0.4.0",
|
|
50
61
|
"typescript": "^5.3.0"
|
|
51
62
|
},
|
|
52
63
|
"peerDependenciesMeta": {
|
|
@@ -59,7 +70,8 @@
|
|
|
59
70
|
"chokidar": "^4.0.3",
|
|
60
71
|
"commander": "^11.1.0",
|
|
61
72
|
"dotenv": "^17.2.3",
|
|
62
|
-
"drizzle-
|
|
73
|
+
"drizzle-kit": "1.0.0-rc.4",
|
|
74
|
+
"drizzle-orm": "1.0.0-rc.4",
|
|
63
75
|
"execa": "^8.0.1",
|
|
64
76
|
"fs-extra": "^11.2.0",
|
|
65
77
|
"ora": "^7.0.1",
|
|
@@ -67,21 +79,21 @@
|
|
|
67
79
|
"postgres": "^3.4.0",
|
|
68
80
|
"prompts": "^2.4.2",
|
|
69
81
|
"tsup": "^8.5.0",
|
|
70
|
-
"
|
|
82
|
+
"yaml": "^2.8.3"
|
|
83
|
+
},
|
|
84
|
+
"optionalDependencies": {
|
|
85
|
+
"@napi-rs/keyring": "^1.1.0"
|
|
71
86
|
},
|
|
72
87
|
"devDependencies": {
|
|
88
|
+
"@electric-sql/pglite": "^0.5.4",
|
|
89
|
+
"@spfn/core": "workspace:*",
|
|
73
90
|
"@types/fs-extra": "^11.0.4",
|
|
74
91
|
"@types/node": "^20.11.0",
|
|
75
92
|
"@types/pg": "^8.10.9",
|
|
76
93
|
"@types/prompts": "^2.4.9",
|
|
77
94
|
"concurrently": "^9.2.1",
|
|
78
|
-
"drizzle-kit": "^0.31.6",
|
|
79
95
|
"tsx": "^4.20.6",
|
|
80
|
-
"typescript": "^5.3.3"
|
|
81
|
-
|
|
82
|
-
"scripts": {
|
|
83
|
-
"build": "tsup && node scripts/copy-templates.js",
|
|
84
|
-
"dev": "tsup --watch",
|
|
85
|
-
"type-check": "tsc --noEmit"
|
|
96
|
+
"typescript": "^5.3.3",
|
|
97
|
+
"vitest": "^4.0.6"
|
|
86
98
|
}
|
|
87
|
-
}
|
|
99
|
+
}
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* {{PASCAL_NAME}} Contracts
|
|
3
|
-
*
|
|
4
|
-
* Auto-generated by SPFN CLI
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { Type } from '@sinclair/typebox';
|
|
8
|
-
import { createContract } from '@spfn/core/route';
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* GET /{{ENTITY_NAME}}
|
|
12
|
-
* List {{PASCAL_NAME}} items
|
|
13
|
-
*/
|
|
14
|
-
export const list{{PASCAL_NAME}}Contract = createContract({
|
|
15
|
-
method: 'GET',
|
|
16
|
-
path: '/{{ENTITY_NAME}}',
|
|
17
|
-
query: Type.Object({
|
|
18
|
-
limit: Type.Optional(Type.Number({ minimum: 1, maximum: 100 })),
|
|
19
|
-
offset: Type.Optional(Type.Number({ minimum: 0 })),
|
|
20
|
-
}),
|
|
21
|
-
responses: {
|
|
22
|
-
200: Type.Object({
|
|
23
|
-
items: Type.Array(
|
|
24
|
-
Type.Object({
|
|
25
|
-
id: Type.Number(),
|
|
26
|
-
name: Type.String(),
|
|
27
|
-
description: Type.Union([Type.String(), Type.Null()]),
|
|
28
|
-
createdAt: Type.String(),
|
|
29
|
-
updatedAt: Type.String(),
|
|
30
|
-
})
|
|
31
|
-
),
|
|
32
|
-
limit: Type.Number(),
|
|
33
|
-
offset: Type.Number(),
|
|
34
|
-
}),
|
|
35
|
-
},
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* POST /{{ENTITY_NAME}}
|
|
40
|
-
* Create new {{PASCAL_NAME}}
|
|
41
|
-
*/
|
|
42
|
-
export const create{{PASCAL_NAME}}Contract = createContract({
|
|
43
|
-
method: 'POST',
|
|
44
|
-
path: '/{{ENTITY_NAME}}',
|
|
45
|
-
body: Type.Object({
|
|
46
|
-
name: Type.String({ minLength: 1 }),
|
|
47
|
-
description: Type.Optional(Type.String()),
|
|
48
|
-
}),
|
|
49
|
-
responses: {
|
|
50
|
-
201: Type.Object({
|
|
51
|
-
id: Type.Number(),
|
|
52
|
-
name: Type.String(),
|
|
53
|
-
description: Type.Union([Type.String(), Type.Null()]),
|
|
54
|
-
createdAt: Type.String(),
|
|
55
|
-
updatedAt: Type.String(),
|
|
56
|
-
}),
|
|
57
|
-
},
|
|
58
|
-
});
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* {{PASCAL_NAME}} Entity
|
|
3
|
-
*
|
|
4
|
-
* Auto-generated by SPFN CLI
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { index, serial, text, timestamp } from 'drizzle-orm/pg-core';
|
|
8
|
-
import { {{SCHEMA_VAR_NAME}} } from './{{SCHEMA_FILE_NAME}}';
|
|
9
|
-
|
|
10
|
-
export const {{ENTITY_NAME}} = {{SCHEMA_VAR_NAME}}.table('{{TABLE_NAME}}', {
|
|
11
|
-
// Primary Key
|
|
12
|
-
id: serial('id').primaryKey(),
|
|
13
|
-
|
|
14
|
-
// Fields
|
|
15
|
-
name: text('name').notNull(),
|
|
16
|
-
description: text('description'),
|
|
17
|
-
|
|
18
|
-
// Timestamps
|
|
19
|
-
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
|
|
20
|
-
updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),
|
|
21
|
-
}, (table) => [
|
|
22
|
-
// Indexes
|
|
23
|
-
index('{{SCHEMA_NAME}}_{{TABLE_NAME}}_name_idx').on(table.name),
|
|
24
|
-
]);
|
|
25
|
-
|
|
26
|
-
// Type inference
|
|
27
|
-
export type {{PASCAL_NAME}} = typeof {{ENTITY_NAME}}.$inferSelect;
|
|
28
|
-
export type New{{PASCAL_NAME}} = typeof {{ENTITY_NAME}}.$inferInsert;
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* {{PASCAL_NAME}} Repository
|
|
3
|
-
*
|
|
4
|
-
* Auto-generated by SPFN CLI
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { findOne, findMany, create, update, deleteById } from '@spfn/core/db';
|
|
8
|
-
import { eq } from 'drizzle-orm';
|
|
9
|
-
import { {{ENTITY_NAME}} } from '@/entities';
|
|
10
|
-
import type { {{PASCAL_NAME}}, New{{PASCAL_NAME}} } from '@/entities';
|
|
11
|
-
|
|
12
|
-
export const {{REPO_NAME}} = {
|
|
13
|
-
/**
|
|
14
|
-
* Find {{PASCAL_NAME}} by ID
|
|
15
|
-
*/
|
|
16
|
-
findById: async (id: number): Promise<{{PASCAL_NAME}} | null> =>
|
|
17
|
-
{
|
|
18
|
-
return findOne({{ENTITY_NAME}}, eq({{ENTITY_NAME}}.id, id));
|
|
19
|
-
},
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Find all {{PASCAL_NAME}} items
|
|
23
|
-
*/
|
|
24
|
-
findMany: async (options?: { limit?: number; offset?: number }): Promise<{{PASCAL_NAME}}[]> =>
|
|
25
|
-
{
|
|
26
|
-
const { limit = 20, offset = 0 } = options || {};
|
|
27
|
-
return findMany({{ENTITY_NAME}}, {
|
|
28
|
-
limit,
|
|
29
|
-
offset,
|
|
30
|
-
});
|
|
31
|
-
},
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Create new {{PASCAL_NAME}}
|
|
35
|
-
*/
|
|
36
|
-
create: async (data: New{{PASCAL_NAME}}): Promise<{{PASCAL_NAME}}> =>
|
|
37
|
-
{
|
|
38
|
-
return create({{ENTITY_NAME}}, data);
|
|
39
|
-
},
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Update {{PASCAL_NAME}} by ID
|
|
43
|
-
*/
|
|
44
|
-
updateById: async (id: number, data: Partial<New{{PASCAL_NAME}}>): Promise<{{PASCAL_NAME}} | null> =>
|
|
45
|
-
{
|
|
46
|
-
return update({{ENTITY_NAME}}, eq({{ENTITY_NAME}}.id, id), data);
|
|
47
|
-
},
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Delete {{PASCAL_NAME}} by ID
|
|
51
|
-
*/
|
|
52
|
-
deleteById: async (id: number): Promise<void> =>
|
|
53
|
-
{
|
|
54
|
-
await deleteById({{ENTITY_NAME}}, id);
|
|
55
|
-
},
|
|
56
|
-
};
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* {{PASCAL_NAME}} Routes
|
|
3
|
-
*
|
|
4
|
-
* Auto-generated by SPFN CLI
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { createApp } from '@spfn/core/route';
|
|
8
|
-
import { Transactional } from '@spfn/core/db';
|
|
9
|
-
import { {{REPO_NAME}} } from '@/repositories/{{KEBAB_NAME}}.repository.js';
|
|
10
|
-
import {
|
|
11
|
-
list{{PASCAL_NAME}}Contract,
|
|
12
|
-
create{{PASCAL_NAME}}Contract,
|
|
13
|
-
} from '@/contracts/{{KEBAB_NAME}}.js';
|
|
14
|
-
|
|
15
|
-
const app = createApp();
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* GET /{{ENTITY_NAME}}
|
|
19
|
-
* List {{PASCAL_NAME}} items
|
|
20
|
-
*/
|
|
21
|
-
app.bind(list{{PASCAL_NAME}}Contract, async (c) =>
|
|
22
|
-
{
|
|
23
|
-
const { limit = 20, offset = 0 } = c.query;
|
|
24
|
-
|
|
25
|
-
const items = await {{REPO_NAME}}.findMany({ limit, offset });
|
|
26
|
-
|
|
27
|
-
return c.json({
|
|
28
|
-
items: items.map((item) => ({
|
|
29
|
-
id: item.id,
|
|
30
|
-
name: item.name,
|
|
31
|
-
description: item.description,
|
|
32
|
-
createdAt: item.createdAt.toISOString(),
|
|
33
|
-
updatedAt: item.updatedAt.toISOString(),
|
|
34
|
-
})),
|
|
35
|
-
limit,
|
|
36
|
-
offset,
|
|
37
|
-
});
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* POST /{{ENTITY_NAME}}
|
|
42
|
-
* Create new {{PASCAL_NAME}}
|
|
43
|
-
*/
|
|
44
|
-
app.bind(create{{PASCAL_NAME}}Contract, [Transactional()], async (c) =>
|
|
45
|
-
{
|
|
46
|
-
const body = await c.data();
|
|
47
|
-
|
|
48
|
-
const item = await {{REPO_NAME}}.create({
|
|
49
|
-
name: body.name,
|
|
50
|
-
description: body.description,
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
return c.json(
|
|
54
|
-
{
|
|
55
|
-
id: item.id,
|
|
56
|
-
name: item.name,
|
|
57
|
-
description: item.description,
|
|
58
|
-
createdAt: item.createdAt.toISOString(),
|
|
59
|
-
updatedAt: item.updatedAt.toISOString(),
|
|
60
|
-
},
|
|
61
|
-
201
|
|
62
|
-
);
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
export default app;
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* {{FN_NAME_PASCAL}} Schema
|
|
3
|
-
*
|
|
4
|
-
* Creates isolated PostgreSQL schema for {{PACKAGE_NAME}} tables.
|
|
5
|
-
* IMPORTANT: Export this schema so drizzle-kit can generate CREATE SCHEMA statement.
|
|
6
|
-
*
|
|
7
|
-
* Auto-generated by SPFN CLI
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import { createFunctionSchema } from '@spfn/core/db';
|
|
11
|
-
|
|
12
|
-
// ✅ Export schema to auto-generate CREATE SCHEMA in migrations
|
|
13
|
-
export const {{SCHEMA_VAR_NAME}} = createFunctionSchema('{{PACKAGE_NAME}}');
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
# Optimized Production Dockerfile for SPFN
|
|
2
|
-
# Multi-stage build for smaller image size
|
|
3
|
-
|
|
4
|
-
# Stage 1: Dependencies
|
|
5
|
-
FROM node:20-alpine AS deps
|
|
6
|
-
WORKDIR /app
|
|
7
|
-
|
|
8
|
-
# Install pnpm
|
|
9
|
-
RUN corepack enable pnpm
|
|
10
|
-
|
|
11
|
-
# Copy dependency files
|
|
12
|
-
COPY package.json pnpm-lock.yaml* ./
|
|
13
|
-
|
|
14
|
-
# Install ALL dependencies (needed for build)
|
|
15
|
-
RUN pnpm install --frozen-lockfile
|
|
16
|
-
|
|
17
|
-
# Stage 2: Builder
|
|
18
|
-
FROM node:20-alpine AS builder
|
|
19
|
-
WORKDIR /app
|
|
20
|
-
|
|
21
|
-
RUN corepack enable pnpm
|
|
22
|
-
|
|
23
|
-
# Copy dependencies from deps stage
|
|
24
|
-
COPY --from=deps /app/node_modules ./node_modules
|
|
25
|
-
COPY . .
|
|
26
|
-
|
|
27
|
-
# Build both Next.js and SPFN server
|
|
28
|
-
RUN pnpm run spfn:build
|
|
29
|
-
|
|
30
|
-
# Stage 3: Runner (Production)
|
|
31
|
-
FROM node:20-alpine AS runner
|
|
32
|
-
WORKDIR /app
|
|
33
|
-
|
|
34
|
-
ENV NODE_ENV=production
|
|
35
|
-
|
|
36
|
-
RUN corepack enable pnpm
|
|
37
|
-
|
|
38
|
-
# Copy necessary files from builder
|
|
39
|
-
COPY --from=builder /app/package.json ./package.json
|
|
40
|
-
COPY --from=builder /app/pnpm-lock.yaml ./pnpm-lock.yaml
|
|
41
|
-
COPY --from=builder /app/.next ./.next
|
|
42
|
-
COPY --from=builder /app/.spfn ./.spfn
|
|
43
|
-
COPY --from=builder /app/public ./public
|
|
44
|
-
COPY --from=builder /app/next.config.* ./
|
|
45
|
-
COPY --from=builder /app/.env.local.example ./.env.local.example
|
|
46
|
-
|
|
47
|
-
# Install PRODUCTION dependencies only
|
|
48
|
-
RUN pnpm install --frozen-lockfile --prod
|
|
49
|
-
|
|
50
|
-
# Create non-root user for security
|
|
51
|
-
RUN addgroup --system --gid 1001 nodejs
|
|
52
|
-
RUN adduser --system --uid 1001 nextjs
|
|
53
|
-
|
|
54
|
-
# Change ownership
|
|
55
|
-
RUN chown -R nextjs:nodejs /app
|
|
56
|
-
|
|
57
|
-
USER nextjs
|
|
58
|
-
|
|
59
|
-
# Expose both ports
|
|
60
|
-
EXPOSE 3790 8790
|
|
61
|
-
|
|
62
|
-
# Health check for SPFN API
|
|
63
|
-
HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \
|
|
64
|
-
CMD node -e "require('http').get('http://localhost:8790/health', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})"
|
|
65
|
-
|
|
66
|
-
# Start both Next.js and SPFN server
|
|
67
|
-
CMD ["pnpm", "run", "spfn:start"]
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Core Package Configuration
|
|
3
|
-
*
|
|
4
|
-
* @example
|
|
5
|
-
* ```typescript
|
|
6
|
-
* import { registry } from '@spfn/core/config';
|
|
7
|
-
*
|
|
8
|
-
* const env = registry.validate();
|
|
9
|
-
* console.log(env.DB_POOL_MAX);
|
|
10
|
-
* ```
|
|
11
|
-
*
|
|
12
|
-
* @module config
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
import { createEnvRegistry } from '@spfn/core/env';
|
|
16
|
-
import { envSchema } from './schema';
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Environment registry
|
|
20
|
-
*/
|
|
21
|
-
const registry = createEnvRegistry(envSchema);
|
|
22
|
-
export const env = registry.validate();
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Environment Variable Schema
|
|
3
|
-
*
|
|
4
|
-
* Centralized schema definition for all environment variables.
|
|
5
|
-
* This provides type safety, validation, and documentation for your application configuration.
|
|
6
|
-
*
|
|
7
|
-
* @example
|
|
8
|
-
* ```typescript
|
|
9
|
-
* import { envSchema } from './config/schema';
|
|
10
|
-
*
|
|
11
|
-
* // Access validated environment variables
|
|
12
|
-
* console.log(envSchema.DATABASE_URL.description);
|
|
13
|
-
* console.log(envSchema.PORT.default);
|
|
14
|
-
* ```
|
|
15
|
-
*
|
|
16
|
-
* @module config/schema
|
|
17
|
-
*/
|
|
18
|
-
|
|
19
|
-
import {
|
|
20
|
-
defineEnvSchema,
|
|
21
|
-
envString,
|
|
22
|
-
envNumber,
|
|
23
|
-
} from '@spfn/core/env';
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Application environment variable schema
|
|
27
|
-
*
|
|
28
|
-
* Add your environment variables here with proper types and validation.
|
|
29
|
-
*/
|
|
30
|
-
export const envSchema = defineEnvSchema({
|
|
31
|
-
// Example: API configuration
|
|
32
|
-
API_KEY: envString({
|
|
33
|
-
description: 'Third-party API key',
|
|
34
|
-
required: true,
|
|
35
|
-
}),
|
|
36
|
-
|
|
37
|
-
// Example: Server port
|
|
38
|
-
PORT: envNumber({
|
|
39
|
-
description: 'Server port number',
|
|
40
|
-
default: 8790,
|
|
41
|
-
}),
|
|
42
|
-
});
|