@tac0de/project-bootstrap-mcp 1.0.2 → 1.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.
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- const startServer = require('../src/server');
2
+ const startServer = require('../dist/server').default;
3
3
 
4
4
  function parsePort(args, envPort) {
5
5
  let portValue = envPort || '4000';
package/dist/app.d.ts ADDED
@@ -0,0 +1,11 @@
1
+ import express from 'express';
2
+ import BootstrapStore from './store';
3
+ interface CreateAppOptions {
4
+ store?: BootstrapStore;
5
+ }
6
+ export default function createApp(options?: CreateAppOptions): {
7
+ app: express.Express;
8
+ store: BootstrapStore;
9
+ };
10
+ export {};
11
+ //# sourceMappingURL=app.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":"AAAA,OAAO,OAAqD,MAAM,SAAS,CAAC;AAG5E,OAAO,cAAc,MAAM,SAAS,CAAC;AAGrC,UAAU,gBAAgB;IACxB,KAAK,CAAC,EAAE,cAAc,CAAC;CACxB;AAED,MAAM,CAAC,OAAO,UAAU,SAAS,CAAC,OAAO,GAAE,gBAAqB;;;EA8D/D"}
package/dist/app.js ADDED
@@ -0,0 +1,65 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.default = createApp;
7
+ const express_1 = __importDefault(require("express"));
8
+ const package_json_1 = __importDefault(require("../package.json"));
9
+ const validators_1 = require("./validators");
10
+ const store_1 = __importDefault(require("./store"));
11
+ function createApp(options = {}) {
12
+ const store = options.store ?? new store_1.default();
13
+ const app = (0, express_1.default)();
14
+ app.use(express_1.default.json());
15
+ app.get('/api/status', (_req, res) => {
16
+ res.json({
17
+ service: package_json_1.default.name,
18
+ version: package_json_1.default.version,
19
+ uptime: process.uptime(),
20
+ timestamp: new Date().toISOString(),
21
+ });
22
+ });
23
+ app.get('/api/bootstraps', (_req, res) => {
24
+ res.json({ items: store.list() });
25
+ });
26
+ app.post('/api/bootstraps', (req, res) => {
27
+ const validation = (0, validators_1.validateBootstrapPayload)(req.body);
28
+ if (!validation.valid) {
29
+ return res.status(400).json({ errors: validation.errors });
30
+ }
31
+ const payload = {
32
+ name: req.body.name,
33
+ description: req.body.description,
34
+ primaryAction: req.body.primaryAction,
35
+ targetPlatforms: Array.isArray(req.body.targetPlatforms)
36
+ ? req.body.targetPlatforms.map(String)
37
+ : [],
38
+ successCriteria: Array.isArray(req.body.successCriteria)
39
+ ? req.body.successCriteria.map(String)
40
+ : [],
41
+ steps: Array.isArray(req.body.steps)
42
+ ? req.body.steps
43
+ : [],
44
+ };
45
+ const saved = store.add(payload);
46
+ res.status(201).json(saved);
47
+ });
48
+ app.get('/api/bootstraps/:id', (req, res) => {
49
+ const record = store.find(req.params.id);
50
+ if (!record) {
51
+ return res.status(404).json({ error: 'bootstrap not found' });
52
+ }
53
+ res.json(record);
54
+ });
55
+ app.use((_req, res) => {
56
+ res.status(404).json({ error: 'not found' });
57
+ });
58
+ app.use((err, _req, res, next) => {
59
+ // eslint-disable-next-line no-console
60
+ console.error('internal error', err);
61
+ res.status(500).json({ error: 'internal server error' });
62
+ next(err);
63
+ });
64
+ return { app, store };
65
+ }
@@ -0,0 +1,5 @@
1
+ import BootstrapStore from './store';
2
+ import createApp from './app';
3
+ import startServer from './server';
4
+ export { BootstrapStore, createApp, startServer };
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,cAAc,MAAM,SAAS,CAAC;AACrC,OAAO,SAAS,MAAM,OAAO,CAAC;AAC9B,OAAO,WAAW,MAAM,UAAU,CAAC;AAEnC,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,WAAW,EAAE,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.startServer = exports.createApp = exports.BootstrapStore = void 0;
7
+ const store_1 = __importDefault(require("./store"));
8
+ exports.BootstrapStore = store_1.default;
9
+ const app_1 = __importDefault(require("./app"));
10
+ exports.createApp = app_1.default;
11
+ const server_1 = __importDefault(require("./server"));
12
+ exports.startServer = server_1.default;
@@ -0,0 +1,12 @@
1
+ import type { Express } from 'express';
2
+ import type { Server } from 'http';
3
+ export interface StartServerOptions {
4
+ port?: number;
5
+ onStart?: (message: string, port: number) => void;
6
+ }
7
+ export interface StartServerResult {
8
+ app: Express;
9
+ server: Server;
10
+ }
11
+ export default function startServer({ port, onStart, }?: StartServerOptions): StartServerResult;
12
+ //# sourceMappingURL=server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AAGnC,MAAM,WAAW,kBAAkB;IACjC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CACnD;AAED,MAAM,WAAW,iBAAiB;IAChC,GAAG,EAAE,OAAO,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,CAAC,OAAO,UAAU,WAAW,CAAC,EAClC,IAAI,EACJ,OAAO,GACR,GAAE,kBAAuB,GAAG,iBAAiB,CA0B7C"}
package/dist/server.js ADDED
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.default = startServer;
7
+ const app_1 = __importDefault(require("./app"));
8
+ function startServer({ port, onStart, } = {}) {
9
+ const defaultPort = Number(process.env.PORT) || 4000;
10
+ const resolvedPort = port ?? defaultPort;
11
+ const { app } = (0, app_1.default)();
12
+ const server = app.listen(resolvedPort, () => {
13
+ const message = `Project Bootstrap MCP listening on port ${resolvedPort}`;
14
+ if (typeof onStart === 'function') {
15
+ onStart(message, resolvedPort);
16
+ }
17
+ else {
18
+ // eslint-disable-next-line no-console
19
+ console.log(message);
20
+ }
21
+ });
22
+ const graceful = () => {
23
+ server.close(() => {
24
+ // eslint-disable-next-line no-console
25
+ console.log('Project Bootstrap MCP shutting down');
26
+ process.exit(0);
27
+ });
28
+ };
29
+ process.on('SIGINT', graceful);
30
+ process.on('SIGTERM', graceful);
31
+ return { app, server };
32
+ }
@@ -0,0 +1,9 @@
1
+ import type { BootstrapPayload, BootstrapRecord } from './types';
2
+ export default class BootstrapStore {
3
+ private readonly items;
4
+ private counter;
5
+ list(): BootstrapRecord[];
6
+ add(payload: BootstrapPayload): BootstrapRecord;
7
+ find(id: string): BootstrapRecord | null;
8
+ }
9
+ //# sourceMappingURL=store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAEjE,MAAM,CAAC,OAAO,OAAO,cAAc;IACjC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAsC;IAC5D,OAAO,CAAC,OAAO,CAAK;IAEb,IAAI,IAAI,eAAe,EAAE;IAIzB,GAAG,CAAC,OAAO,EAAE,gBAAgB,GAAG,eAAe;IAW/C,IAAI,CAAC,EAAE,EAAE,MAAM,GAAG,eAAe,GAAG,IAAI;CAGhD"}
package/dist/store.js ADDED
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ class BootstrapStore {
4
+ constructor() {
5
+ this.items = new Map();
6
+ this.counter = 1;
7
+ }
8
+ list() {
9
+ return Array.from(this.items.values());
10
+ }
11
+ add(payload) {
12
+ const id = String(this.counter++);
13
+ const record = {
14
+ id,
15
+ createdAt: new Date().toISOString(),
16
+ ...payload,
17
+ };
18
+ this.items.set(id, record);
19
+ return record;
20
+ }
21
+ find(id) {
22
+ return this.items.get(id) ?? null;
23
+ }
24
+ }
25
+ exports.default = BootstrapStore;
@@ -0,0 +1,17 @@
1
+ export interface BootstrapStep {
2
+ title: string;
3
+ detail: string;
4
+ }
5
+ export interface BootstrapPayload {
6
+ name: string;
7
+ description: string;
8
+ primaryAction: string;
9
+ targetPlatforms?: string[];
10
+ successCriteria?: string[];
11
+ steps: BootstrapStep[];
12
+ }
13
+ export interface BootstrapRecord extends BootstrapPayload {
14
+ id: string;
15
+ createdAt: string;
16
+ }
17
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;IAC3B,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;IAC3B,KAAK,EAAE,aAAa,EAAE,CAAC;CACxB;AAED,MAAM,WAAW,eAAgB,SAAQ,gBAAgB;IACvD,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;CACnB"}
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,6 @@
1
+ export interface ValidationResult {
2
+ valid: boolean;
3
+ errors: string[];
4
+ }
5
+ export declare function validateBootstrapPayload(payload: unknown): ValidationResult;
6
+ //# sourceMappingURL=validators.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validators.d.ts","sourceRoot":"","sources":["../src/validators.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,OAAO,GAAG,gBAAgB,CAmC3E"}
@@ -0,0 +1,36 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.validateBootstrapPayload = validateBootstrapPayload;
4
+ function validateBootstrapPayload(payload) {
5
+ const errors = [];
6
+ if (!payload || typeof payload !== 'object') {
7
+ errors.push('payload must be an object');
8
+ return { valid: false, errors };
9
+ }
10
+ const typedPayload = payload;
11
+ const { name, description, primaryAction, steps } = typedPayload;
12
+ if (!name || typeof name !== 'string') {
13
+ errors.push('name is required and must be a string');
14
+ }
15
+ if (!description || typeof description !== 'string') {
16
+ errors.push('description is required and must be a string');
17
+ }
18
+ if (!primaryAction || typeof primaryAction !== 'string') {
19
+ errors.push('primaryAction is required and must be a string');
20
+ }
21
+ if (!Array.isArray(steps) || steps.length === 0) {
22
+ errors.push('steps must be a non-empty array');
23
+ }
24
+ else {
25
+ steps.forEach((step, index) => {
26
+ const currentStep = step;
27
+ if (!currentStep || typeof currentStep.title !== 'string') {
28
+ errors.push(`steps[${index}].title is required`);
29
+ }
30
+ if (!currentStep || typeof currentStep.detail !== 'string') {
31
+ errors.push(`steps[${index}].detail is required`);
32
+ }
33
+ });
34
+ }
35
+ return { valid: errors.length === 0, errors };
36
+ }
package/package.json CHANGED
@@ -1,14 +1,15 @@
1
1
  {
2
2
  "name": "@tac0de/project-bootstrap-mcp",
3
- "version": "1.0.2",
3
+ "version": "1.1.0",
4
4
  "description": "Project Bootstrap MCP API tooling for launching MCP bootstrap definitions and documentation.",
5
- "main": "src/index.js",
5
+ "main": "dist/index.js",
6
6
  "bin": {
7
7
  "project-bootstrap-mcp": "bin/project-bootstrap-mcp.js"
8
8
  },
9
9
  "scripts": {
10
- "start": "node ./bin/project-bootstrap-mcp.js",
11
- "test": "node --test test/store.test.js"
10
+ "build": "tsc",
11
+ "start": "npm run build && node ./bin/project-bootstrap-mcp.js",
12
+ "test": "node --test -r ts-node/register test/store.test.ts"
12
13
  },
13
14
  "keywords": [
14
15
  "mcp",
@@ -28,17 +29,25 @@
28
29
  },
29
30
  "files": [
30
31
  "bin",
32
+ "dist",
31
33
  "src",
32
34
  "test",
33
35
  "LICENSE",
34
36
  "README.md",
35
37
  "TRD.md"
36
38
  ],
39
+ "types": "dist/index.d.ts",
37
40
  "repository": {
38
41
  "type": "git",
39
42
  "url": "https://github.com/tac0de/project-bootstrap-mcp"
40
43
  },
41
44
  "dependencies": {
42
45
  "express": "^4.19.2"
46
+ },
47
+ "devDependencies": {
48
+ "@types/express": "^5.0.6",
49
+ "@types/node": "^25.0.3",
50
+ "ts-node": "^10.9.2",
51
+ "typescript": "^5.9.3"
43
52
  }
44
53
  }
package/src/app.ts ADDED
@@ -0,0 +1,73 @@
1
+ import express, { Express, Request, Response, NextFunction } from 'express';
2
+ import pkg from '../package.json';
3
+ import { validateBootstrapPayload } from './validators';
4
+ import BootstrapStore from './store';
5
+ import type { BootstrapPayload } from './types';
6
+
7
+ interface CreateAppOptions {
8
+ store?: BootstrapStore;
9
+ }
10
+
11
+ export default function createApp(options: CreateAppOptions = {}) {
12
+ const store = options.store ?? new BootstrapStore();
13
+ const app: Express = express();
14
+
15
+ app.use(express.json());
16
+
17
+ app.get('/api/status', (_req: Request, res: Response) => {
18
+ res.json({
19
+ service: pkg.name,
20
+ version: pkg.version,
21
+ uptime: process.uptime(),
22
+ timestamp: new Date().toISOString(),
23
+ });
24
+ });
25
+
26
+ app.get('/api/bootstraps', (_req: Request, res: Response) => {
27
+ res.json({ items: store.list() });
28
+ });
29
+
30
+ app.post('/api/bootstraps', (req: Request, res: Response) => {
31
+ const validation = validateBootstrapPayload(req.body);
32
+ if (!validation.valid) {
33
+ return res.status(400).json({ errors: validation.errors });
34
+ }
35
+ const payload: BootstrapPayload = {
36
+ name: req.body.name,
37
+ description: req.body.description,
38
+ primaryAction: req.body.primaryAction,
39
+ targetPlatforms: Array.isArray(req.body.targetPlatforms)
40
+ ? req.body.targetPlatforms.map(String)
41
+ : [],
42
+ successCriteria: Array.isArray(req.body.successCriteria)
43
+ ? req.body.successCriteria.map(String)
44
+ : [],
45
+ steps: Array.isArray(req.body.steps)
46
+ ? (req.body.steps as BootstrapPayload['steps'])
47
+ : [],
48
+ };
49
+ const saved = store.add(payload);
50
+ res.status(201).json(saved);
51
+ });
52
+
53
+ app.get('/api/bootstraps/:id', (req: Request, res: Response) => {
54
+ const record = store.find(req.params.id);
55
+ if (!record) {
56
+ return res.status(404).json({ error: 'bootstrap not found' });
57
+ }
58
+ res.json(record);
59
+ });
60
+
61
+ app.use((_req: Request, res: Response) => {
62
+ res.status(404).json({ error: 'not found' });
63
+ });
64
+
65
+ app.use((err: Error, _req: Request, res: Response, next: NextFunction) => {
66
+ // eslint-disable-next-line no-console
67
+ console.error('internal error', err);
68
+ res.status(500).json({ error: 'internal server error' });
69
+ next(err);
70
+ });
71
+
72
+ return { app, store };
73
+ }
package/src/index.ts ADDED
@@ -0,0 +1,5 @@
1
+ import BootstrapStore from './store';
2
+ import createApp from './app';
3
+ import startServer from './server';
4
+
5
+ export { BootstrapStore, createApp, startServer };
package/src/server.ts ADDED
@@ -0,0 +1,44 @@
1
+ import type { Express } from 'express';
2
+ import type { Server } from 'http';
3
+ import createApp from './app';
4
+
5
+ export interface StartServerOptions {
6
+ port?: number;
7
+ onStart?: (message: string, port: number) => void;
8
+ }
9
+
10
+ export interface StartServerResult {
11
+ app: Express;
12
+ server: Server;
13
+ }
14
+
15
+ export default function startServer({
16
+ port,
17
+ onStart,
18
+ }: StartServerOptions = {}): StartServerResult {
19
+ const defaultPort = Number(process.env.PORT) || 4000;
20
+ const resolvedPort = port ?? defaultPort;
21
+ const { app } = createApp();
22
+ const server = app.listen(resolvedPort, () => {
23
+ const message = `Project Bootstrap MCP listening on port ${resolvedPort}`;
24
+ if (typeof onStart === 'function') {
25
+ onStart(message, resolvedPort);
26
+ } else {
27
+ // eslint-disable-next-line no-console
28
+ console.log(message);
29
+ }
30
+ });
31
+
32
+ const graceful = () => {
33
+ server.close(() => {
34
+ // eslint-disable-next-line no-console
35
+ console.log('Project Bootstrap MCP shutting down');
36
+ process.exit(0);
37
+ });
38
+ };
39
+
40
+ process.on('SIGINT', graceful);
41
+ process.on('SIGTERM', graceful);
42
+
43
+ return { app, server };
44
+ }
package/src/store.ts ADDED
@@ -0,0 +1,25 @@
1
+ import type { BootstrapPayload, BootstrapRecord } from './types';
2
+
3
+ export default class BootstrapStore {
4
+ private readonly items = new Map<string, BootstrapRecord>();
5
+ private counter = 1;
6
+
7
+ public list(): BootstrapRecord[] {
8
+ return Array.from(this.items.values());
9
+ }
10
+
11
+ public add(payload: BootstrapPayload): BootstrapRecord {
12
+ const id = String(this.counter++);
13
+ const record: BootstrapRecord = {
14
+ id,
15
+ createdAt: new Date().toISOString(),
16
+ ...payload,
17
+ };
18
+ this.items.set(id, record);
19
+ return record;
20
+ }
21
+
22
+ public find(id: string): BootstrapRecord | null {
23
+ return this.items.get(id) ?? null;
24
+ }
25
+ }
package/src/types.ts ADDED
@@ -0,0 +1,18 @@
1
+ export interface BootstrapStep {
2
+ title: string;
3
+ detail: string;
4
+ }
5
+
6
+ export interface BootstrapPayload {
7
+ name: string;
8
+ description: string;
9
+ primaryAction: string;
10
+ targetPlatforms?: string[];
11
+ successCriteria?: string[];
12
+ steps: BootstrapStep[];
13
+ }
14
+
15
+ export interface BootstrapRecord extends BootstrapPayload {
16
+ id: string;
17
+ createdAt: string;
18
+ }
@@ -1,11 +1,20 @@
1
- function validateBootstrapPayload(payload) {
2
- const errors = [];
1
+ import type { BootstrapPayload, BootstrapStep } from './types';
2
+
3
+ export interface ValidationResult {
4
+ valid: boolean;
5
+ errors: string[];
6
+ }
7
+
8
+ export function validateBootstrapPayload(payload: unknown): ValidationResult {
9
+ const errors: string[] = [];
3
10
  if (!payload || typeof payload !== 'object') {
4
11
  errors.push('payload must be an object');
5
12
  return { valid: false, errors };
6
13
  }
7
14
 
8
- const { name, description, primaryAction, steps } = payload;
15
+ const typedPayload = payload as BootstrapPayload;
16
+ const { name, description, primaryAction, steps } = typedPayload;
17
+
9
18
  if (!name || typeof name !== 'string') {
10
19
  errors.push('name is required and must be a string');
11
20
  }
@@ -15,14 +24,16 @@ function validateBootstrapPayload(payload) {
15
24
  if (!primaryAction || typeof primaryAction !== 'string') {
16
25
  errors.push('primaryAction is required and must be a string');
17
26
  }
27
+
18
28
  if (!Array.isArray(steps) || steps.length === 0) {
19
29
  errors.push('steps must be a non-empty array');
20
30
  } else {
21
31
  steps.forEach((step, index) => {
22
- if (!step || typeof step.title !== 'string') {
32
+ const currentStep = step as BootstrapStep | undefined;
33
+ if (!currentStep || typeof currentStep.title !== 'string') {
23
34
  errors.push(`steps[${index}].title is required`);
24
35
  }
25
- if (!step || typeof step.detail !== 'string') {
36
+ if (!currentStep || typeof currentStep.detail !== 'string') {
26
37
  errors.push(`steps[${index}].detail is required`);
27
38
  }
28
39
  });
@@ -30,5 +41,3 @@ function validateBootstrapPayload(payload) {
30
41
 
31
42
  return { valid: errors.length === 0, errors };
32
43
  }
33
-
34
- module.exports = { validateBootstrapPayload };
@@ -1,7 +1,7 @@
1
- const test = require('node:test');
2
- const assert = require('node:assert');
3
- const BootstrapStore = require('../src/store');
4
- const { validateBootstrapPayload } = require('../src/validators');
1
+ import test from 'node:test';
2
+ import assert from 'node:assert';
3
+ import BootstrapStore from '../src/store';
4
+ import { validateBootstrapPayload } from '../src/validators';
5
5
 
6
6
  test('BootstrapStore should add and list records', () => {
7
7
  const store = new BootstrapStore();
@@ -9,9 +9,7 @@ test('BootstrapStore should add and list records', () => {
9
9
  name: 'bootstrap-a',
10
10
  description: 'test',
11
11
  primaryAction: 'deploy',
12
- steps: [
13
- { title: 'step1', detail: 'run' },
14
- ],
12
+ steps: [{ title: 'step1', detail: 'run' }],
15
13
  };
16
14
 
17
15
  const record = store.add(payload);
package/src/app.js DELETED
@@ -1,71 +0,0 @@
1
- const express = require('express');
2
- const path = require('path');
3
- const { validateBootstrapPayload } = require('./validators');
4
- const BootstrapStore = require('./store');
5
-
6
- const pkg = require(path.join(__dirname, '..', 'package.json'));
7
-
8
- function createApp(options = {}) {
9
- const store = options.store || new BootstrapStore();
10
- const app = express();
11
-
12
- app.use(express.json());
13
-
14
- app.get('/api/status', (req, res) => {
15
- res.json({
16
- service: pkg.name,
17
- version: pkg.version,
18
- uptime: process.uptime(),
19
- timestamp: new Date().toISOString(),
20
- });
21
- });
22
-
23
- app.get('/api/bootstraps', (req, res) => {
24
- res.json({ items: store.list() });
25
- });
26
-
27
- app.post('/api/bootstraps', (req, res) => {
28
- const validation = validateBootstrapPayload(req.body);
29
- if (!validation.valid) {
30
- return res.status(400).json({ errors: validation.errors });
31
- }
32
- const payload = {
33
- name: req.body.name,
34
- description: req.body.description,
35
- primaryAction: req.body.primaryAction,
36
- targetPlatforms: Array.isArray(req.body.targetPlatforms)
37
- ? req.body.targetPlatforms
38
- : [],
39
- successCriteria: Array.isArray(req.body.successCriteria)
40
- ? req.body.successCriteria
41
- : [],
42
- steps: req.body.steps,
43
- };
44
- const saved = store.add(payload);
45
- res.status(201).json(saved);
46
- });
47
-
48
- app.get('/api/bootstraps/:id', (req, res) => {
49
- const record = store.find(req.params.id);
50
- if (!record) {
51
- return res.status(404).json({ error: 'bootstrap not found' });
52
- }
53
- res.json(record);
54
- });
55
-
56
- app.use((req, res) => {
57
- res.status(404).json({ error: 'not found' });
58
- });
59
-
60
- app.use((err, req, res, next) => {
61
- /* eslint-disable no-console */
62
- console.error('internal error', err);
63
- /* eslint-enable no-console */
64
- res.status(500).json({ error: 'internal server error' });
65
- next(err);
66
- });
67
-
68
- return { app, store };
69
- }
70
-
71
- module.exports = createApp;
package/src/index.js DELETED
@@ -1,9 +0,0 @@
1
- const BootstrapStore = require('./store');
2
- const createApp = require('./app');
3
- const startServer = require('./server');
4
-
5
- module.exports = {
6
- BootstrapStore,
7
- createApp,
8
- startServer,
9
- };
package/src/store.js DELETED
@@ -1,27 +0,0 @@
1
- class BootstrapStore {
2
- constructor() {
3
- this.items = new Map();
4
- this.counter = 1;
5
- }
6
-
7
- list() {
8
- return Array.from(this.items.values());
9
- }
10
-
11
- add(payload) {
12
- const id = String(this.counter++);
13
- const record = {
14
- id,
15
- createdAt: new Date().toISOString(),
16
- ...payload,
17
- };
18
- this.items.set(id, record);
19
- return record;
20
- }
21
-
22
- find(id) {
23
- return this.items.get(id) ?? null;
24
- }
25
- }
26
-
27
- module.exports = BootstrapStore;