@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 CHANGED
@@ -1,73 +1,43 @@
1
1
  {
2
2
  "name": "@appliance.sh/api-server",
3
- "version": "1.14.0",
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": "nest build",
9
+ "build": "tsc",
10
10
  "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
11
- "start": "nest start",
12
- "start:dev": "nest start --watch",
13
- "start:debug": "nest start --debug --watch",
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": "jest",
17
- "test:watch": "jest --watch",
18
- "test:cov": "jest --coverage",
19
- "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
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
- "reflect-metadata": "^0.2.2",
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
- "ts-jest": "^29.2.5",
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 { Test, TestingModule } from '@nestjs/testing';
2
- import { AppController } from './app.controller';
3
- import { AppService } from './app.service';
4
-
5
- describe('AppController', () => {
6
- let appController: AppController;
7
-
8
- beforeEach(async () => {
9
- const app: TestingModule = await Test.createTestingModule({
10
- controllers: [AppController],
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 { NestFactory } from '@nestjs/core';
2
- import { AppModule } from './app.module';
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 = await NestFactory.create(AppModule);
6
- await app.listen(process.env.PORT ?? 3000);
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,7 @@
1
+ import { Router } from 'express';
2
+
3
+ export const indexRoutes = Router();
4
+
5
+ indexRoutes.get('/', (_req, res) => {
6
+ res.send('Hello World!');
7
+ });
@@ -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(`${name}-url-invoke-url-permission`, {
77
- function: this.lambda.name,
78
- action: 'lambda:InvokeFunctionUrl',
79
- principal: 'cloudfront.amazonaws.com',
80
- functionUrlAuthType: 'AWS_IAM',
81
- sourceArn: pulumi.interpolate`arn:aws:cloudfront::${
82
- aws.getCallerIdentityOutput({}, { provider: opts.provider }).accountId
83
- }:distribution/${args.config.aws.cloudfrontDistributionId}`,
84
- statementId: 'FunctionURLAllowCloudFrontAccess',
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(`${name}-invoke-url-edge-router-permission`, {
91
- function: this.lambda.name,
92
- action: 'lambda:InvokeFunctionUrl',
93
- principal: args.config.aws.edgeRouterRoleArn,
94
- functionUrlAuthType: 'AWS_IAM',
95
- statementId: 'FunctionURLAllowEdgeRouterRoleAccess',
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(`${name}-url-invoke-url-permission`, {
111
- function: this.lambda.name,
112
- action: 'lambda:InvokeFunctionUrl',
113
- principal: '*',
114
- functionUrlAuthType: 'NONE',
115
- statementId: 'FunctionURLAllowPublicAccess',
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
- @Injectable()
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) => this.logger.log(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) => this.logger.log(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();
@@ -1,22 +1,18 @@
1
- import { Test, TestingModule } from '@nestjs/testing';
2
- import { INestApplication } from '@nestjs/common';
1
+ import { describe, it, expect, beforeAll } from 'vitest';
3
2
  import request from 'supertest';
4
- import { App } from 'supertest/types';
5
- import { AppModule } from '../src/app.module';
3
+ import { createApp } from '../src/main';
4
+ import type { Express } from 'express';
6
5
 
7
- describe('AppController (e2e)', () => {
8
- let app: INestApplication<App>;
6
+ describe('App (e2e)', () => {
7
+ let app: Express;
9
8
 
10
- beforeEach(async () => {
11
- const moduleFixture: TestingModule = await Test.createTestingModule({
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
- return request(app.getHttpServer()).get('/').expect(200).expect('Hello World!');
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
  });
@@ -0,0 +1,8 @@
1
+ import { defineConfig } from 'vitest/config';
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ include: ['src/**/*.spec.ts'],
6
+ globals: false,
7
+ },
8
+ });
@@ -0,0 +1,8 @@
1
+ import { defineConfig } from 'vitest/config';
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ include: ['test/**/*.e2e-spec.ts'],
6
+ globals: false,
7
+ },
8
+ });
package/nest-cli.json DELETED
@@ -1,8 +0,0 @@
1
- {
2
- "$schema": "https://json.schemastore.org/nest-cli",
3
- "collection": "@nestjs/schematics",
4
- "sourceRoot": "src",
5
- "compilerOptions": {
6
- "deleteOutDir": true
7
- }
8
- }
@@ -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 {}
@@ -1,8 +0,0 @@
1
- import { Injectable } from '@nestjs/common';
2
-
3
- @Injectable()
4
- export class AppService {
5
- getHello(): string {
6
- return 'Hello World!';
7
- }
8
- }
@@ -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 {}
@@ -1,9 +0,0 @@
1
- {
2
- "moduleFileExtensions": ["js", "json", "ts"],
3
- "rootDir": ".",
4
- "testEnvironment": "node",
5
- "testRegex": ".e2e-spec.ts$",
6
- "transform": {
7
- "^.+\\.(t|j)s$": "ts-jest"
8
- }
9
- }