@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.
- package/LICENSE +21 -0
- package/README.md +20 -0
- package/dist/executors.json +3 -0
- package/dist/generators/service/files-go/Dockerfile.template +11 -0
- package/dist/generators/service/files-go/README.md.template +11 -0
- package/dist/generators/service/files-go/cmd/main.go.template +34 -0
- package/dist/generators/service/files-go/docs/contracts.md.template +9 -0
- package/dist/generators/service/files-go/go.mod.template +3 -0
- package/dist/generators/service/files-go/infra/service.ts.template +59 -0
- package/dist/generators/service/files-python/Dockerfile.template +12 -0
- package/dist/generators/service/files-python/README.md.template +11 -0
- package/dist/generators/service/files-python/docs/contracts.md.template +9 -0
- package/dist/generators/service/files-python/infra/service.ts.template +68 -0
- package/dist/generators/service/files-python/pyproject.toml.template +15 -0
- package/dist/generators/service/files-python/src/main.py.template +20 -0
- package/dist/generators/service/files-rust/Cargo.toml.template +16 -0
- package/dist/generators/service/files-rust/Dockerfile.template +12 -0
- package/dist/generators/service/files-rust/README.md.template +11 -0
- package/dist/generators/service/files-rust/docs/contracts.md.template +9 -0
- package/dist/generators/service/files-rust/infra/service.ts.template +59 -0
- package/dist/generators/service/files-rust/src/main.rs.template +29 -0
- package/dist/generators/service/files-typescript/Dockerfile.template +30 -0
- package/dist/generators/service/files-typescript/README.md.template +24 -0
- package/dist/generators/service/files-typescript/docs/architecture.md.template +18 -0
- package/dist/generators/service/files-typescript/docs/contracts.md.template +20 -0
- package/dist/generators/service/files-typescript/docs/operations.md.template +24 -0
- package/dist/generators/service/files-typescript/eslint.config.mjs.template +3 -0
- package/dist/generators/service/files-typescript/infra/service.ts.template +64 -0
- package/dist/generators/service/files-typescript/package.json.template +7 -0
- package/dist/generators/service/files-typescript/src/index.ts.template +2 -0
- package/dist/generators/service/files-typescript/src/main.ts.template +60 -0
- package/dist/generators/service/files-typescript/src/service.spec.ts.template +49 -0
- package/dist/generators/service/files-typescript/src/service.ts.template +57 -0
- package/dist/generators/service/files-typescript/tsconfig.app.json.template +12 -0
- package/dist/generators/service/files-typescript/tsconfig.json.template +7 -0
- package/dist/generators/service/files-typescript/tsconfig.spec.json.template +7 -0
- package/dist/generators/service/files-typescript/vitest.config.mts.template +17 -0
- package/dist/generators/service/schema.d.ts +8 -0
- package/dist/generators/service/schema.json +40 -0
- package/dist/generators/service/service.d.ts +5 -0
- package/dist/generators/service/service.d.ts.map +1 -0
- package/dist/generators/service/service.js +198 -0
- package/dist/generators/task/files-go/Dockerfile.template +10 -0
- package/dist/generators/task/files-go/README.md.template +11 -0
- package/dist/generators/task/files-go/cmd/main.go.template +22 -0
- package/dist/generators/task/files-go/docs/contracts.md.template +9 -0
- package/dist/generators/task/files-go/go.mod.template +3 -0
- package/dist/generators/task/files-go/infra/task.ts.template +38 -0
- package/dist/generators/task/files-python/Dockerfile.template +10 -0
- package/dist/generators/task/files-python/README.md.template +11 -0
- package/dist/generators/task/files-python/docs/contracts.md.template +9 -0
- package/dist/generators/task/files-python/infra/task.ts.template +38 -0
- package/dist/generators/task/files-python/pyproject.toml.template +12 -0
- package/dist/generators/task/files-python/src/main.py.template +26 -0
- package/dist/generators/task/files-rust/Cargo.toml.template +15 -0
- package/dist/generators/task/files-rust/Dockerfile.template +11 -0
- package/dist/generators/task/files-rust/README.md.template +11 -0
- package/dist/generators/task/files-rust/docs/contracts.md.template +9 -0
- package/dist/generators/task/files-rust/infra/task.ts.template +38 -0
- package/dist/generators/task/files-rust/src/main.rs.template +12 -0
- package/dist/generators/task/files-typescript/Dockerfile.template +25 -0
- package/dist/generators/task/files-typescript/README.md.template +24 -0
- package/dist/generators/task/files-typescript/docs/architecture.md.template +18 -0
- package/dist/generators/task/files-typescript/docs/contracts.md.template +20 -0
- package/dist/generators/task/files-typescript/docs/operations.md.template +24 -0
- package/dist/generators/task/files-typescript/eslint.config.mjs.template +3 -0
- package/dist/generators/task/files-typescript/infra/task.ts.template +35 -0
- package/dist/generators/task/files-typescript/package.json.template +7 -0
- package/dist/generators/task/files-typescript/src/index.ts.template +6 -0
- package/dist/generators/task/files-typescript/src/main.ts.template +45 -0
- package/dist/generators/task/files-typescript/src/task.spec.ts.template +66 -0
- package/dist/generators/task/files-typescript/src/task.ts.template +71 -0
- package/dist/generators/task/files-typescript/tsconfig.app.json.template +13 -0
- package/dist/generators/task/files-typescript/tsconfig.json.template +19 -0
- package/dist/generators/task/files-typescript/tsconfig.spec.json.template +11 -0
- package/dist/generators/task/files-typescript/vitest.config.mts.template +9 -0
- package/dist/generators/task/schema.d.ts +8 -0
- package/dist/generators/task/schema.json +40 -0
- package/dist/generators/task/task.d.ts +5 -0
- package/dist/generators/task/task.d.ts.map +1 -0
- package/dist/generators/task/task.js +198 -0
- package/dist/generators.json +14 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/executors.json +3 -0
- package/generators.json +14 -0
- package/package.json +93 -0
|
@@ -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,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,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 @@
|
|
|
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,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,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,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
|
+
}
|