@mesh-tech/mesh-cli 0.1.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/dist/bin/mesh.d.ts +16 -0
- package/dist/bin/mesh.d.ts.map +1 -0
- package/dist/bin/mesh.js +40 -0
- package/dist/bin/mesh.js.map +1 -0
- package/dist/src/commands/create-app.d.ts +10 -0
- package/dist/src/commands/create-app.d.ts.map +1 -0
- package/dist/src/commands/create-app.js +176 -0
- package/dist/src/commands/create-app.js.map +1 -0
- package/dist/src/commands/db/connect.d.ts +13 -0
- package/dist/src/commands/db/connect.d.ts.map +1 -0
- package/dist/src/commands/db/connect.js +68 -0
- package/dist/src/commands/db/connect.js.map +1 -0
- package/dist/src/commands/db/credentials.d.ts +16 -0
- package/dist/src/commands/db/credentials.d.ts.map +1 -0
- package/dist/src/commands/db/credentials.js +56 -0
- package/dist/src/commands/db/credentials.js.map +1 -0
- package/dist/src/commands/db/env.d.ts +15 -0
- package/dist/src/commands/db/env.d.ts.map +1 -0
- package/dist/src/commands/db/env.js +50 -0
- package/dist/src/commands/db/env.js.map +1 -0
- package/dist/src/commands/db/exec.d.ts +54 -0
- package/dist/src/commands/db/exec.d.ts.map +1 -0
- package/dist/src/commands/db/exec.js +331 -0
- package/dist/src/commands/db/exec.js.map +1 -0
- package/dist/src/commands/db/index.d.ts +12 -0
- package/dist/src/commands/db/index.d.ts.map +1 -0
- package/dist/src/commands/db/index.js +62 -0
- package/dist/src/commands/db/index.js.map +1 -0
- package/dist/src/commands/db/psql.d.ts +16 -0
- package/dist/src/commands/db/psql.d.ts.map +1 -0
- package/dist/src/commands/db/psql.js +133 -0
- package/dist/src/commands/db/psql.js.map +1 -0
- package/dist/src/commands/deploy.d.ts +24 -0
- package/dist/src/commands/deploy.d.ts.map +1 -0
- package/dist/src/commands/deploy.js +228 -0
- package/dist/src/commands/deploy.js.map +1 -0
- package/dist/src/commands/dev.d.ts +17 -0
- package/dist/src/commands/dev.d.ts.map +1 -0
- package/dist/src/commands/dev.js +1248 -0
- package/dist/src/commands/dev.js.map +1 -0
- package/dist/src/commands/devbox.d.ts +22 -0
- package/dist/src/commands/devbox.d.ts.map +1 -0
- package/dist/src/commands/devbox.js +463 -0
- package/dist/src/commands/devbox.js.map +1 -0
- package/dist/src/commands/login.d.ts +47 -0
- package/dist/src/commands/login.d.ts.map +1 -0
- package/dist/src/commands/login.js +344 -0
- package/dist/src/commands/login.js.map +1 -0
- package/dist/src/commands/registry.d.ts +20 -0
- package/dist/src/commands/registry.d.ts.map +1 -0
- package/dist/src/commands/registry.js +336 -0
- package/dist/src/commands/registry.js.map +1 -0
- package/dist/src/commands/secrets/exec.d.ts +17 -0
- package/dist/src/commands/secrets/exec.d.ts.map +1 -0
- package/dist/src/commands/secrets/exec.js +132 -0
- package/dist/src/commands/secrets/exec.js.map +1 -0
- package/dist/src/commands/secrets/index.d.ts +10 -0
- package/dist/src/commands/secrets/index.d.ts.map +1 -0
- package/dist/src/commands/secrets/index.js +35 -0
- package/dist/src/commands/secrets/index.js.map +1 -0
- package/dist/src/commands/secrets/set.d.ts +22 -0
- package/dist/src/commands/secrets/set.d.ts.map +1 -0
- package/dist/src/commands/secrets/set.js +234 -0
- package/dist/src/commands/secrets/set.js.map +1 -0
- package/dist/src/commands/stack.d.ts +17 -0
- package/dist/src/commands/stack.d.ts.map +1 -0
- package/dist/src/commands/stack.js +175 -0
- package/dist/src/commands/stack.js.map +1 -0
- package/dist/src/commands/tunnel/index.d.ts +14 -0
- package/dist/src/commands/tunnel/index.d.ts.map +1 -0
- package/dist/src/commands/tunnel/index.js +217 -0
- package/dist/src/commands/tunnel/index.js.map +1 -0
- package/dist/src/index.d.ts +7 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +7 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/utils/bastion.d.ts +43 -0
- package/dist/src/utils/bastion.d.ts.map +1 -0
- package/dist/src/utils/bastion.js +77 -0
- package/dist/src/utils/bastion.js.map +1 -0
- package/dist/src/utils/context.d.ts +29 -0
- package/dist/src/utils/context.d.ts.map +1 -0
- package/dist/src/utils/context.js +132 -0
- package/dist/src/utils/context.js.map +1 -0
- package/dist/src/utils/credentials.d.ts +61 -0
- package/dist/src/utils/credentials.d.ts.map +1 -0
- package/dist/src/utils/credentials.js +145 -0
- package/dist/src/utils/credentials.js.map +1 -0
- package/dist/src/utils/index.d.ts +9 -0
- package/dist/src/utils/index.d.ts.map +1 -0
- package/dist/src/utils/index.js +9 -0
- package/dist/src/utils/index.js.map +1 -0
- package/dist/src/utils/log.d.ts +8 -0
- package/dist/src/utils/log.d.ts.map +1 -0
- package/dist/src/utils/log.js +17 -0
- package/dist/src/utils/log.js.map +1 -0
- package/dist/src/utils/pulumi.d.ts +24 -0
- package/dist/src/utils/pulumi.d.ts.map +1 -0
- package/dist/src/utils/pulumi.js +63 -0
- package/dist/src/utils/pulumi.js.map +1 -0
- package/package.json +50 -0
- package/templates/api-web-db/api/.dockerignore +2 -0
- package/templates/api-web-db/api/Dockerfile +16 -0
- package/templates/api-web-db/api/package.json.hbs +21 -0
- package/templates/api-web-db/api/src/index.ts.hbs +123 -0
- package/templates/api-web-db/api/tsconfig.json +13 -0
- package/templates/api-web-db/package.json.hbs +27 -0
- package/templates/api-web-db/sst.config.ts.hbs +151 -0
- package/templates/api-web-db/tsconfig.json +11 -0
- package/templates/api-web-db/web/.dockerignore +3 -0
- package/templates/api-web-db/web/Dockerfile +17 -0
- package/templates/api-web-db/web/app/lib/db.server.ts +36 -0
- package/templates/api-web-db/web/app/root.tsx +29 -0
- package/templates/api-web-db/web/app/routes/home.tsx.hbs +114 -0
- package/templates/api-web-db/web/app/routes.ts +3 -0
- package/templates/api-web-db/web/package.json.hbs +30 -0
- package/templates/api-web-db/web/prisma/schema.prisma +25 -0
- package/templates/api-web-db/web/prisma.config.ts +18 -0
- package/templates/api-web-db/web/react-router.config.ts +5 -0
- package/templates/api-web-db/web/tsconfig.json +19 -0
- package/templates/api-web-db/web/vite.config.ts +9 -0
- package/templates/temporal-api-worker/README.md.hbs +133 -0
- package/templates/temporal-api-worker/api/.dockerignore +4 -0
- package/templates/temporal-api-worker/api/Dockerfile +13 -0
- package/templates/temporal-api-worker/api/package.json.hbs +23 -0
- package/templates/temporal-api-worker/api/src/encryption-codec.ts +133 -0
- package/templates/temporal-api-worker/api/src/index.ts.hbs +145 -0
- package/templates/temporal-api-worker/api/tsconfig.json +14 -0
- package/templates/temporal-api-worker/package.json.hbs +29 -0
- package/templates/temporal-api-worker/sst.config.ts.hbs +161 -0
- package/templates/temporal-api-worker/tsconfig.json +10 -0
- package/templates/temporal-api-worker/worker/.dockerignore +4 -0
- package/templates/temporal-api-worker/worker/Dockerfile +11 -0
- package/templates/temporal-api-worker/worker/package.json.hbs +25 -0
- package/templates/temporal-api-worker/worker/src/activities.ts.hbs +49 -0
- package/templates/temporal-api-worker/worker/src/bundle-workflows.ts +38 -0
- package/templates/temporal-api-worker/worker/src/encryption-codec.ts +156 -0
- package/templates/temporal-api-worker/worker/src/worker.ts.hbs +93 -0
- package/templates/temporal-api-worker/worker/src/workflows.ts.hbs +60 -0
- package/templates/temporal-api-worker/worker/tsconfig.json +14 -0
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/// <reference path="./.sst/platform/config.d.ts" />
|
|
2
|
+
|
|
3
|
+
export default $config({
|
|
4
|
+
app(input) {
|
|
5
|
+
return {
|
|
6
|
+
name: "{{name}}",
|
|
7
|
+
removal: input?.stage === "prod" ? "retain" : "remove",
|
|
8
|
+
protect: ["prod"].includes(input?.stage ?? ""),
|
|
9
|
+
home: "aws",
|
|
10
|
+
providers: {
|
|
11
|
+
aws: {
|
|
12
|
+
region: "us-east-1",
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
};
|
|
16
|
+
},
|
|
17
|
+
async run() {
|
|
18
|
+
// Dynamic imports (SST requirement for non-SST packages)
|
|
19
|
+
const { mesh } = await import("@mesh-tech/infra-components");
|
|
20
|
+
const { config, getPlatform } = await import("../config.js");
|
|
21
|
+
|
|
22
|
+
const stage = $app.stage;
|
|
23
|
+
const tenant = config.tenant;
|
|
24
|
+
const platform = getPlatform(stage);
|
|
25
|
+
|
|
26
|
+
// Create tenant environment (isolated per-app namespace)
|
|
27
|
+
// DevCommand enables unified dev tunnel (mesh tunnel dev) for DB access
|
|
28
|
+
const env = new mesh.apps.AppEnvironment("env", {
|
|
29
|
+
tenant,
|
|
30
|
+
platform,
|
|
31
|
+
stage,
|
|
32
|
+
appName: "{{name}}",
|
|
33
|
+
dev: $dev,
|
|
34
|
+
namespace: true,
|
|
35
|
+
DevCommand: sst.x.DevCommand,
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
// Create database
|
|
39
|
+
// Credentials are injected at runtime via envFromSecrets:
|
|
40
|
+
// - Dev mode: mesh db exec fetches from Secrets Manager
|
|
41
|
+
// - K8s mode: ExternalSecrets syncs to K8s Secret, envFrom injects
|
|
42
|
+
const db = new mesh.apps.Database("db", {
|
|
43
|
+
env,
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// Prisma DevCommand for migrations and Studio
|
|
47
|
+
// Usage: npx sst shell --target Prisma -- npx prisma <command>
|
|
48
|
+
// Uses mesh db exec to load DATABASE_URL from secret (same as web/api)
|
|
49
|
+
new sst.x.DevCommand("Prisma", {
|
|
50
|
+
dev: {
|
|
51
|
+
title: "prisma",
|
|
52
|
+
command: $interpolate`pnpm exec mesh db exec --secret=${db.secretArn} --ssl=require -- npx prisma studio`,
|
|
53
|
+
directory: "./web",
|
|
54
|
+
autostart: false,
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// ==========================================================================
|
|
59
|
+
// API Service (Hono)
|
|
60
|
+
// ==========================================================================
|
|
61
|
+
const api = new mesh.apps.Service(
|
|
62
|
+
"api",
|
|
63
|
+
{
|
|
64
|
+
dev: $dev,
|
|
65
|
+
tenant,
|
|
66
|
+
stage,
|
|
67
|
+
appName: "{{name}}",
|
|
68
|
+
namespace: env.namespace!,
|
|
69
|
+
rootDir: $cli.paths.root,
|
|
70
|
+
src: "./api",
|
|
71
|
+
port: 3001,
|
|
72
|
+
devCommand: ["pnpm", "dev"],
|
|
73
|
+
environment: {
|
|
74
|
+
APP_NAME: "api",
|
|
75
|
+
STAGE: stage,
|
|
76
|
+
NODE_ENV: $dev ? "development" : "production",
|
|
77
|
+
},
|
|
78
|
+
// Inject DB credentials via envFromSecrets (works in both dev and K8s mode)
|
|
79
|
+
// Dev: mesh db exec fetches from Secrets Manager at runtime
|
|
80
|
+
// K8s: ExternalSecret syncs to K8s Secret, envFrom injects
|
|
81
|
+
envFromSecrets: $dev ? [db.secretArn] : [db.k8sSecretName!],
|
|
82
|
+
ingress: env.buildIngress({
|
|
83
|
+
subdomain: "{{name}}-api",
|
|
84
|
+
healthCheckPath: "/health",
|
|
85
|
+
}),
|
|
86
|
+
waitForDns: true,
|
|
87
|
+
replicas: 1,
|
|
88
|
+
resources: {
|
|
89
|
+
cpu: $dev ? "50m" : "100m",
|
|
90
|
+
memory: "128Mi",
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
providers: env.providers,
|
|
95
|
+
parent: env,
|
|
96
|
+
},
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
// ==========================================================================
|
|
100
|
+
// Web Service (React Router v7)
|
|
101
|
+
// ==========================================================================
|
|
102
|
+
const web = new mesh.apps.Service(
|
|
103
|
+
"web",
|
|
104
|
+
{
|
|
105
|
+
dev: $dev,
|
|
106
|
+
tenant,
|
|
107
|
+
stage,
|
|
108
|
+
appName: "{{name}}",
|
|
109
|
+
namespace: env.namespace!,
|
|
110
|
+
rootDir: $cli.paths.root,
|
|
111
|
+
src: "./web",
|
|
112
|
+
port: 3000,
|
|
113
|
+
devCommand: ["pnpm", "dev"],
|
|
114
|
+
environment: {
|
|
115
|
+
APP_NAME: "web",
|
|
116
|
+
STAGE: stage,
|
|
117
|
+
NODE_ENV: $dev ? "development" : "production",
|
|
118
|
+
API_URL: api.serviceEndpoint ?? api.url,
|
|
119
|
+
PUBLIC_API_URL: api.url,
|
|
120
|
+
},
|
|
121
|
+
// Inject DB credentials via envFromSecrets (works in both dev and K8s mode)
|
|
122
|
+
// Dev: mesh db exec fetches from Secrets Manager at runtime
|
|
123
|
+
// K8s: ExternalSecret syncs to K8s Secret, envFrom injects
|
|
124
|
+
envFromSecrets: $dev ? [db.secretArn] : [db.k8sSecretName!],
|
|
125
|
+
ingress: env.buildIngress({
|
|
126
|
+
subdomain: "{{name}}",
|
|
127
|
+
healthCheckPath: "/",
|
|
128
|
+
}),
|
|
129
|
+
waitForDns: true,
|
|
130
|
+
replicas: 1,
|
|
131
|
+
resources: {
|
|
132
|
+
cpu: $dev ? "50m" : "200m",
|
|
133
|
+
memory: $dev ? "128Mi" : "256Mi",
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
providers: env.providers,
|
|
138
|
+
parent: env,
|
|
139
|
+
dependsOn: [api],
|
|
140
|
+
},
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
return {
|
|
144
|
+
namespace: env.namespace,
|
|
145
|
+
apiUrl: api.url,
|
|
146
|
+
webUrl: web.url,
|
|
147
|
+
databaseUrl: db.url,
|
|
148
|
+
databaseName: db.name,
|
|
149
|
+
};
|
|
150
|
+
},
|
|
151
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
FROM node:22-alpine AS builder
|
|
2
|
+
WORKDIR /app
|
|
3
|
+
RUN corepack enable && corepack prepare pnpm@latest --activate
|
|
4
|
+
COPY package.json pnpm-lock.yaml* ./
|
|
5
|
+
RUN pnpm install
|
|
6
|
+
COPY . .
|
|
7
|
+
RUN pnpm build
|
|
8
|
+
|
|
9
|
+
FROM node:22-alpine
|
|
10
|
+
WORKDIR /app
|
|
11
|
+
RUN corepack enable && corepack prepare pnpm@latest --activate
|
|
12
|
+
COPY package.json pnpm-lock.yaml* ./
|
|
13
|
+
RUN pnpm install --prod --frozen-lockfile || pnpm install --prod
|
|
14
|
+
COPY --from=builder /app/build ./build
|
|
15
|
+
ENV NODE_ENV=production PORT=3000
|
|
16
|
+
EXPOSE 3000
|
|
17
|
+
CMD ["pnpm", "start"]
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Database connection for server-side code (loaders/actions)
|
|
3
|
+
*
|
|
4
|
+
* Uses Prisma v7 adapter pattern with DATABASE_URL from environment.
|
|
5
|
+
* - Dev mode: DATABASE_URL includes password (set by mesh db exec from SecretImport)
|
|
6
|
+
* - K8s mode: DATABASE_URL comes from ExternalSecret via envFrom
|
|
7
|
+
*
|
|
8
|
+
* This pattern eliminates runtime Secrets Manager calls.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { Pool } from "pg";
|
|
12
|
+
import { PrismaClient } from "../../src/generated/prisma/client";
|
|
13
|
+
import { PrismaPg } from "@prisma/adapter-pg";
|
|
14
|
+
|
|
15
|
+
let prisma: PrismaClient | null = null;
|
|
16
|
+
|
|
17
|
+
export function getDb(): PrismaClient {
|
|
18
|
+
if (!prisma) {
|
|
19
|
+
// Create pg Pool with SSL for RDS (required even through SSM tunnel)
|
|
20
|
+
const pool = new Pool({
|
|
21
|
+
connectionString: process.env.DATABASE_URL,
|
|
22
|
+
ssl: { rejectUnauthorized: false },
|
|
23
|
+
});
|
|
24
|
+
const adapter = new PrismaPg(pool);
|
|
25
|
+
prisma = new PrismaClient({ adapter });
|
|
26
|
+
}
|
|
27
|
+
return prisma;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Cleanup function for graceful shutdown
|
|
31
|
+
export async function closeDb(): Promise<void> {
|
|
32
|
+
if (prisma) {
|
|
33
|
+
await prisma.$disconnect();
|
|
34
|
+
prisma = null;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Links,
|
|
3
|
+
Meta,
|
|
4
|
+
Outlet,
|
|
5
|
+
Scripts,
|
|
6
|
+
ScrollRestoration,
|
|
7
|
+
} from "react-router";
|
|
8
|
+
|
|
9
|
+
export function Layout({ children }: { children: React.ReactNode }) {
|
|
10
|
+
return (
|
|
11
|
+
<html lang="en">
|
|
12
|
+
<head>
|
|
13
|
+
<meta charSet="utf-8" />
|
|
14
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
15
|
+
<Meta />
|
|
16
|
+
<Links />
|
|
17
|
+
</head>
|
|
18
|
+
<body style={{ fontFamily: "system-ui, sans-serif", margin: 0 }}>
|
|
19
|
+
{children}
|
|
20
|
+
<ScrollRestoration />
|
|
21
|
+
<Scripts />
|
|
22
|
+
</body>
|
|
23
|
+
</html>
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export default function App() {
|
|
28
|
+
return <Outlet />;
|
|
29
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import type { Route } from "./+types/home";
|
|
2
|
+
|
|
3
|
+
export function meta({}: Route.MetaArgs) {
|
|
4
|
+
return [
|
|
5
|
+
{ title: "{{titleCase name}}" },
|
|
6
|
+
{ name: "description", content: "{{titleCase name}} - Mesh App" },
|
|
7
|
+
];
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export async function loader({}: Route.LoaderArgs) {
|
|
11
|
+
const apiUrl = process.env.API_URL || "http://localhost:3001";
|
|
12
|
+
|
|
13
|
+
// Fetch API hello endpoint
|
|
14
|
+
let apiData = { message: "API unavailable", timestamp: null };
|
|
15
|
+
try {
|
|
16
|
+
const response = await fetch(`${apiUrl}/hello`);
|
|
17
|
+
apiData = await response.json();
|
|
18
|
+
} catch (error) {
|
|
19
|
+
console.error("API fetch error:", error);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Fetch database status
|
|
23
|
+
let dbData = { status: "unknown", database: null, user: null, version: null };
|
|
24
|
+
try {
|
|
25
|
+
const response = await fetch(`${apiUrl}/db`);
|
|
26
|
+
dbData = await response.json();
|
|
27
|
+
} catch (error) {
|
|
28
|
+
console.error("DB fetch error:", error);
|
|
29
|
+
dbData = { status: "error", database: null, user: null, version: null };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return {
|
|
33
|
+
message: apiData.message,
|
|
34
|
+
timestamp: apiData.timestamp,
|
|
35
|
+
stage: process.env.STAGE || "unknown",
|
|
36
|
+
db: dbData,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export default function Home({ loaderData }: Route.ComponentProps) {
|
|
41
|
+
const { message, timestamp, stage, db } = loaderData;
|
|
42
|
+
const dbConnected = db?.status === "connected";
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<div style=\{{ padding: "2rem", maxWidth: "600px", margin: "0 auto" }}>
|
|
46
|
+
<h1 style=\{{ fontSize: "2rem", marginBottom: "1rem" }}>{{titleCase name}}</h1>
|
|
47
|
+
|
|
48
|
+
<div
|
|
49
|
+
style=\{{
|
|
50
|
+
background: "#f0f9ff",
|
|
51
|
+
border: "1px solid #0ea5e9",
|
|
52
|
+
borderRadius: "8px",
|
|
53
|
+
padding: "1rem",
|
|
54
|
+
marginBottom: "1rem",
|
|
55
|
+
}}
|
|
56
|
+
>
|
|
57
|
+
<h2 style=\{{ margin: "0 0 0.5rem 0", fontSize: "1rem" }}>
|
|
58
|
+
Web (React Router v7)
|
|
59
|
+
</h2>
|
|
60
|
+
<p style=\{{ margin: 0, color: "#666" }}>Stage: {stage}</p>
|
|
61
|
+
</div>
|
|
62
|
+
|
|
63
|
+
<div
|
|
64
|
+
style=\{{
|
|
65
|
+
background: "#f0fdf4",
|
|
66
|
+
border: "1px solid #22c55e",
|
|
67
|
+
borderRadius: "8px",
|
|
68
|
+
padding: "1rem",
|
|
69
|
+
marginBottom: "1rem",
|
|
70
|
+
}}
|
|
71
|
+
>
|
|
72
|
+
<h2 style=\{{ margin: "0 0 0.5rem 0", fontSize: "1rem" }}>
|
|
73
|
+
API Response (Hono)
|
|
74
|
+
</h2>
|
|
75
|
+
<p style=\{{ margin: "0 0 0.25rem 0" }}>Message: {message}</p>
|
|
76
|
+
{timestamp && (
|
|
77
|
+
<p style=\{{ margin: 0, color: "#666", fontSize: "0.875rem" }}>
|
|
78
|
+
Timestamp: {timestamp}
|
|
79
|
+
</p>
|
|
80
|
+
)}
|
|
81
|
+
</div>
|
|
82
|
+
|
|
83
|
+
<div
|
|
84
|
+
style=\{{
|
|
85
|
+
background: dbConnected ? "#fdf4ff" : "#fef2f2",
|
|
86
|
+
border: `1px solid ${dbConnected ? "#a855f7" : "#ef4444"}`,
|
|
87
|
+
borderRadius: "8px",
|
|
88
|
+
padding: "1rem",
|
|
89
|
+
}}
|
|
90
|
+
>
|
|
91
|
+
<h2 style=\{{ margin: "0 0 0.5rem 0", fontSize: "1rem" }}>
|
|
92
|
+
Database (RDS)
|
|
93
|
+
</h2>
|
|
94
|
+
<p style=\{{ margin: "0 0 0.25rem 0" }}>
|
|
95
|
+
Status: {dbConnected ? "Connected" : `${db?.status || "Unknown"}`}
|
|
96
|
+
</p>
|
|
97
|
+
{dbConnected && (
|
|
98
|
+
<>
|
|
99
|
+
<p style=\{{ margin: "0 0 0.25rem 0", color: "#666", fontSize: "0.875rem" }}>
|
|
100
|
+
Database: {db.database}
|
|
101
|
+
</p>
|
|
102
|
+
<p style=\{{ margin: 0, color: "#666", fontSize: "0.875rem" }}>
|
|
103
|
+
User: {db.user}
|
|
104
|
+
</p>
|
|
105
|
+
</>
|
|
106
|
+
)}
|
|
107
|
+
</div>
|
|
108
|
+
|
|
109
|
+
<p style=\{{ marginTop: "1.5rem", color: "#666", fontSize: "0.875rem" }}>
|
|
110
|
+
Edit files in <code>web/</code> or <code>api/</code> to see live reload!
|
|
111
|
+
</p>
|
|
112
|
+
</div>
|
|
113
|
+
);
|
|
114
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{name}}-web",
|
|
3
|
+
"type": "module",
|
|
4
|
+
"scripts": {
|
|
5
|
+
"dev": "react-router dev",
|
|
6
|
+
"build": "react-router build",
|
|
7
|
+
"start": "react-router-serve ./build/server/index.js"
|
|
8
|
+
},
|
|
9
|
+
"dependencies": {
|
|
10
|
+
"@prisma/adapter-pg": "^7.0.0",
|
|
11
|
+
"@prisma/client": "^7.0.0",
|
|
12
|
+
"@react-router/node": "^7.1.1",
|
|
13
|
+
"@react-router/serve": "^7.1.1",
|
|
14
|
+
"isbot": "^5.1.17",
|
|
15
|
+
"pg": "^8.13.0",
|
|
16
|
+
"prisma": "^7.0.0",
|
|
17
|
+
"react": "^19.0.0",
|
|
18
|
+
"react-dom": "^19.0.0",
|
|
19
|
+
"react-router": "^7.1.1"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"@react-router/dev": "^7.1.1",
|
|
23
|
+
"@types/node": "^22.10.5",
|
|
24
|
+
"@types/pg": "^8.11.0",
|
|
25
|
+
"@types/react": "^19.0.2",
|
|
26
|
+
"@types/react-dom": "^19.0.2",
|
|
27
|
+
"typescript": "^5.7.2",
|
|
28
|
+
"vite": "^6.0.7"
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// Prisma v7 Schema
|
|
2
|
+
// Docs: https://www.prisma.io/docs
|
|
3
|
+
|
|
4
|
+
generator client {
|
|
5
|
+
provider = "prisma-client"
|
|
6
|
+
output = "../src/generated/prisma"
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
datasource db {
|
|
10
|
+
provider = "postgresql"
|
|
11
|
+
// Note: url is configured in prisma.config.ts (Prisma v7 pattern)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// =============================================================================
|
|
15
|
+
// Example Model - Replace with your domain models
|
|
16
|
+
// =============================================================================
|
|
17
|
+
|
|
18
|
+
model Example {
|
|
19
|
+
id String @id @default(cuid())
|
|
20
|
+
name String
|
|
21
|
+
createdAt DateTime @default(now())
|
|
22
|
+
updatedAt DateTime @updatedAt
|
|
23
|
+
|
|
24
|
+
@@map("examples")
|
|
25
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Prisma v7 configuration for CLI commands (migrations, db push, etc.)
|
|
3
|
+
*
|
|
4
|
+
* Usage with SST:
|
|
5
|
+
* npx sst shell --target Prisma -- npx prisma db push
|
|
6
|
+
* npx sst shell --target Prisma -- npx prisma migrate dev --name <name>
|
|
7
|
+
* npx sst shell --target Prisma -- npx prisma studio
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { defineConfig } from "prisma/config";
|
|
11
|
+
|
|
12
|
+
export default defineConfig({
|
|
13
|
+
schema: "prisma/schema.prisma",
|
|
14
|
+
|
|
15
|
+
datasource: {
|
|
16
|
+
url: process.env.DATABASE_URL!,
|
|
17
|
+
},
|
|
18
|
+
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"include": [
|
|
3
|
+
"**/*.ts",
|
|
4
|
+
"**/*.tsx",
|
|
5
|
+
".react-router/types/**/*"
|
|
6
|
+
],
|
|
7
|
+
"compilerOptions": {
|
|
8
|
+
"target": "ES2022",
|
|
9
|
+
"lib": ["DOM", "DOM.Iterable", "ES2022"],
|
|
10
|
+
"types": ["node", "@react-router/node", "vite/client"],
|
|
11
|
+
"module": "ESNext",
|
|
12
|
+
"moduleResolution": "bundler",
|
|
13
|
+
"jsx": "react-jsx",
|
|
14
|
+
"strict": true,
|
|
15
|
+
"skipLibCheck": true,
|
|
16
|
+
"noEmit": true,
|
|
17
|
+
"rootDirs": [".", "./.react-router/types"]
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# {{titleCase name}}
|
|
2
|
+
|
|
3
|
+
Temporal workflow application for {{tenant}}.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
This app demonstrates Temporal workflow orchestration with:
|
|
8
|
+
- **Worker**: Executes workflows and activities
|
|
9
|
+
- **API**: HTTP endpoints to trigger workflows
|
|
10
|
+
|
|
11
|
+
## Prerequisites
|
|
12
|
+
|
|
13
|
+
- MeshPlatform deployed with `temporal: { ... }` enabled
|
|
14
|
+
- AWS credentials with apps-deployer role
|
|
15
|
+
|
|
16
|
+
## Development
|
|
17
|
+
|
|
18
|
+
### 1. Start Dev Server
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
pnpm mesh dev
|
|
22
|
+
# Auto-starts unified tunnel (Temporal gRPC:7233, Web UI:8080, DB:5432)
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### 2. Start Worker Manually
|
|
26
|
+
|
|
27
|
+
Workers don't auto-start because they need the Temporal namespace to exist first.
|
|
28
|
+
After `sst dev` is ready:
|
|
29
|
+
1. Open SST dev UI
|
|
30
|
+
2. Find the worker service
|
|
31
|
+
3. Click to start it
|
|
32
|
+
|
|
33
|
+
### 4. Test Workflows
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
# Trigger greeting workflow
|
|
37
|
+
curl -X POST http://localhost:3000/greet \
|
|
38
|
+
-H "Content-Type: application/json" \
|
|
39
|
+
-d '{"name": "World"}'
|
|
40
|
+
|
|
41
|
+
# Trigger task workflow
|
|
42
|
+
curl -X POST http://localhost:3000/task \
|
|
43
|
+
-H "Content-Type: application/json" \
|
|
44
|
+
-d '{"taskId": "TASK-123", "userId": "user-456"}'
|
|
45
|
+
|
|
46
|
+
# Check workflow status
|
|
47
|
+
curl http://localhost:3000/workflow/task-TASK-123
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### 5. View in Web UI
|
|
51
|
+
|
|
52
|
+
Open http://localhost:8080 and navigate to your namespace.
|
|
53
|
+
|
|
54
|
+
## Deployment
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
pnpm deploy --stage dev
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Project Structure
|
|
61
|
+
|
|
62
|
+
```
|
|
63
|
+
{{name}}/
|
|
64
|
+
├── sst.config.ts # SST config with AppEnvironment + TemporalWorker
|
|
65
|
+
├── worker/
|
|
66
|
+
│ ├── src/
|
|
67
|
+
│ │ ├── worker.ts # Worker entry point
|
|
68
|
+
│ │ ├── workflows.ts # Workflow definitions
|
|
69
|
+
│ │ ├── activities.ts # Activity implementations
|
|
70
|
+
│ │ └── encryption-codec.ts # Payload encryption
|
|
71
|
+
│ ├── Dockerfile
|
|
72
|
+
│ └── package.json
|
|
73
|
+
├── api/
|
|
74
|
+
│ ├── src/
|
|
75
|
+
│ │ ├── index.ts # Express API
|
|
76
|
+
│ │ └── encryption-codec.ts # Payload encryption
|
|
77
|
+
│ ├── Dockerfile
|
|
78
|
+
│ └── package.json
|
|
79
|
+
└── README.md
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Customization
|
|
83
|
+
|
|
84
|
+
### Adding Workflows
|
|
85
|
+
|
|
86
|
+
1. Define workflow in `worker/src/workflows.ts`
|
|
87
|
+
2. Add activities in `worker/src/activities.ts`
|
|
88
|
+
3. Add API endpoint in `api/src/index.ts`
|
|
89
|
+
|
|
90
|
+
### Workflow Patterns
|
|
91
|
+
|
|
92
|
+
```typescript
|
|
93
|
+
// Simple workflow
|
|
94
|
+
export async function myWorkflow(input: string): Promise<string> {
|
|
95
|
+
const result = await myActivity(input);
|
|
96
|
+
return result;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Workflow with timer
|
|
100
|
+
export async function timedWorkflow(): Promise<void> {
|
|
101
|
+
await doSomething();
|
|
102
|
+
await sleep("1 hour"); // Durable timer
|
|
103
|
+
await doSomethingElse();
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Workflow with error handling
|
|
107
|
+
export async function safeWorkflow(): Promise<string> {
|
|
108
|
+
try {
|
|
109
|
+
return await riskyActivity();
|
|
110
|
+
} catch (err) {
|
|
111
|
+
return await fallbackActivity();
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Troubleshooting
|
|
117
|
+
|
|
118
|
+
### Worker Can't Connect
|
|
119
|
+
|
|
120
|
+
1. Ensure tunnel is running in SST dev (check "tunnel" pane)
|
|
121
|
+
2. Check `TEMPORAL_ADDRESS` in worker logs
|
|
122
|
+
3. Or start tunnel manually: `pnpm mesh tunnel dev`
|
|
123
|
+
|
|
124
|
+
### Encrypted Payloads in Web UI
|
|
125
|
+
|
|
126
|
+
Configure codec server in Web UI settings:
|
|
127
|
+
- Dev: `http://localhost:8083`
|
|
128
|
+
- Deploy: Check `codecServerUrl` output
|
|
129
|
+
|
|
130
|
+
### Workflow Not Found
|
|
131
|
+
|
|
132
|
+
1. Ensure worker is running
|
|
133
|
+
2. Check task queue name matches between API and worker
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@{{tenant}}/{{name}}-api",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "tsx watch src/index.ts",
|
|
8
|
+
"start": "node dist/index.js",
|
|
9
|
+
"build": "tsc"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"@temporalio/client": "^1.11.7",
|
|
13
|
+
"@temporalio/common": "^1.11.7",
|
|
14
|
+
"@temporalio/proto": "^1.11.7",
|
|
15
|
+
"express": "^4.21.0"
|
|
16
|
+
},
|
|
17
|
+
"devDependencies": {
|
|
18
|
+
"@types/express": "^4.17.21",
|
|
19
|
+
"@types/node": "^20.0.0",
|
|
20
|
+
"tsx": "^4.0.0",
|
|
21
|
+
"typescript": "^5.0.0"
|
|
22
|
+
}
|
|
23
|
+
}
|