@appliance.sh/api-server 1.14.0 → 1.15.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/package.json +14 -44
- package/src/app.controller.spec.ts +10 -20
- package/src/main.ts +22 -4
- package/src/routes/index.ts +7 -0
- package/src/routes/infra/index.ts +24 -0
- package/src/{pulumi → services}/ApplianceStack.ts +37 -25
- package/src/{pulumi → services}/pulumi.service.ts +6 -6
- package/test/app.e2e-spec.ts +11 -15
- package/vitest.config.ts +8 -0
- package/vitest.e2e.config.ts +8 -0
- package/nest-cli.json +0 -8
- package/src/app.controller.ts +0 -12
- package/src/app.module.ts +0 -11
- package/src/app.service.ts +0 -8
- package/src/pulumi/pulumi.controller.ts +0 -19
- package/src/pulumi/pulumi.module.ts +0 -9
- package/test/jest-e2e.json +0 -9
package/package.json
CHANGED
|
@@ -1,73 +1,43 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@appliance.sh/api-server",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.15.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"author": "Eliot Lim",
|
|
6
6
|
"repository": "https://github.com/appliance-sh/appliance.sh",
|
|
7
7
|
"license": "MIT",
|
|
8
8
|
"scripts": {
|
|
9
|
-
"build": "
|
|
9
|
+
"build": "tsc",
|
|
10
10
|
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
|
|
11
|
-
"start": "
|
|
12
|
-
"start:dev": "
|
|
13
|
-
"start:debug": "
|
|
14
|
-
"start:prod": "node dist/main",
|
|
11
|
+
"start": "node dist/src/main.js",
|
|
12
|
+
"start:dev": "tsx watch src/main.ts",
|
|
13
|
+
"start:debug": "tsx watch --inspect src/main.ts",
|
|
14
|
+
"start:prod": "node dist/src/main.js",
|
|
15
15
|
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
|
|
16
|
-
"test": "
|
|
17
|
-
"test:watch": "
|
|
18
|
-
"test:cov": "
|
|
19
|
-
"test:
|
|
20
|
-
"test:e2e": "jest --config ./test/jest-e2e.json"
|
|
16
|
+
"test": "vitest run",
|
|
17
|
+
"test:watch": "vitest",
|
|
18
|
+
"test:cov": "vitest run --coverage",
|
|
19
|
+
"test:e2e": "vitest run --config vitest.e2e.config.ts"
|
|
21
20
|
},
|
|
22
21
|
"dependencies": {
|
|
23
|
-
"@nestjs/common": "^11.0.1",
|
|
24
|
-
"@nestjs/core": "^11.0.1",
|
|
25
|
-
"@nestjs/platform-express": "^11.0.1",
|
|
26
22
|
"@pulumi/aws": "^7.15.0",
|
|
27
23
|
"@pulumi/pulumi": "^3.213.0",
|
|
28
|
-
"
|
|
29
|
-
"rxjs": "^7.8.1"
|
|
24
|
+
"express": "^5.2.1"
|
|
30
25
|
},
|
|
31
26
|
"devDependencies": {
|
|
32
27
|
"@eslint/eslintrc": "^3.2.0",
|
|
33
28
|
"@eslint/js": "^9.18.0",
|
|
34
|
-
"@nestjs/cli": "^11.0.0",
|
|
35
|
-
"@nestjs/schematics": "^11.0.0",
|
|
36
|
-
"@nestjs/testing": "^11.0.1",
|
|
37
29
|
"@types/express": "^5.0.0",
|
|
38
|
-
"@types/jest": "^30.0.0",
|
|
39
30
|
"@types/node": "^22.10.7",
|
|
40
31
|
"@types/supertest": "^6.0.2",
|
|
41
32
|
"eslint": "^9.18.0",
|
|
42
33
|
"eslint-config-prettier": "^10.0.1",
|
|
43
34
|
"eslint-plugin-prettier": "^5.2.2",
|
|
44
35
|
"globals": "^16.0.0",
|
|
45
|
-
"jest": "^30.0.0",
|
|
46
36
|
"prettier": "^3.4.2",
|
|
47
|
-
"source-map-support": "^0.5.21",
|
|
48
37
|
"supertest": "^7.0.0",
|
|
49
|
-
"
|
|
50
|
-
"ts-loader": "^9.5.2",
|
|
51
|
-
"ts-node": "^10.9.2",
|
|
52
|
-
"tsconfig-paths": "^4.2.0",
|
|
38
|
+
"tsx": "^4.19.2",
|
|
53
39
|
"typescript": "^5.7.3",
|
|
54
|
-
"typescript-eslint": "^8.20.0"
|
|
55
|
-
|
|
56
|
-
"jest": {
|
|
57
|
-
"moduleFileExtensions": [
|
|
58
|
-
"js",
|
|
59
|
-
"json",
|
|
60
|
-
"ts"
|
|
61
|
-
],
|
|
62
|
-
"rootDir": "src",
|
|
63
|
-
"testRegex": ".*\\.spec\\.ts$",
|
|
64
|
-
"transform": {
|
|
65
|
-
"^.+\\.(t|j)s$": "ts-jest"
|
|
66
|
-
},
|
|
67
|
-
"collectCoverageFrom": [
|
|
68
|
-
"**/*.(t|j)s"
|
|
69
|
-
],
|
|
70
|
-
"coverageDirectory": "../coverage",
|
|
71
|
-
"testEnvironment": "node"
|
|
40
|
+
"typescript-eslint": "^8.20.0",
|
|
41
|
+
"vitest": "^3.0.4"
|
|
72
42
|
}
|
|
73
43
|
}
|
|
@@ -1,22 +1,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
describe('
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
providers: [AppService],
|
|
12
|
-
}).compile();
|
|
13
|
-
|
|
14
|
-
appController = app.get<AppController>(AppController);
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
describe('root', () => {
|
|
18
|
-
it('should return "Hello World!"', () => {
|
|
19
|
-
expect(appController.getHello()).toBe('Hello World!');
|
|
20
|
-
});
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import request from 'supertest';
|
|
3
|
+
import { createApp } from './main';
|
|
4
|
+
|
|
5
|
+
describe('Index Route', () => {
|
|
6
|
+
it('should return "Hello World!"', async () => {
|
|
7
|
+
const app = createApp();
|
|
8
|
+
const response = await request(app).get('/');
|
|
9
|
+
expect(response.status).toBe(200);
|
|
10
|
+
expect(response.text).toBe('Hello World!');
|
|
21
11
|
});
|
|
22
12
|
});
|
package/src/main.ts
CHANGED
|
@@ -1,8 +1,26 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
1
|
+
import express from 'express';
|
|
2
|
+
import { indexRoutes } from './routes';
|
|
3
|
+
import { infraRoutes } from './routes/infra';
|
|
4
|
+
|
|
5
|
+
export function createApp() {
|
|
6
|
+
const app = express();
|
|
7
|
+
|
|
8
|
+
app.use(express.json());
|
|
9
|
+
|
|
10
|
+
// Set up routes
|
|
11
|
+
app.use('/', indexRoutes);
|
|
12
|
+
app.use('/infra', infraRoutes);
|
|
13
|
+
|
|
14
|
+
return app;
|
|
15
|
+
}
|
|
3
16
|
|
|
4
17
|
async function bootstrap() {
|
|
5
|
-
const app =
|
|
6
|
-
|
|
18
|
+
const app = createApp();
|
|
19
|
+
const port = process.env.PORT ?? 3000;
|
|
20
|
+
|
|
21
|
+
app.listen(port, () => {
|
|
22
|
+
console.log(`Server is running on http://localhost:${port}`);
|
|
23
|
+
});
|
|
7
24
|
}
|
|
25
|
+
|
|
8
26
|
bootstrap();
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Router } from 'express';
|
|
2
|
+
import { pulumiService } from '../../services/pulumi.service';
|
|
3
|
+
|
|
4
|
+
export const infraRoutes = Router();
|
|
5
|
+
|
|
6
|
+
infraRoutes.post('/deploy', async (_req, res) => {
|
|
7
|
+
try {
|
|
8
|
+
const result = await pulumiService.deploy();
|
|
9
|
+
res.json(result);
|
|
10
|
+
} catch (error) {
|
|
11
|
+
console.error('Deploy error:', error);
|
|
12
|
+
res.status(500).json({ error: 'Deploy failed', message: String(error) });
|
|
13
|
+
}
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
infraRoutes.post('/destroy', async (_req, res) => {
|
|
17
|
+
try {
|
|
18
|
+
const result = await pulumiService.destroy();
|
|
19
|
+
res.json(result);
|
|
20
|
+
} catch (error) {
|
|
21
|
+
console.error('Destroy error:', error);
|
|
22
|
+
res.status(500).json({ error: 'Destroy failed', message: String(error) });
|
|
23
|
+
}
|
|
24
|
+
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as pulumi from '@pulumi/pulumi';
|
|
2
2
|
import * as aws from '@pulumi/aws';
|
|
3
3
|
import * as awsNative from '@pulumi/aws-native';
|
|
4
|
-
import { ApplianceBaseConfig } from '@appliance.sh/sdk';
|
|
4
|
+
import type { ApplianceBaseConfig } from '@appliance.sh/sdk';
|
|
5
5
|
|
|
6
6
|
export interface ApplianceStackArgs {
|
|
7
7
|
tags?: Record<string, string>;
|
|
@@ -73,27 +73,35 @@ export class ApplianceStack extends pulumi.ComponentResource {
|
|
|
73
73
|
this.dnsRecord = pulumi.interpolate`${name}.${args.config.domainName ?? ''}`;
|
|
74
74
|
|
|
75
75
|
if (args.config.aws.cloudfrontDistributionId) {
|
|
76
|
-
new aws.lambda.Permission(
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
76
|
+
new aws.lambda.Permission(
|
|
77
|
+
`${name}-url-invoke-url-permission`,
|
|
78
|
+
{
|
|
79
|
+
function: this.lambda.name,
|
|
80
|
+
action: 'lambda:InvokeFunctionUrl',
|
|
81
|
+
principal: 'cloudfront.amazonaws.com',
|
|
82
|
+
functionUrlAuthType: 'AWS_IAM',
|
|
83
|
+
sourceArn: pulumi.interpolate`arn:aws:cloudfront::${
|
|
84
|
+
aws.getCallerIdentityOutput({}, { provider: opts.provider }).accountId
|
|
85
|
+
}:distribution/${args.config.aws.cloudfrontDistributionId}`,
|
|
86
|
+
statementId: 'FunctionURLAllowCloudFrontAccess',
|
|
87
|
+
},
|
|
88
|
+
defaultOpts
|
|
89
|
+
);
|
|
86
90
|
|
|
87
91
|
// Grant the edge router role permission to invoke the Lambda Function URL
|
|
88
92
|
// The edge router role is the execution role of the Lambda@Edge function that signs requests
|
|
89
93
|
if (args.config.aws.edgeRouterRoleArn) {
|
|
90
|
-
new aws.lambda.Permission(
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
94
|
+
new aws.lambda.Permission(
|
|
95
|
+
`${name}-invoke-url-edge-router-permission`,
|
|
96
|
+
{
|
|
97
|
+
function: this.lambda.name,
|
|
98
|
+
action: 'lambda:InvokeFunctionUrl',
|
|
99
|
+
principal: args.config.aws.edgeRouterRoleArn,
|
|
100
|
+
functionUrlAuthType: 'AWS_IAM',
|
|
101
|
+
statementId: 'FunctionURLAllowEdgeRouterRoleAccess',
|
|
102
|
+
},
|
|
103
|
+
defaultOpts
|
|
104
|
+
);
|
|
97
105
|
|
|
98
106
|
new awsNative.lambda.Permission(
|
|
99
107
|
`${name}-invoke-edge-router-permission`,
|
|
@@ -107,13 +115,17 @@ export class ApplianceStack extends pulumi.ComponentResource {
|
|
|
107
115
|
);
|
|
108
116
|
}
|
|
109
117
|
} else {
|
|
110
|
-
new aws.lambda.Permission(
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
118
|
+
new aws.lambda.Permission(
|
|
119
|
+
`${name}-url-invoke-url-permission`,
|
|
120
|
+
{
|
|
121
|
+
function: this.lambda.name,
|
|
122
|
+
action: 'lambda:InvokeFunctionUrl',
|
|
123
|
+
principal: '*',
|
|
124
|
+
functionUrlAuthType: 'NONE',
|
|
125
|
+
statementId: 'FunctionURLAllowPublicAccess',
|
|
126
|
+
},
|
|
127
|
+
defaultOpts
|
|
128
|
+
);
|
|
117
129
|
}
|
|
118
130
|
|
|
119
131
|
if (args.config.aws.cloudfrontDistributionId && args.config.aws.cloudfrontDistributionDomainName) {
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { Injectable, Logger } from '@nestjs/common';
|
|
2
1
|
import * as auto from '@pulumi/pulumi/automation';
|
|
3
2
|
import * as aws from '@pulumi/aws';
|
|
4
3
|
import * as awsNative from '@pulumi/aws-native';
|
|
@@ -15,9 +14,7 @@ export interface PulumiResult {
|
|
|
15
14
|
stackName: string;
|
|
16
15
|
}
|
|
17
16
|
|
|
18
|
-
|
|
19
|
-
export class PulumiService {
|
|
20
|
-
private readonly logger = new Logger(PulumiService.name);
|
|
17
|
+
class PulumiService {
|
|
21
18
|
private readonly projectName = 'appliance-api-managed-proj';
|
|
22
19
|
|
|
23
20
|
private readonly baseConfig = process.env.APPLIANCE_BASE_CONFIG
|
|
@@ -108,7 +105,7 @@ export class PulumiService {
|
|
|
108
105
|
|
|
109
106
|
async deploy(stackName = 'appliance-api-managed'): Promise<PulumiResult> {
|
|
110
107
|
const stack = await this.getOrCreateStack(stackName);
|
|
111
|
-
const result = await stack.up({ onOutput: (m) =>
|
|
108
|
+
const result = await stack.up({ onOutput: (m) => console.log(m) });
|
|
112
109
|
const changes = result.summary.resourceChanges || {};
|
|
113
110
|
const totalChanges = Object.entries(changes)
|
|
114
111
|
.filter(([k]) => k !== 'same')
|
|
@@ -126,7 +123,7 @@ export class PulumiService {
|
|
|
126
123
|
async destroy(stackName = 'appliance-api-managed'): Promise<PulumiResult> {
|
|
127
124
|
try {
|
|
128
125
|
const stack = await this.selectExistingStack(stackName);
|
|
129
|
-
await stack.destroy({ onOutput: (m) =>
|
|
126
|
+
await stack.destroy({ onOutput: (m) => console.log(m) });
|
|
130
127
|
return { action: 'destroy', ok: true, idempotentNoop: false, message: 'Stack resources deleted', stackName };
|
|
131
128
|
} catch (e) {
|
|
132
129
|
if (!(e instanceof Error)) throw e;
|
|
@@ -144,3 +141,6 @@ export class PulumiService {
|
|
|
144
141
|
}
|
|
145
142
|
}
|
|
146
143
|
}
|
|
144
|
+
|
|
145
|
+
// Export a singleton instance
|
|
146
|
+
export const pulumiService = new PulumiService();
|
package/test/app.e2e-spec.ts
CHANGED
|
@@ -1,22 +1,18 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { INestApplication } from '@nestjs/common';
|
|
1
|
+
import { describe, it, expect, beforeAll } from 'vitest';
|
|
3
2
|
import request from 'supertest';
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
3
|
+
import { createApp } from '../src/main';
|
|
4
|
+
import type { Express } from 'express';
|
|
6
5
|
|
|
7
|
-
describe('
|
|
8
|
-
let app:
|
|
6
|
+
describe('App (e2e)', () => {
|
|
7
|
+
let app: Express;
|
|
9
8
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
imports: [AppModule],
|
|
13
|
-
}).compile();
|
|
14
|
-
|
|
15
|
-
app = moduleFixture.createNestApplication();
|
|
16
|
-
await app.init();
|
|
9
|
+
beforeAll(() => {
|
|
10
|
+
app = createApp();
|
|
17
11
|
});
|
|
18
12
|
|
|
19
|
-
it('/ (GET)', () => {
|
|
20
|
-
|
|
13
|
+
it('/ (GET)', async () => {
|
|
14
|
+
const response = await request(app).get('/');
|
|
15
|
+
expect(response.status).toBe(200);
|
|
16
|
+
expect(response.text).toBe('Hello World!');
|
|
21
17
|
});
|
|
22
18
|
});
|
package/vitest.config.ts
ADDED
package/nest-cli.json
DELETED
package/src/app.controller.ts
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import { Controller, Get } from '@nestjs/common';
|
|
2
|
-
import { AppService } from './app.service';
|
|
3
|
-
|
|
4
|
-
@Controller()
|
|
5
|
-
export class AppController {
|
|
6
|
-
constructor(private readonly appService: AppService) {}
|
|
7
|
-
|
|
8
|
-
@Get()
|
|
9
|
-
getHello(): string {
|
|
10
|
-
return this.appService.getHello();
|
|
11
|
-
}
|
|
12
|
-
}
|
package/src/app.module.ts
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import { Module } from '@nestjs/common';
|
|
2
|
-
import { AppController } from './app.controller';
|
|
3
|
-
import { AppService } from './app.service';
|
|
4
|
-
import { PulumiModule } from './pulumi/pulumi.module';
|
|
5
|
-
|
|
6
|
-
@Module({
|
|
7
|
-
imports: [PulumiModule],
|
|
8
|
-
controllers: [AppController],
|
|
9
|
-
providers: [AppService],
|
|
10
|
-
})
|
|
11
|
-
export class AppModule {}
|
package/src/app.service.ts
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { Controller, HttpCode, Post } from '@nestjs/common';
|
|
2
|
-
import { PulumiService } from './pulumi.service';
|
|
3
|
-
|
|
4
|
-
@Controller('infra')
|
|
5
|
-
export class PulumiController {
|
|
6
|
-
constructor(private readonly pulumi: PulumiService) {}
|
|
7
|
-
|
|
8
|
-
@Post('deploy')
|
|
9
|
-
@HttpCode(200)
|
|
10
|
-
async deploy() {
|
|
11
|
-
return await this.pulumi.deploy();
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
@Post('destroy')
|
|
15
|
-
@HttpCode(200)
|
|
16
|
-
async destroy() {
|
|
17
|
-
return await this.pulumi.destroy();
|
|
18
|
-
}
|
|
19
|
-
}
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import { Module } from '@nestjs/common';
|
|
2
|
-
import { PulumiService } from './pulumi.service';
|
|
3
|
-
import { PulumiController } from './pulumi.controller';
|
|
4
|
-
|
|
5
|
-
@Module({
|
|
6
|
-
providers: [PulumiService],
|
|
7
|
-
controllers: [PulumiController],
|
|
8
|
-
})
|
|
9
|
-
export class PulumiModule {}
|