@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.
Files changed (140) hide show
  1. package/dist/bin/mesh.d.ts +16 -0
  2. package/dist/bin/mesh.d.ts.map +1 -0
  3. package/dist/bin/mesh.js +40 -0
  4. package/dist/bin/mesh.js.map +1 -0
  5. package/dist/src/commands/create-app.d.ts +10 -0
  6. package/dist/src/commands/create-app.d.ts.map +1 -0
  7. package/dist/src/commands/create-app.js +176 -0
  8. package/dist/src/commands/create-app.js.map +1 -0
  9. package/dist/src/commands/db/connect.d.ts +13 -0
  10. package/dist/src/commands/db/connect.d.ts.map +1 -0
  11. package/dist/src/commands/db/connect.js +68 -0
  12. package/dist/src/commands/db/connect.js.map +1 -0
  13. package/dist/src/commands/db/credentials.d.ts +16 -0
  14. package/dist/src/commands/db/credentials.d.ts.map +1 -0
  15. package/dist/src/commands/db/credentials.js +56 -0
  16. package/dist/src/commands/db/credentials.js.map +1 -0
  17. package/dist/src/commands/db/env.d.ts +15 -0
  18. package/dist/src/commands/db/env.d.ts.map +1 -0
  19. package/dist/src/commands/db/env.js +50 -0
  20. package/dist/src/commands/db/env.js.map +1 -0
  21. package/dist/src/commands/db/exec.d.ts +54 -0
  22. package/dist/src/commands/db/exec.d.ts.map +1 -0
  23. package/dist/src/commands/db/exec.js +331 -0
  24. package/dist/src/commands/db/exec.js.map +1 -0
  25. package/dist/src/commands/db/index.d.ts +12 -0
  26. package/dist/src/commands/db/index.d.ts.map +1 -0
  27. package/dist/src/commands/db/index.js +62 -0
  28. package/dist/src/commands/db/index.js.map +1 -0
  29. package/dist/src/commands/db/psql.d.ts +16 -0
  30. package/dist/src/commands/db/psql.d.ts.map +1 -0
  31. package/dist/src/commands/db/psql.js +133 -0
  32. package/dist/src/commands/db/psql.js.map +1 -0
  33. package/dist/src/commands/deploy.d.ts +24 -0
  34. package/dist/src/commands/deploy.d.ts.map +1 -0
  35. package/dist/src/commands/deploy.js +228 -0
  36. package/dist/src/commands/deploy.js.map +1 -0
  37. package/dist/src/commands/dev.d.ts +17 -0
  38. package/dist/src/commands/dev.d.ts.map +1 -0
  39. package/dist/src/commands/dev.js +1248 -0
  40. package/dist/src/commands/dev.js.map +1 -0
  41. package/dist/src/commands/devbox.d.ts +22 -0
  42. package/dist/src/commands/devbox.d.ts.map +1 -0
  43. package/dist/src/commands/devbox.js +463 -0
  44. package/dist/src/commands/devbox.js.map +1 -0
  45. package/dist/src/commands/login.d.ts +47 -0
  46. package/dist/src/commands/login.d.ts.map +1 -0
  47. package/dist/src/commands/login.js +344 -0
  48. package/dist/src/commands/login.js.map +1 -0
  49. package/dist/src/commands/registry.d.ts +20 -0
  50. package/dist/src/commands/registry.d.ts.map +1 -0
  51. package/dist/src/commands/registry.js +336 -0
  52. package/dist/src/commands/registry.js.map +1 -0
  53. package/dist/src/commands/secrets/exec.d.ts +17 -0
  54. package/dist/src/commands/secrets/exec.d.ts.map +1 -0
  55. package/dist/src/commands/secrets/exec.js +132 -0
  56. package/dist/src/commands/secrets/exec.js.map +1 -0
  57. package/dist/src/commands/secrets/index.d.ts +10 -0
  58. package/dist/src/commands/secrets/index.d.ts.map +1 -0
  59. package/dist/src/commands/secrets/index.js +35 -0
  60. package/dist/src/commands/secrets/index.js.map +1 -0
  61. package/dist/src/commands/secrets/set.d.ts +22 -0
  62. package/dist/src/commands/secrets/set.d.ts.map +1 -0
  63. package/dist/src/commands/secrets/set.js +234 -0
  64. package/dist/src/commands/secrets/set.js.map +1 -0
  65. package/dist/src/commands/stack.d.ts +17 -0
  66. package/dist/src/commands/stack.d.ts.map +1 -0
  67. package/dist/src/commands/stack.js +175 -0
  68. package/dist/src/commands/stack.js.map +1 -0
  69. package/dist/src/commands/tunnel/index.d.ts +14 -0
  70. package/dist/src/commands/tunnel/index.d.ts.map +1 -0
  71. package/dist/src/commands/tunnel/index.js +217 -0
  72. package/dist/src/commands/tunnel/index.js.map +1 -0
  73. package/dist/src/index.d.ts +7 -0
  74. package/dist/src/index.d.ts.map +1 -0
  75. package/dist/src/index.js +7 -0
  76. package/dist/src/index.js.map +1 -0
  77. package/dist/src/utils/bastion.d.ts +43 -0
  78. package/dist/src/utils/bastion.d.ts.map +1 -0
  79. package/dist/src/utils/bastion.js +77 -0
  80. package/dist/src/utils/bastion.js.map +1 -0
  81. package/dist/src/utils/context.d.ts +29 -0
  82. package/dist/src/utils/context.d.ts.map +1 -0
  83. package/dist/src/utils/context.js +132 -0
  84. package/dist/src/utils/context.js.map +1 -0
  85. package/dist/src/utils/credentials.d.ts +61 -0
  86. package/dist/src/utils/credentials.d.ts.map +1 -0
  87. package/dist/src/utils/credentials.js +145 -0
  88. package/dist/src/utils/credentials.js.map +1 -0
  89. package/dist/src/utils/index.d.ts +9 -0
  90. package/dist/src/utils/index.d.ts.map +1 -0
  91. package/dist/src/utils/index.js +9 -0
  92. package/dist/src/utils/index.js.map +1 -0
  93. package/dist/src/utils/log.d.ts +8 -0
  94. package/dist/src/utils/log.d.ts.map +1 -0
  95. package/dist/src/utils/log.js +17 -0
  96. package/dist/src/utils/log.js.map +1 -0
  97. package/dist/src/utils/pulumi.d.ts +24 -0
  98. package/dist/src/utils/pulumi.d.ts.map +1 -0
  99. package/dist/src/utils/pulumi.js +63 -0
  100. package/dist/src/utils/pulumi.js.map +1 -0
  101. package/package.json +50 -0
  102. package/templates/api-web-db/api/.dockerignore +2 -0
  103. package/templates/api-web-db/api/Dockerfile +16 -0
  104. package/templates/api-web-db/api/package.json.hbs +21 -0
  105. package/templates/api-web-db/api/src/index.ts.hbs +123 -0
  106. package/templates/api-web-db/api/tsconfig.json +13 -0
  107. package/templates/api-web-db/package.json.hbs +27 -0
  108. package/templates/api-web-db/sst.config.ts.hbs +151 -0
  109. package/templates/api-web-db/tsconfig.json +11 -0
  110. package/templates/api-web-db/web/.dockerignore +3 -0
  111. package/templates/api-web-db/web/Dockerfile +17 -0
  112. package/templates/api-web-db/web/app/lib/db.server.ts +36 -0
  113. package/templates/api-web-db/web/app/root.tsx +29 -0
  114. package/templates/api-web-db/web/app/routes/home.tsx.hbs +114 -0
  115. package/templates/api-web-db/web/app/routes.ts +3 -0
  116. package/templates/api-web-db/web/package.json.hbs +30 -0
  117. package/templates/api-web-db/web/prisma/schema.prisma +25 -0
  118. package/templates/api-web-db/web/prisma.config.ts +18 -0
  119. package/templates/api-web-db/web/react-router.config.ts +5 -0
  120. package/templates/api-web-db/web/tsconfig.json +19 -0
  121. package/templates/api-web-db/web/vite.config.ts +9 -0
  122. package/templates/temporal-api-worker/README.md.hbs +133 -0
  123. package/templates/temporal-api-worker/api/.dockerignore +4 -0
  124. package/templates/temporal-api-worker/api/Dockerfile +13 -0
  125. package/templates/temporal-api-worker/api/package.json.hbs +23 -0
  126. package/templates/temporal-api-worker/api/src/encryption-codec.ts +133 -0
  127. package/templates/temporal-api-worker/api/src/index.ts.hbs +145 -0
  128. package/templates/temporal-api-worker/api/tsconfig.json +14 -0
  129. package/templates/temporal-api-worker/package.json.hbs +29 -0
  130. package/templates/temporal-api-worker/sst.config.ts.hbs +161 -0
  131. package/templates/temporal-api-worker/tsconfig.json +10 -0
  132. package/templates/temporal-api-worker/worker/.dockerignore +4 -0
  133. package/templates/temporal-api-worker/worker/Dockerfile +11 -0
  134. package/templates/temporal-api-worker/worker/package.json.hbs +25 -0
  135. package/templates/temporal-api-worker/worker/src/activities.ts.hbs +49 -0
  136. package/templates/temporal-api-worker/worker/src/bundle-workflows.ts +38 -0
  137. package/templates/temporal-api-worker/worker/src/encryption-codec.ts +156 -0
  138. package/templates/temporal-api-worker/worker/src/worker.ts.hbs +93 -0
  139. package/templates/temporal-api-worker/worker/src/workflows.ts.hbs +60 -0
  140. package/templates/temporal-api-worker/worker/tsconfig.json +14 -0
@@ -0,0 +1,133 @@
1
+ /**
2
+ * Encryption Codec for Temporal Payload Encryption
3
+ *
4
+ * Implements AES-256-GCM encryption for workflow payloads.
5
+ * Uses TEMPORAL_ENCODING_KEY environment variable.
6
+ *
7
+ * This is a copy of the worker's encryption codec - both need
8
+ * to use the same encryption to communicate.
9
+ */
10
+
11
+ import * as crypto from "crypto";
12
+ import {
13
+ PayloadCodec,
14
+ Payload,
15
+ encodingKeys,
16
+ METADATA_ENCODING_KEY,
17
+ } from "@temporalio/common";
18
+ import proto from "@temporalio/proto";
19
+
20
+ const { temporal } = proto;
21
+
22
+ const ENCODING_TYPE = "binary/encrypted";
23
+ const ALGORITHM = "aes-256-gcm";
24
+ const IV_LENGTH = 12;
25
+ const AUTH_TAG_LENGTH = 16;
26
+
27
+ /**
28
+ * Derive a 256-bit key from the encoding key using SHA-256
29
+ */
30
+ async function deriveKey(encodingKey: string): Promise<Buffer> {
31
+ return crypto.createHash("sha256").update(encodingKey).digest();
32
+ }
33
+
34
+ /**
35
+ * Encrypt data using AES-256-GCM
36
+ */
37
+ function encrypt(data: Uint8Array, key: Buffer): Uint8Array {
38
+ const iv = crypto.randomBytes(IV_LENGTH);
39
+ const cipher = crypto.createCipheriv(ALGORITHM, key, iv);
40
+
41
+ const encrypted = Buffer.concat([cipher.update(data), cipher.final()]);
42
+ const authTag = cipher.getAuthTag();
43
+
44
+ // Format: IV (12 bytes) + Ciphertext + AuthTag (16 bytes)
45
+ return Buffer.concat([iv, encrypted, authTag]);
46
+ }
47
+
48
+ /**
49
+ * Decrypt data using AES-256-GCM
50
+ */
51
+ function decrypt(data: Uint8Array, key: Buffer): Uint8Array {
52
+ const iv = data.slice(0, IV_LENGTH);
53
+ const authTag = data.slice(-AUTH_TAG_LENGTH);
54
+ const ciphertext = data.slice(IV_LENGTH, -AUTH_TAG_LENGTH);
55
+
56
+ const decipher = crypto.createDecipheriv(ALGORITHM, key, iv);
57
+ decipher.setAuthTag(authTag);
58
+
59
+ return Buffer.concat([decipher.update(ciphertext), decipher.final()]);
60
+ }
61
+
62
+ /**
63
+ * Encryption codec implementing Temporal's PayloadCodec interface
64
+ */
65
+ export class EncryptionCodec implements PayloadCodec {
66
+ private readonly key: Buffer;
67
+ private readonly keyId: string;
68
+
69
+ private constructor(key: Buffer, keyId: string) {
70
+ this.key = key;
71
+ this.keyId = keyId;
72
+ }
73
+
74
+ static async create(encodingKey: string): Promise<EncryptionCodec> {
75
+ const key = await deriveKey(encodingKey);
76
+ // Use first 8 chars of key hash as key ID
77
+ const keyId = crypto
78
+ .createHash("sha256")
79
+ .update(encodingKey)
80
+ .digest("hex")
81
+ .slice(0, 8);
82
+ return new EncryptionCodec(key, keyId);
83
+ }
84
+
85
+ async encode(payloads: Payload[]): Promise<Payload[]> {
86
+ return payloads.map((payload) => {
87
+ // Serialize the payload to bytes
88
+ const bytes = temporal.api.common.v1.Payload.encode(payload).finish();
89
+
90
+ // Encrypt
91
+ const encrypted = encrypt(bytes, this.key);
92
+
93
+ return {
94
+ metadata: {
95
+ [METADATA_ENCODING_KEY]: encodingKeys.METADATA_ENCODING_RAW,
96
+ "encryption-codec": Buffer.from(ENCODING_TYPE),
97
+ "encryption-key-id": Buffer.from(this.keyId),
98
+ },
99
+ data: encrypted,
100
+ };
101
+ });
102
+ }
103
+
104
+ async decode(payloads: Payload[]): Promise<Payload[]> {
105
+ return payloads.map((payload) => {
106
+ // Check if this payload is encrypted by us
107
+ const encoding = payload.metadata?.["encryption-codec"];
108
+ if (!encoding || Buffer.from(encoding).toString() !== ENCODING_TYPE) {
109
+ return payload;
110
+ }
111
+
112
+ // Decrypt
113
+ const decrypted = decrypt(payload.data!, this.key);
114
+
115
+ // Deserialize back to Payload
116
+ return temporal.api.common.v1.Payload.decode(decrypted);
117
+ });
118
+ }
119
+ }
120
+
121
+ /**
122
+ * Create encryption codec from environment variable
123
+ */
124
+ export async function createEncryptionCodec(): Promise<
125
+ EncryptionCodec | undefined
126
+ > {
127
+ const encodingKey = process.env.TEMPORAL_ENCODING_KEY;
128
+ if (!encodingKey) {
129
+ return undefined;
130
+ }
131
+
132
+ return EncryptionCodec.create(encodingKey);
133
+ }
@@ -0,0 +1,145 @@
1
+ /**
2
+ * {{titleCase name}} API
3
+ *
4
+ * HTTP API for triggering Temporal workflows.
5
+ *
6
+ * Environment variables:
7
+ * - PORT: Server port (default: 3000)
8
+ * - TEMPORAL_ADDRESS: Temporal server address (default: localhost:7233)
9
+ * - TEMPORAL_NAMESPACE: Temporal namespace (required)
10
+ * - TEMPORAL_TASK_QUEUE: Task queue name (required)
11
+ * - TEMPORAL_ENCODING_KEY: Encoding key for payload encryption (optional)
12
+ */
13
+
14
+ import express, { Request, Response } from "express";
15
+ import { Client, Connection } from "@temporalio/client";
16
+ import { createEncryptionCodec } from "./encryption-codec.js";
17
+
18
+ const PORT = parseInt(process.env.PORT ?? "3000", 10);
19
+ const TEMPORAL_ADDRESS = process.env.TEMPORAL_ADDRESS ?? "localhost:7233";
20
+
21
+ function requireEnv(name: string): string {
22
+ const value = process.env[name];
23
+ if (!value) {
24
+ throw new Error(`${name} environment variable is required`);
25
+ }
26
+ return value;
27
+ }
28
+
29
+ const TEMPORAL_NAMESPACE = requireEnv("TEMPORAL_NAMESPACE");
30
+ const TEMPORAL_TASK_QUEUE = requireEnv("TEMPORAL_TASK_QUEUE");
31
+
32
+ const app = express();
33
+ app.use(express.json());
34
+
35
+ let client: Client;
36
+
37
+ // Initialize Temporal client
38
+ async function initClient(): Promise<void> {
39
+ console.log(`[API] Connecting to Temporal at ${TEMPORAL_ADDRESS}...`);
40
+
41
+ const connection = await Connection.connect({
42
+ address: TEMPORAL_ADDRESS,
43
+ });
44
+
45
+ // Set up payload encryption if encoding key is available
46
+ const encryptionCodec = await createEncryptionCodec();
47
+ const dataConverter = encryptionCodec
48
+ ? { payloadCodecs: [encryptionCodec] }
49
+ : undefined;
50
+
51
+ client = new Client({
52
+ connection,
53
+ namespace: TEMPORAL_NAMESPACE,
54
+ dataConverter,
55
+ });
56
+
57
+ console.log(`[API] Connected. Namespace: ${TEMPORAL_NAMESPACE}`);
58
+ }
59
+
60
+ // Health check
61
+ app.get("/health", (_req: Request, res: Response) => {
62
+ res.json({ status: "ok" });
63
+ });
64
+
65
+ // Trigger greeting workflow
66
+ app.post("/greet", async (req: Request, res: Response) => {
67
+ try {
68
+ const { name } = req.body as { name: string };
69
+ if (!name) {
70
+ res.status(400).json({ error: "name is required" });
71
+ return;
72
+ }
73
+
74
+ const handle = await client.workflow.start("greetingWorkflow", {
75
+ taskQueue: TEMPORAL_TASK_QUEUE,
76
+ workflowId: `greeting-${Date.now()}`,
77
+ args: [name],
78
+ });
79
+
80
+ // Wait for result (greeting is quick)
81
+ const result = await handle.result();
82
+ res.json({ workflowId: handle.workflowId, result });
83
+ } catch (error) {
84
+ console.error("[API] Error starting workflow:", error);
85
+ res.status(500).json({ error: "Failed to start workflow" });
86
+ }
87
+ });
88
+
89
+ // Trigger task workflow
90
+ app.post("/task", async (req: Request, res: Response) => {
91
+ try {
92
+ const { taskId, userId } = req.body as { taskId: string; userId: string };
93
+ if (!taskId || !userId) {
94
+ res.status(400).json({ error: "taskId and userId are required" });
95
+ return;
96
+ }
97
+
98
+ const handle = await client.workflow.start("taskWorkflow", {
99
+ taskQueue: TEMPORAL_TASK_QUEUE,
100
+ workflowId: `task-${taskId}`,
101
+ args: [taskId, userId],
102
+ });
103
+
104
+ // Don't wait for result - return workflow ID immediately
105
+ res.json({
106
+ workflowId: handle.workflowId,
107
+ status: "started",
108
+ message: "Workflow started. Check Temporal Web UI for progress.",
109
+ });
110
+ } catch (error) {
111
+ console.error("[API] Error starting workflow:", error);
112
+ res.status(500).json({ error: "Failed to start workflow" });
113
+ }
114
+ });
115
+
116
+ // Get workflow status
117
+ app.get("/workflow/:workflowId", async (req: Request, res: Response) => {
118
+ try {
119
+ const { workflowId } = req.params;
120
+ const handle = client.workflow.getHandle(workflowId);
121
+ const describe = await handle.describe();
122
+
123
+ res.json({
124
+ workflowId,
125
+ status: describe.status.name,
126
+ startTime: describe.startTime,
127
+ closeTime: describe.closeTime,
128
+ });
129
+ } catch (error) {
130
+ console.error("[API] Error getting workflow:", error);
131
+ res.status(500).json({ error: "Failed to get workflow status" });
132
+ }
133
+ });
134
+
135
+ // Start server
136
+ initClient()
137
+ .then(() => {
138
+ app.listen(PORT, "0.0.0.0", () => {
139
+ console.log(`[API] {{titleCase name}} API listening on port ${PORT}`);
140
+ });
141
+ })
142
+ .catch((err) => {
143
+ console.error("[API] Failed to initialize:", err);
144
+ process.exit(1);
145
+ });
@@ -0,0 +1,14 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "NodeNext",
5
+ "moduleResolution": "NodeNext",
6
+ "outDir": "./dist",
7
+ "rootDir": "./src",
8
+ "strict": true,
9
+ "esModuleInterop": true,
10
+ "skipLibCheck": true,
11
+ "declaration": true
12
+ },
13
+ "include": ["src/**/*"]
14
+ }
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "{{name}}",
3
+ "version": "0.0.1",
4
+ "private": true,
5
+ "type": "module",
6
+ "scripts": {
7
+ "typecheck": "tsc --noEmit",
8
+ "sst:patch": "patch-sst-pulumi .",
9
+ "dev": "pnpm run sst:patch && sst dev",
10
+ "deploy": "pnpm run sst:patch && sst deploy",
11
+ "deploy:dev": "pnpm run sst:patch && sst deploy --stage dev",
12
+ "deploy:prod": "pnpm run sst:patch && sst deploy --stage prod",
13
+ "remove": "sst remove"
14
+ },
15
+ "dependencies": {
16
+ "@mesh-tech/infra-components": "workspace:*",
17
+ "@pulumi/aws": "6.66.2",
18
+ "@pulumi/docker-build": "^0.0.15",
19
+ "@pulumi/eks": "4.2.0",
20
+ "@pulumi/kubernetes": "4.24.1",
21
+ "@pulumi/pulumi": "3.214.1",
22
+ "@pulumi/random": "4.18.2",
23
+ "sst": "^3.3.61"
24
+ },
25
+ "devDependencies": {
26
+ "@aws-sdk/credential-providers": "^3.964.0",
27
+ "typescript": "^5.7.2"
28
+ }
29
+ }
@@ -0,0 +1,161 @@
1
+ /// <reference path="./.sst/platform/config.d.ts" />
2
+
3
+ /**
4
+ * {{titleCase name}} - Temporal Workflow Application
5
+ *
6
+ * This app demonstrates Temporal workflow orchestration with:
7
+ * - AppEnvironment with temporal: true (namespace provisioning, encoding key)
8
+ * - TemporalWorker component (auto-configured env vars, no Service/Ingress)
9
+ * - API Service for triggering workflows
10
+ *
11
+ * Dev mode:
12
+ * 1. Run: pnpm mesh dev (auto-starts tunnel to Temporal + DB)
13
+ * 2. Start worker manually in SST dev UI
14
+ *
15
+ * Deploy:
16
+ * pnpm deploy --stage dev
17
+ */
18
+
19
+ export default $config({
20
+ app(input) {
21
+ return {
22
+ name: "{{name}}",
23
+ removal: input?.stage === "prod" ? "retain" : "remove",
24
+ protect: ["prod"].includes(input?.stage ?? ""),
25
+ home: "aws",
26
+ providers: {
27
+ aws: {
28
+ region: "us-east-2",
29
+ },
30
+ },
31
+ };
32
+ },
33
+ async run() {
34
+ // Dynamic imports (SST requirement for non-SST packages)
35
+ const { mesh } = await import("@mesh-tech/infra-components");
36
+ const { config, getPlatform } = await import("../config.js");
37
+
38
+ const stage = $app.stage;
39
+ const tenant = config.tenant;
40
+ const platform = getPlatform(stage);
41
+
42
+ // =========================================================================
43
+ // AppEnvironment with Temporal enabled
44
+ // =========================================================================
45
+ // This creates:
46
+ // - Kubernetes namespace (deploy mode)
47
+ // - Temporal namespace (via TemporalNamespaceProvisioner Lambda)
48
+ // - Encoding key K8s Secret for payload encryption
49
+ // - Codec server deployment (for Web UI decryption)
50
+ // - Unified dev tunnel (mesh tunnel dev) for Temporal + DB access
51
+
52
+ const env = new mesh.apps.AppEnvironment("env", {
53
+ tenant,
54
+ platform,
55
+ stage,
56
+ appName: "{{name}}",
57
+ dev: $dev,
58
+ namespace: !$dev,
59
+
60
+ // Enable Temporal support
61
+ temporal: true,
62
+ DevCommand: sst.x.DevCommand,
63
+ });
64
+
65
+ // =========================================================================
66
+ // Temporal Worker
67
+ // =========================================================================
68
+ // TemporalWorker automatically handles:
69
+ // - Temporal environment variables (ADDRESS, NAMESPACE, TASK_QUEUE)
70
+ // - Encoding key secret mounting for payload encryption
71
+ // - No Service/Ingress (workers don't serve HTTP)
72
+ // - Dev mode: doesn't autostart (waits for namespace provisioning)
73
+
74
+ const worker = new mesh.apps.TemporalWorker(
75
+ "worker",
76
+ {
77
+ env,
78
+ rootDir: $cli.paths.root,
79
+ src: "./worker",
80
+ devCommand: ["pnpm", "dev"],
81
+
82
+ // Task queue defaults to worker name ("worker")
83
+ taskQueue: "{{name}}",
84
+
85
+ environment: {
86
+ NODE_ENV: $dev ? "development" : "production",
87
+ },
88
+
89
+ replicas: 1,
90
+ resources: {
91
+ cpu: "100m",
92
+ memory: "256Mi",
93
+ },
94
+ },
95
+ {
96
+ providers: env.providers,
97
+ parent: env,
98
+ },
99
+ );
100
+
101
+ // =========================================================================
102
+ // API Service (triggers workflows)
103
+ // =========================================================================
104
+ const api = new mesh.apps.Service(
105
+ "api",
106
+ {
107
+ dev: $dev,
108
+ tenant,
109
+ stage,
110
+ appName: "{{name}}",
111
+ namespace: env.namespace!,
112
+ rootDir: $cli.paths.root,
113
+ src: "./api",
114
+ port: 3000,
115
+ devCommand: ["pnpm", "dev"],
116
+
117
+ environment: {
118
+ NODE_ENV: $dev ? "development" : "production",
119
+ TEMPORAL_ADDRESS: env.temporal!.endpoint.apply((e) =>
120
+ $dev ? "localhost:7233" : `${e}:7233`,
121
+ ),
122
+ TEMPORAL_NAMESPACE: env.temporal!.namespace,
123
+ TEMPORAL_TASK_QUEUE: "{{name}}",
124
+ // Dev mode: inject encoding key directly
125
+ ...($dev && env.temporal!.devEncodingKey
126
+ ? { TEMPORAL_ENCODING_KEY: env.temporal!.devEncodingKey }
127
+ : {}),
128
+ },
129
+
130
+ // Deploy mode: inject encoding key from K8s secret
131
+ envFromSecrets: $dev ? [] : [env.temporal!.encodingKeySecretName],
132
+
133
+ ingress: env.buildIngress({
134
+ subdomain: "{{name}}-api",
135
+ healthCheckPath: "/health",
136
+ }),
137
+
138
+ replicas: 1,
139
+ resources: {
140
+ cpu: "100m",
141
+ memory: "128Mi",
142
+ },
143
+ },
144
+ {
145
+ providers: env.providers,
146
+ parent: env,
147
+ dependsOn: [worker],
148
+ },
149
+ );
150
+
151
+ return {
152
+ namespace: env.namespace,
153
+ temporalNamespace: env.temporal?.namespace,
154
+ temporalEndpoint: env.temporal?.endpoint,
155
+ encodingKeySecret: env.temporal?.encodingKeySecretName,
156
+ codecServerUrl: env.temporal?.codecServerUrl,
157
+ workerTaskQueue: worker.taskQueue,
158
+ apiUrl: api.url,
159
+ };
160
+ },
161
+ });
@@ -0,0 +1,10 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "NodeNext",
5
+ "moduleResolution": "NodeNext",
6
+ "strict": true,
7
+ "esModuleInterop": true,
8
+ "skipLibCheck": true
9
+ }
10
+ }
@@ -0,0 +1,4 @@
1
+ node_modules
2
+ dist
3
+ *.log
4
+ .env*
@@ -0,0 +1,11 @@
1
+ FROM node:20-slim
2
+
3
+ WORKDIR /app
4
+
5
+ COPY package*.json ./
6
+ RUN npm ci --omit=dev
7
+
8
+ COPY . .
9
+ RUN npm run build
10
+
11
+ CMD ["node", "dist/worker.js"]
@@ -0,0 +1,25 @@
1
+ {
2
+ "name": "@{{tenant}}/{{name}}-worker",
3
+ "version": "0.0.1",
4
+ "private": true,
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "tsx watch src/worker.ts",
8
+ "start": "node dist/worker.js",
9
+ "build": "tsc && npm run bundle:workflows",
10
+ "bundle:workflows": "tsx src/bundle-workflows.ts"
11
+ },
12
+ "dependencies": {
13
+ "@temporalio/activity": "^1.11.7",
14
+ "@temporalio/client": "^1.11.7",
15
+ "@temporalio/common": "^1.11.7",
16
+ "@temporalio/proto": "^1.11.7",
17
+ "@temporalio/worker": "^1.11.7",
18
+ "@temporalio/workflow": "^1.11.7"
19
+ },
20
+ "devDependencies": {
21
+ "@types/node": "^20.0.0",
22
+ "tsx": "^4.0.0",
23
+ "typescript": "^5.0.0"
24
+ }
25
+ }
@@ -0,0 +1,49 @@
1
+ /**
2
+ * {{titleCase name}} Activities
3
+ *
4
+ * Activities are the building blocks of workflows. They handle
5
+ * non-deterministic operations like API calls, database queries,
6
+ * and external service interactions.
7
+ *
8
+ * Activities can:
9
+ * - Make HTTP requests
10
+ * - Query databases
11
+ * - Send emails/notifications
12
+ * - Access the file system
13
+ * - Use Date.now(), Math.random(), etc.
14
+ */
15
+
16
+ /**
17
+ * Generate a greeting message
18
+ */
19
+ export async function greet(name: string): Promise<string> {
20
+ console.log(`[Activity] Generating greeting for ${name}`);
21
+ return `Hello, ${name}!`;
22
+ }
23
+
24
+ /**
25
+ * Process a task (simulated)
26
+ */
27
+ export async function processTask(taskId: string): Promise<string> {
28
+ console.log(`[Activity] Processing task ${taskId}`);
29
+
30
+ // Simulate processing time
31
+ await new Promise((resolve) => setTimeout(resolve, 1000));
32
+
33
+ return "completed";
34
+ }
35
+
36
+ /**
37
+ * Send a notification (simulated)
38
+ */
39
+ export async function sendNotification(
40
+ userId: string,
41
+ message: string
42
+ ): Promise<boolean> {
43
+ console.log(`[Activity] Sending notification to ${userId}: ${message}`);
44
+
45
+ // Simulate sending
46
+ await new Promise((resolve) => setTimeout(resolve, 500));
47
+
48
+ return true;
49
+ }
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Bundle Workflows for Production
3
+ *
4
+ * Pre-bundles workflows to avoid runtime bundling in production.
5
+ * Run with: npm run bundle:workflows
6
+ */
7
+
8
+ import * as fs from "fs";
9
+ import * as path from "path";
10
+ import { fileURLToPath } from "url";
11
+ import { bundleWorkflowCode } from "@temporalio/worker";
12
+
13
+ const __filename = fileURLToPath(import.meta.url);
14
+ const __dirname = path.dirname(__filename);
15
+
16
+ async function bundle() {
17
+ console.log("[Bundle] Bundling workflows...");
18
+
19
+ const bundle = await bundleWorkflowCode({
20
+ workflowsPath: path.join(__dirname, "workflows.ts"),
21
+ });
22
+
23
+ const outPath = path.join(__dirname, "..", "dist", "workflows-bundle.js");
24
+
25
+ // Ensure dist directory exists
26
+ const distDir = path.dirname(outPath);
27
+ if (!fs.existsSync(distDir)) {
28
+ fs.mkdirSync(distDir, { recursive: true });
29
+ }
30
+
31
+ fs.writeFileSync(outPath, bundle.code);
32
+ console.log(`[Bundle] Wrote bundle to ${outPath}`);
33
+ }
34
+
35
+ bundle().catch((err) => {
36
+ console.error("[Bundle] Failed:", err);
37
+ process.exit(1);
38
+ });