@empire-builder-kit/containers 0.0.1-alpha.10

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 (88) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +20 -0
  3. package/dist/executors.json +3 -0
  4. package/dist/generators/service/files-go/Dockerfile.template +11 -0
  5. package/dist/generators/service/files-go/README.md.template +11 -0
  6. package/dist/generators/service/files-go/cmd/main.go.template +34 -0
  7. package/dist/generators/service/files-go/docs/contracts.md.template +9 -0
  8. package/dist/generators/service/files-go/go.mod.template +3 -0
  9. package/dist/generators/service/files-go/infra/service.ts.template +59 -0
  10. package/dist/generators/service/files-python/Dockerfile.template +12 -0
  11. package/dist/generators/service/files-python/README.md.template +11 -0
  12. package/dist/generators/service/files-python/docs/contracts.md.template +9 -0
  13. package/dist/generators/service/files-python/infra/service.ts.template +68 -0
  14. package/dist/generators/service/files-python/pyproject.toml.template +15 -0
  15. package/dist/generators/service/files-python/src/main.py.template +20 -0
  16. package/dist/generators/service/files-rust/Cargo.toml.template +16 -0
  17. package/dist/generators/service/files-rust/Dockerfile.template +12 -0
  18. package/dist/generators/service/files-rust/README.md.template +11 -0
  19. package/dist/generators/service/files-rust/docs/contracts.md.template +9 -0
  20. package/dist/generators/service/files-rust/infra/service.ts.template +59 -0
  21. package/dist/generators/service/files-rust/src/main.rs.template +29 -0
  22. package/dist/generators/service/files-typescript/Dockerfile.template +30 -0
  23. package/dist/generators/service/files-typescript/README.md.template +24 -0
  24. package/dist/generators/service/files-typescript/docs/architecture.md.template +18 -0
  25. package/dist/generators/service/files-typescript/docs/contracts.md.template +20 -0
  26. package/dist/generators/service/files-typescript/docs/operations.md.template +24 -0
  27. package/dist/generators/service/files-typescript/eslint.config.mjs.template +3 -0
  28. package/dist/generators/service/files-typescript/infra/service.ts.template +64 -0
  29. package/dist/generators/service/files-typescript/package.json.template +7 -0
  30. package/dist/generators/service/files-typescript/src/index.ts.template +2 -0
  31. package/dist/generators/service/files-typescript/src/main.ts.template +60 -0
  32. package/dist/generators/service/files-typescript/src/service.spec.ts.template +49 -0
  33. package/dist/generators/service/files-typescript/src/service.ts.template +57 -0
  34. package/dist/generators/service/files-typescript/tsconfig.app.json.template +12 -0
  35. package/dist/generators/service/files-typescript/tsconfig.json.template +7 -0
  36. package/dist/generators/service/files-typescript/tsconfig.spec.json.template +7 -0
  37. package/dist/generators/service/files-typescript/vitest.config.mts.template +17 -0
  38. package/dist/generators/service/schema.d.ts +8 -0
  39. package/dist/generators/service/schema.json +40 -0
  40. package/dist/generators/service/service.d.ts +5 -0
  41. package/dist/generators/service/service.d.ts.map +1 -0
  42. package/dist/generators/service/service.js +198 -0
  43. package/dist/generators/task/files-go/Dockerfile.template +10 -0
  44. package/dist/generators/task/files-go/README.md.template +11 -0
  45. package/dist/generators/task/files-go/cmd/main.go.template +22 -0
  46. package/dist/generators/task/files-go/docs/contracts.md.template +9 -0
  47. package/dist/generators/task/files-go/go.mod.template +3 -0
  48. package/dist/generators/task/files-go/infra/task.ts.template +38 -0
  49. package/dist/generators/task/files-python/Dockerfile.template +10 -0
  50. package/dist/generators/task/files-python/README.md.template +11 -0
  51. package/dist/generators/task/files-python/docs/contracts.md.template +9 -0
  52. package/dist/generators/task/files-python/infra/task.ts.template +38 -0
  53. package/dist/generators/task/files-python/pyproject.toml.template +12 -0
  54. package/dist/generators/task/files-python/src/main.py.template +26 -0
  55. package/dist/generators/task/files-rust/Cargo.toml.template +15 -0
  56. package/dist/generators/task/files-rust/Dockerfile.template +11 -0
  57. package/dist/generators/task/files-rust/README.md.template +11 -0
  58. package/dist/generators/task/files-rust/docs/contracts.md.template +9 -0
  59. package/dist/generators/task/files-rust/infra/task.ts.template +38 -0
  60. package/dist/generators/task/files-rust/src/main.rs.template +12 -0
  61. package/dist/generators/task/files-typescript/Dockerfile.template +25 -0
  62. package/dist/generators/task/files-typescript/README.md.template +24 -0
  63. package/dist/generators/task/files-typescript/docs/architecture.md.template +18 -0
  64. package/dist/generators/task/files-typescript/docs/contracts.md.template +20 -0
  65. package/dist/generators/task/files-typescript/docs/operations.md.template +24 -0
  66. package/dist/generators/task/files-typescript/eslint.config.mjs.template +3 -0
  67. package/dist/generators/task/files-typescript/infra/task.ts.template +35 -0
  68. package/dist/generators/task/files-typescript/package.json.template +7 -0
  69. package/dist/generators/task/files-typescript/src/index.ts.template +6 -0
  70. package/dist/generators/task/files-typescript/src/main.ts.template +45 -0
  71. package/dist/generators/task/files-typescript/src/task.spec.ts.template +66 -0
  72. package/dist/generators/task/files-typescript/src/task.ts.template +71 -0
  73. package/dist/generators/task/files-typescript/tsconfig.app.json.template +13 -0
  74. package/dist/generators/task/files-typescript/tsconfig.json.template +19 -0
  75. package/dist/generators/task/files-typescript/tsconfig.spec.json.template +11 -0
  76. package/dist/generators/task/files-typescript/vitest.config.mts.template +9 -0
  77. package/dist/generators/task/schema.d.ts +8 -0
  78. package/dist/generators/task/schema.json +40 -0
  79. package/dist/generators/task/task.d.ts +5 -0
  80. package/dist/generators/task/task.d.ts.map +1 -0
  81. package/dist/generators/task/task.js +198 -0
  82. package/dist/generators.json +14 -0
  83. package/dist/index.d.ts +3 -0
  84. package/dist/index.d.ts.map +1 -0
  85. package/dist/index.js +7 -0
  86. package/executors.json +3 -0
  87. package/generators.json +14 -0
  88. package/package.json +93 -0
@@ -0,0 +1,7 @@
1
+ {
2
+ "name": "@<%= appName %>/service",
3
+ "private": true,
4
+ "version": "0.0.1",
5
+ "type": "module",
6
+ "main": "./dist/main.js"
7
+ }
@@ -0,0 +1,2 @@
1
+ export { createHealthResponse, formatLogEvent, readServiceConfig } from './service.js';
2
+ export type { HealthResponse, LogContext, ServiceConfig } from './service.js';
@@ -0,0 +1,60 @@
1
+ import { createServer } from 'node:http';
2
+ import { createLogger, createRequestContext } from '@empire-builder-kit/runtime';
3
+ import {
4
+ createHealthResponse,
5
+ readServiceConfig,
6
+ } from './service.js';
7
+
8
+ function readPort(env: NodeJS.ProcessEnv = process.env): number {
9
+ const value = env.PORT?.trim();
10
+
11
+ if (!value) {
12
+ return 3000;
13
+ }
14
+
15
+ const parsed = Number.parseInt(value, 10);
16
+
17
+ if (Number.isNaN(parsed) || parsed <= 0) {
18
+ throw new Error(`Invalid PORT value: ${value}`);
19
+ }
20
+
21
+ return parsed;
22
+ }
23
+
24
+ const config = readServiceConfig();
25
+ const port = readPort();
26
+ const logger = createLogger({ app: '<%= app %>', source: '<%= projectName %>' });
27
+
28
+ const server = createServer((request, response) => {
29
+ const method = request.method ?? 'GET';
30
+ const url = request.url ?? '/';
31
+ const ctx = createRequestContext({
32
+ app: '<%= app %>',
33
+ operation: `${method} ${url}`,
34
+ stage: process.env.SST_STAGE ?? process.env.EBK_STAGE ?? 'development',
35
+ });
36
+
37
+ logger.info('request received', {
38
+ correlationId: ctx.correlationId,
39
+ method,
40
+ path: url,
41
+ });
42
+
43
+ if (method === 'GET' && url === '/health') {
44
+ response.writeHead(200, { 'content-type': 'application/json; charset=utf-8' });
45
+ response.end(JSON.stringify(createHealthResponse(config)));
46
+ return;
47
+ }
48
+
49
+ response.writeHead(404, { 'content-type': 'application/json; charset=utf-8' });
50
+ response.end(
51
+ JSON.stringify({
52
+ error: 'Not Found',
53
+ path: url,
54
+ })
55
+ );
56
+ });
57
+
58
+ server.listen(port, () => {
59
+ logger.info('service listening', { port });
60
+ });
@@ -0,0 +1,49 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import {
3
+ createHealthResponse,
4
+ formatLogEvent,
5
+ readServiceConfig,
6
+ } from './service.js';
7
+
8
+ describe('service helpers', () => {
9
+ it('falls back to service defaults', () => {
10
+ expect(readServiceConfig({})).toEqual({
11
+ serviceName: '<%= className %>Service',
12
+ version: '0.0.1',
13
+ });
14
+ });
15
+
16
+ it('builds a stable health payload', () => {
17
+ expect(
18
+ createHealthResponse({
19
+ serviceName: '<%= className %>Service',
20
+ version: '1.2.3',
21
+ })
22
+ ).toEqual({
23
+ service: '<%= className %>Service',
24
+ status: 'ok',
25
+ version: '1.2.3',
26
+ });
27
+ });
28
+
29
+ it('formats structured log output', () => {
30
+ expect(
31
+ formatLogEvent(
32
+ {
33
+ serviceName: '<%= className %>Service',
34
+ version: '1.2.3',
35
+ },
36
+ 'service.started',
37
+ { port: 3000 }
38
+ )
39
+ ).toBe(
40
+ JSON.stringify({
41
+ level: 'info',
42
+ message: 'service.started',
43
+ service: '<%= className %>Service',
44
+ version: '1.2.3',
45
+ port: 3000,
46
+ })
47
+ );
48
+ });
49
+ });
@@ -0,0 +1,57 @@
1
+ export interface ServiceConfig {
2
+ serviceName: string;
3
+ version: string;
4
+ }
5
+
6
+ export interface HealthResponse {
7
+ service: string;
8
+ status: 'ok';
9
+ version: string;
10
+ }
11
+
12
+ export interface LogContext {
13
+ [key: string]: string | number | boolean | null;
14
+ }
15
+
16
+ const DEFAULT_SERVICE_NAME = '<%= className %>Service';
17
+ const DEFAULT_SERVICE_VERSION = '0.0.1';
18
+
19
+ function readOptionalEnv(
20
+ env: NodeJS.ProcessEnv,
21
+ key: string,
22
+ fallback: string
23
+ ): string {
24
+ const value = env[key]?.trim();
25
+ return value && value.length > 0 ? value : fallback;
26
+ }
27
+
28
+ export function readServiceConfig(
29
+ env: NodeJS.ProcessEnv = process.env
30
+ ): ServiceConfig {
31
+ return {
32
+ serviceName: readOptionalEnv(env, 'SERVICE_NAME', DEFAULT_SERVICE_NAME),
33
+ version: readOptionalEnv(env, 'SERVICE_VERSION', DEFAULT_SERVICE_VERSION),
34
+ };
35
+ }
36
+
37
+ export function createHealthResponse(config: ServiceConfig): HealthResponse {
38
+ return {
39
+ service: config.serviceName,
40
+ status: 'ok',
41
+ version: config.version,
42
+ };
43
+ }
44
+
45
+ export function formatLogEvent(
46
+ config: ServiceConfig,
47
+ message: string,
48
+ context: LogContext = {}
49
+ ): string {
50
+ return JSON.stringify({
51
+ level: 'info',
52
+ message,
53
+ service: config.serviceName,
54
+ version: config.version,
55
+ ...context,
56
+ });
57
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "compilerOptions": {
4
+ "rootDir": "./src",
5
+ "outDir": "<%= offsetFromRoot %>dist/<%= projectRoot %>",
6
+ "declaration": false,
7
+ "declarationMap": false,
8
+ "emitDeclarationOnly": false
9
+ },
10
+ "include": ["src/**/*.ts"],
11
+ "exclude": ["src/**/*.spec.ts"]
12
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "extends": "<%= offsetFromRoot %>tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "types": ["node"]
5
+ },
6
+ "include": ["src/**/*.ts", "vitest.config.mts"]
7
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "compilerOptions": {
4
+ "types": ["node", "vitest/globals"]
5
+ },
6
+ "include": ["src/**/*.spec.ts", "src/**/*.test.ts", "vitest.config.mts"]
7
+ }
@@ -0,0 +1,17 @@
1
+ import { defineConfig } from 'vitest/config';
2
+
3
+ export default defineConfig({
4
+ root: __dirname,
5
+ cacheDir: '<%= offsetFromRoot %>node_modules/.vite/<%= projectRoot %>',
6
+ test: {
7
+ name: '<%= projectName %>',
8
+ watch: false,
9
+ environment: 'node',
10
+ include: ['src/**/*.{test,spec}.ts'],
11
+ reporters: ['default'],
12
+ coverage: {
13
+ provider: 'v8',
14
+ reportsDirectory: './test-output/vitest/coverage',
15
+ },
16
+ },
17
+ });
@@ -0,0 +1,8 @@
1
+ export interface ServiceGeneratorSchema {
2
+ name: string;
3
+ app: string;
4
+ language?: 'typescript' | 'rust' | 'go' | 'python';
5
+ directory?: string;
6
+ skipFormat?: boolean;
7
+ tags?: string;
8
+ }
@@ -0,0 +1,40 @@
1
+ {
2
+ "$schema": "https://json-schema.org/schema",
3
+ "$id": "Service",
4
+ "title": "Generate an EBK container service",
5
+ "type": "object",
6
+ "properties": {
7
+ "name": {
8
+ "type": "string",
9
+ "description": "Name for the service within the app group.",
10
+ "$default": { "$source": "argv", "index": 0 },
11
+ "x-prompt": "What name should the service have?"
12
+ },
13
+ "app": {
14
+ "type": "string",
15
+ "description": "Owning Empire Builder Kit app-group name.",
16
+ "x-prompt": "Which app group should receive the service?"
17
+ },
18
+ "language": {
19
+ "type": "string",
20
+ "description": "Programming language for the service.",
21
+ "enum": ["typescript", "rust", "go", "python"],
22
+ "default": "typescript"
23
+ },
24
+ "directory": {
25
+ "type": "string",
26
+ "description": "Base directory for app groups.",
27
+ "default": "packages"
28
+ },
29
+ "tags": {
30
+ "type": "string",
31
+ "description": "Comma-separated additional tags to apply to the project."
32
+ },
33
+ "skipFormat": {
34
+ "type": "boolean",
35
+ "description": "Skip formatting files.",
36
+ "default": false
37
+ }
38
+ },
39
+ "required": ["name", "app"]
40
+ }
@@ -0,0 +1,5 @@
1
+ import { Tree } from '@nx/devkit';
2
+ import { ServiceGeneratorSchema } from './schema';
3
+ export declare function serviceGenerator(tree: Tree, options: ServiceGeneratorSchema): Promise<void>;
4
+ export default serviceGenerator;
5
+ //# sourceMappingURL=service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../../src/generators/service/service.ts"],"names":[],"mappings":"AAAA,OAAO,EAQL,IAAI,EACL,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,sBAAsB,EAAE,MAAM,UAAU,CAAC;AA+MlD,wBAAsB,gBAAgB,CACpC,IAAI,EAAE,IAAI,EACV,OAAO,EAAE,sBAAsB,iBA8ChC;AAED,eAAe,gBAAgB,CAAC"}
@@ -0,0 +1,198 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.serviceGenerator = serviceGenerator;
4
+ const tslib_1 = require("tslib");
5
+ const devkit_1 = require("@nx/devkit");
6
+ const path = tslib_1.__importStar(require("node:path"));
7
+ function runtimeForLanguage(language) {
8
+ switch (language) {
9
+ case 'typescript':
10
+ return 'node';
11
+ case 'rust':
12
+ return 'native';
13
+ case 'go':
14
+ return 'native';
15
+ case 'python':
16
+ return 'python';
17
+ }
18
+ }
19
+ function targetsForLanguage(language, projectRoot, projectName, outputPath) {
20
+ const cwd = projectRoot;
21
+ switch (language) {
22
+ case 'typescript':
23
+ return {
24
+ build: {
25
+ executor: 'nx:run-commands',
26
+ outputs: [`{workspaceRoot}/${outputPath}`],
27
+ options: { cwd, command: 'tsc -p tsconfig.app.json' },
28
+ },
29
+ lint: {
30
+ executor: 'nx:run-commands',
31
+ options: { cwd, command: 'eslint .' },
32
+ },
33
+ typecheck: {
34
+ executor: 'nx:run-commands',
35
+ options: { cwd, command: 'tsc --noEmit -p tsconfig.json' },
36
+ },
37
+ test: {
38
+ executor: 'nx:run-commands',
39
+ options: {
40
+ cwd,
41
+ command: 'vitest run --config vitest.config.mts',
42
+ },
43
+ },
44
+ dev: {
45
+ executor: 'nx:run-commands',
46
+ options: {
47
+ cwd,
48
+ command: 'node --watch --enable-source-maps --import @swc-node/register/esm-register src/main.ts',
49
+ },
50
+ },
51
+ 'docker-build': {
52
+ executor: 'nx:run-commands',
53
+ options: {
54
+ command: `docker build --tag ${projectName} ${projectRoot}`,
55
+ },
56
+ },
57
+ };
58
+ case 'rust':
59
+ return {
60
+ build: {
61
+ executor: 'nx:run-commands',
62
+ options: { cwd, command: 'cargo build --release' },
63
+ },
64
+ lint: {
65
+ executor: 'nx:run-commands',
66
+ options: { cwd, command: 'cargo clippy -- -D warnings' },
67
+ },
68
+ test: {
69
+ executor: 'nx:run-commands',
70
+ options: { cwd, command: 'cargo test' },
71
+ },
72
+ dev: {
73
+ executor: 'nx:run-commands',
74
+ options: { cwd, command: 'cargo watch -x run' },
75
+ },
76
+ 'docker-build': {
77
+ executor: 'nx:run-commands',
78
+ options: {
79
+ command: `docker build --tag ${projectName} ${projectRoot}`,
80
+ },
81
+ },
82
+ };
83
+ case 'go':
84
+ return {
85
+ build: {
86
+ executor: 'nx:run-commands',
87
+ options: { cwd, command: 'go build -o bin/service ./cmd/...' },
88
+ },
89
+ lint: {
90
+ executor: 'nx:run-commands',
91
+ options: { cwd, command: 'golangci-lint run' },
92
+ },
93
+ typecheck: {
94
+ executor: 'nx:run-commands',
95
+ options: { cwd, command: 'go vet ./...' },
96
+ },
97
+ test: {
98
+ executor: 'nx:run-commands',
99
+ options: { cwd, command: 'go test ./...' },
100
+ },
101
+ dev: {
102
+ executor: 'nx:run-commands',
103
+ options: { cwd, command: 'go run ./cmd/...' },
104
+ },
105
+ 'docker-build': {
106
+ executor: 'nx:run-commands',
107
+ options: {
108
+ command: `docker build --tag ${projectName} ${projectRoot}`,
109
+ },
110
+ },
111
+ };
112
+ case 'python':
113
+ return {
114
+ lint: {
115
+ executor: 'nx:run-commands',
116
+ options: { cwd, command: 'ruff check .' },
117
+ },
118
+ typecheck: {
119
+ executor: 'nx:run-commands',
120
+ options: { cwd, command: 'mypy src/' },
121
+ },
122
+ test: {
123
+ executor: 'nx:run-commands',
124
+ options: { cwd, command: 'pytest' },
125
+ },
126
+ dev: {
127
+ executor: 'nx:run-commands',
128
+ options: { cwd, command: 'uvicorn src.main:app --reload' },
129
+ },
130
+ 'docker-build': {
131
+ executor: 'nx:run-commands',
132
+ options: {
133
+ command: `docker build --tag ${projectName} ${projectRoot}`,
134
+ },
135
+ },
136
+ };
137
+ }
138
+ }
139
+ function normalizeOptions(options) {
140
+ const appNames = (0, devkit_1.names)(options.app);
141
+ const serviceNames = (0, devkit_1.names)(options.name);
142
+ const language = options.language ?? 'typescript';
143
+ const directory = (options.directory ?? 'packages').replace(/\\/g, '/');
144
+ const projectRoot = (0, devkit_1.joinPathFragments)(directory, appNames.fileName, 'apps', 'service', serviceNames.fileName);
145
+ const userTags = options.tags
146
+ ?.split(',')
147
+ .map((tag) => tag.trim())
148
+ .filter(Boolean) ?? [];
149
+ return {
150
+ app: appNames.fileName,
151
+ appName: appNames.fileName,
152
+ className: appNames.className,
153
+ directory,
154
+ language,
155
+ name: serviceNames.fileName,
156
+ offsetFromRoot: (0, devkit_1.offsetFromRoot)(projectRoot),
157
+ outputPath: (0, devkit_1.joinPathFragments)('dist', projectRoot),
158
+ projectName: `${appNames.fileName}-service-${serviceNames.fileName}`,
159
+ projectRoot,
160
+ projectTitle: `${appNames.className} Service ${serviceNames.className}`,
161
+ skipFormat: options.skipFormat,
162
+ tags: [
163
+ `scope:${appNames.fileName}`,
164
+ 'layer:app',
165
+ 'type:service',
166
+ `runtime:${runtimeForLanguage(language)}`,
167
+ 'deploy:container',
168
+ 'visibility:internal',
169
+ ...userTags,
170
+ ],
171
+ };
172
+ }
173
+ async function serviceGenerator(tree, options) {
174
+ const normalized = normalizeOptions(options);
175
+ const appRoot = (0, devkit_1.joinPathFragments)(normalized.directory, normalized.appName);
176
+ if (!tree.exists(appRoot)) {
177
+ throw new Error(`App group "${normalized.app}" does not exist at "${appRoot}". Generate the app group first.`);
178
+ }
179
+ if (tree.exists((0, devkit_1.joinPathFragments)(normalized.projectRoot, 'project.json')) ||
180
+ tree.exists((0, devkit_1.joinPathFragments)(normalized.projectRoot, 'package.json'))) {
181
+ throw new Error(`Service "${normalized.name}" already exists at "${normalized.projectRoot}".`);
182
+ }
183
+ (0, devkit_1.addProjectConfiguration)(tree, normalized.projectName, {
184
+ root: normalized.projectRoot,
185
+ projectType: 'application',
186
+ sourceRoot: `${normalized.projectRoot}/src`,
187
+ tags: normalized.tags,
188
+ targets: targetsForLanguage(normalized.language, normalized.projectRoot, normalized.projectName, normalized.outputPath),
189
+ });
190
+ (0, devkit_1.generateFiles)(tree, path.join(__dirname, `files-${normalized.language}`), normalized.projectRoot, {
191
+ ...normalized,
192
+ tmpl: '',
193
+ });
194
+ if (!normalized.skipFormat) {
195
+ await (0, devkit_1.formatFiles)(tree);
196
+ }
197
+ }
198
+ exports.default = serviceGenerator;
@@ -0,0 +1,10 @@
1
+ FROM golang:1-bookworm AS builder
2
+ WORKDIR /app
3
+ COPY go.mod go.sum* ./
4
+ RUN go mod download
5
+ COPY . .
6
+ RUN CGO_ENABLED=0 go build -o /task ./cmd/...
7
+
8
+ FROM gcr.io/distroless/static-debian12
9
+ COPY --from=builder /task /task
10
+ CMD ["/task"]
@@ -0,0 +1,11 @@
1
+ # <%= projectTitle %>
2
+
3
+ Go task for the `<%= app %>` app group.
4
+
5
+ ## Commands
6
+
7
+ - `pnpm nx run <%= projectName %>:build` — compile binary
8
+ - `pnpm nx run <%= projectName %>:dev` — run locally
9
+ - `pnpm nx run <%= projectName %>:test` — run tests
10
+ - `pnpm nx run <%= projectName %>:lint` — run golangci-lint
11
+ - `pnpm nx run <%= projectName %>:docker-build` — build container image
@@ -0,0 +1,22 @@
1
+ package main
2
+
3
+ import (
4
+ "log/slog"
5
+ "os"
6
+ )
7
+
8
+ func main() {
9
+ logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
10
+ slog.SetDefault(logger)
11
+
12
+ trigger := os.Getenv("TASK_TRIGGER")
13
+ if trigger == "" {
14
+ trigger = "manual"
15
+ }
16
+
17
+ slog.Info("task started", "trigger", trigger, "task", "<%= projectName %>")
18
+
19
+ // Task logic goes here
20
+
21
+ slog.Info("task completed successfully")
22
+ }
@@ -0,0 +1,9 @@
1
+ # <%= projectTitle %> Contracts
2
+
3
+ ## Input
4
+
5
+ - `TASK_TRIGGER` env var controls the task trigger type (default: `manual`)
6
+
7
+ ## Local Development
8
+
9
+ - `pnpm nx run <%= projectName %>:dev`
@@ -0,0 +1,3 @@
1
+ module github.com/org/<%= projectName %>
2
+
3
+ go 1.22
@@ -0,0 +1,38 @@
1
+ export interface Register<%= className %>TaskArgs {
2
+ cluster: sst.aws.Cluster;
3
+ environment?: Record<string, string>;
4
+ link?: any[];
5
+ }
6
+
7
+ // ECS task (not a long-running service) — runs once per trigger, no health
8
+ // endpoint required. Runtime-agnostic SST wrapper; the Dockerfile decides
9
+ // whether the workload is Rust, Go, Python, or Node.
10
+ export function register<%= className %>Task({
11
+ cluster,
12
+ environment = {},
13
+ link = [],
14
+ }: Register<%= className %>TaskArgs) {
15
+ const task = new sst.aws.Task('<%= projectName %>', {
16
+ cluster,
17
+ image: {
18
+ context: './apps/task/<%= name %>',
19
+ dockerfile: 'Dockerfile',
20
+ },
21
+ dev: {
22
+ command: 'pnpm nx run <%= projectName %>:dev',
23
+ directory: '../../..',
24
+ },
25
+ environment: {
26
+ TASK_NAME: '<%= className %>Task',
27
+ TASK_VERSION: '0.0.1',
28
+ TASK_TRIGGER: 'manual',
29
+ ...environment,
30
+ },
31
+ link,
32
+ logging: {
33
+ retention: '1 month',
34
+ },
35
+ });
36
+
37
+ return { task };
38
+ }
@@ -0,0 +1,10 @@
1
+ FROM python:3.12-slim AS builder
2
+ WORKDIR /app
3
+ COPY pyproject.toml ./
4
+ RUN pip install --no-cache-dir .
5
+
6
+ FROM python:3.12-slim
7
+ WORKDIR /app
8
+ COPY --from=builder /usr/local/lib/python3.12/site-packages /usr/local/lib/python3.12/site-packages
9
+ COPY src/ src/
10
+ CMD ["python", "src/main.py"]
@@ -0,0 +1,11 @@
1
+ # <%= projectTitle %>
2
+
3
+ Python task for the `<%= app %>` app group.
4
+
5
+ ## Commands
6
+
7
+ - `pnpm nx run <%= projectName %>:dev` — run locally
8
+ - `pnpm nx run <%= projectName %>:test` — run pytest
9
+ - `pnpm nx run <%= projectName %>:lint` — run ruff
10
+ - `pnpm nx run <%= projectName %>:typecheck` — run mypy
11
+ - `pnpm nx run <%= projectName %>:docker-build` — build container image
@@ -0,0 +1,9 @@
1
+ # <%= projectTitle %> Contracts
2
+
3
+ ## Input
4
+
5
+ - `TASK_TRIGGER` env var controls the task trigger type (default: `manual`)
6
+
7
+ ## Local Development
8
+
9
+ - `pnpm nx run <%= projectName %>:dev`
@@ -0,0 +1,38 @@
1
+ export interface Register<%= className %>TaskArgs {
2
+ cluster: sst.aws.Cluster;
3
+ environment?: Record<string, string>;
4
+ link?: any[];
5
+ }
6
+
7
+ // ECS task (not a long-running service) — runs once per trigger, no health
8
+ // endpoint required. Runtime-agnostic SST wrapper; the Dockerfile decides
9
+ // whether the workload is Rust, Go, Python, or Node.
10
+ export function register<%= className %>Task({
11
+ cluster,
12
+ environment = {},
13
+ link = [],
14
+ }: Register<%= className %>TaskArgs) {
15
+ const task = new sst.aws.Task('<%= projectName %>', {
16
+ cluster,
17
+ image: {
18
+ context: './apps/task/<%= name %>',
19
+ dockerfile: 'Dockerfile',
20
+ },
21
+ dev: {
22
+ command: 'pnpm nx run <%= projectName %>:dev',
23
+ directory: '../../..',
24
+ },
25
+ environment: {
26
+ TASK_NAME: '<%= className %>Task',
27
+ TASK_VERSION: '0.0.1',
28
+ TASK_TRIGGER: 'manual',
29
+ ...environment,
30
+ },
31
+ link,
32
+ logging: {
33
+ retention: '1 month',
34
+ },
35
+ });
36
+
37
+ return { task };
38
+ }