@mbc-cqrs-serverless/cli 1.0.25 → 1.1.0-beta.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.
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.VERSION_FILE_NAME = void 0;
6
+ exports.VERSION_CACHE_TTL_MS = exports.VERSION_CACHE_FILE_NAME = exports.VERSION_FILE_NAME = void 0;
7
7
  exports.getSkillsSourcePath = getSkillsSourcePath;
8
8
  exports.getPersonalSkillsPath = getPersonalSkillsPath;
9
9
  exports.getProjectSkillsPath = getProjectSkillsPath;
@@ -11,7 +11,9 @@ exports.copySkills = copySkills;
11
11
  exports.getInstalledVersion = getInstalledVersion;
12
12
  exports.getPackageVersion = getPackageVersion;
13
13
  exports.writeVersionFile = writeVersionFile;
14
+ exports.getLatestVersionFromRegistry = getLatestVersionFromRegistry;
14
15
  exports.default = installSkillsAction;
16
+ const child_process_1 = require("child_process");
15
17
  const fs_1 = require("fs");
16
18
  const os_1 = __importDefault(require("os"));
17
19
  const path_1 = __importDefault(require("path"));
@@ -20,6 +22,18 @@ const ui_1 = require("../ui");
20
22
  * Version file name for tracking installed skills version
21
23
  */
22
24
  exports.VERSION_FILE_NAME = '.mbc-skills-version';
25
+ /**
26
+ * Cache file name for storing latest version from npm registry
27
+ */
28
+ exports.VERSION_CACHE_FILE_NAME = '.mbc-version-cache.json';
29
+ /**
30
+ * Cache TTL in milliseconds (24 hours)
31
+ */
32
+ exports.VERSION_CACHE_TTL_MS = 24 * 60 * 60 * 1000;
33
+ /**
34
+ * npm package name for mcp-server
35
+ */
36
+ const MCP_SERVER_PACKAGE = '@mbc-cqrs-serverless/mcp-server';
23
37
  /**
24
38
  * Get the path to the mcp-server skills source directory
25
39
  */
@@ -130,6 +144,61 @@ function writeVersionFile(destPath, version) {
130
144
  const versionFilePath = path_1.default.join(destPath, exports.VERSION_FILE_NAME);
131
145
  (0, fs_1.writeFileSync)(versionFilePath, version, 'utf-8');
132
146
  }
147
+ /**
148
+ * Get the latest package version from npm registry with caching
149
+ * @param destPath - The destination path where cache file is stored
150
+ * @param forceRefresh - Force refresh from npm registry, ignoring cache
151
+ * @returns The latest version or null if not available
152
+ */
153
+ function getLatestVersionFromRegistry(destPath, forceRefresh = false) {
154
+ const cacheFilePath = path_1.default.join(destPath, exports.VERSION_CACHE_FILE_NAME);
155
+ // Check cache first (unless force refresh)
156
+ if (!forceRefresh && (0, fs_1.existsSync)(cacheFilePath)) {
157
+ try {
158
+ const cache = JSON.parse((0, fs_1.readFileSync)(cacheFilePath, 'utf-8'));
159
+ const cacheAge = Date.now() - new Date(cache.checkedAt).getTime();
160
+ if (cacheAge < exports.VERSION_CACHE_TTL_MS) {
161
+ // Cache is still valid
162
+ return cache.version;
163
+ }
164
+ }
165
+ catch {
166
+ // Cache read failed, continue to fetch from registry
167
+ }
168
+ }
169
+ // Fetch from npm registry
170
+ try {
171
+ const version = (0, child_process_1.execSync)(`npm view ${MCP_SERVER_PACKAGE} version`, {
172
+ encoding: 'utf-8',
173
+ timeout: 10000,
174
+ stdio: ['pipe', 'pipe', 'pipe'],
175
+ }).trim();
176
+ // Ensure destination directory exists before writing cache
177
+ if (!(0, fs_1.existsSync)(destPath)) {
178
+ (0, fs_1.mkdirSync)(destPath, { recursive: true });
179
+ }
180
+ // Save to cache
181
+ const cache = {
182
+ version,
183
+ checkedAt: new Date().toISOString(),
184
+ };
185
+ (0, fs_1.writeFileSync)(cacheFilePath, JSON.stringify(cache, null, 2), 'utf-8');
186
+ return version;
187
+ }
188
+ catch {
189
+ // Offline or error - try to use expired cache as fallback
190
+ if ((0, fs_1.existsSync)(cacheFilePath)) {
191
+ try {
192
+ const cache = JSON.parse((0, fs_1.readFileSync)(cacheFilePath, 'utf-8'));
193
+ return cache.version;
194
+ }
195
+ catch {
196
+ return null;
197
+ }
198
+ }
199
+ return null;
200
+ }
201
+ }
133
202
  /**
134
203
  * Install Claude Code skills for MBC CQRS Serverless
135
204
  */
@@ -170,23 +239,24 @@ async function installSkillsAction(options, command) {
170
239
  // Check mode - compare versions without installing
171
240
  if (check) {
172
241
  const installedVersion = getInstalledVersion(destPath);
173
- const packageVersion = getPackageVersion(sourcePath);
242
+ // Get latest version from npm registry (with 24h cache)
243
+ const latestVersion = getLatestVersionFromRegistry(destPath, force);
174
244
  if (!installedVersion) {
175
245
  ui_1.logger.warn('Skills are not installed.');
176
- ui_1.logger.info(`Available version: ${packageVersion || 'unknown'}`);
246
+ ui_1.logger.info(`Available version: ${latestVersion || 'unknown'}`);
177
247
  ui_1.logger.info('Run `mbc install-skills` to install.');
178
248
  return;
179
249
  }
180
- if (!packageVersion) {
181
- ui_1.logger.warn('Could not determine package version.');
250
+ if (!latestVersion) {
251
+ ui_1.logger.warn('Could not determine latest version. Check your network connection.');
182
252
  ui_1.logger.info(`Installed version: ${installedVersion}`);
183
253
  return;
184
254
  }
185
- if (installedVersion === packageVersion) {
255
+ if (installedVersion === latestVersion) {
186
256
  ui_1.logger.success(`Skills are up to date (${installedVersion}).`);
187
257
  }
188
258
  else {
189
- ui_1.logger.warn(`Update available: ${installedVersion} → ${packageVersion}`);
259
+ ui_1.logger.warn(`Update available: ${installedVersion} → ${latestVersion}`);
190
260
  ui_1.logger.info('Run `mbc install-skills --force` to update.');
191
261
  }
192
262
  return;
@@ -203,8 +273,10 @@ async function installSkillsAction(options, command) {
203
273
  // Copy skills
204
274
  ui_1.logger.title('install', `Installing skills to ${destPath}`);
205
275
  const copiedSkills = copySkills(sourcePath, destPath);
276
+ // Get version from npm registry (preferred) or fall back to local package.json
277
+ const latestVersion = getLatestVersionFromRegistry(destPath, true);
278
+ const packageVersion = latestVersion || getPackageVersion(sourcePath);
206
279
  // Write version file
207
- const packageVersion = getPackageVersion(sourcePath);
208
280
  if (packageVersion) {
209
281
  writeVersionFile(destPath, packageVersion);
210
282
  }
@@ -39,7 +39,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
39
39
  const install_skills_action_1 = __importStar(require("./install-skills.action"));
40
40
  jest.mock('fs');
41
41
  jest.mock('os');
42
+ jest.mock('child_process');
42
43
  const fs_1 = require("fs");
44
+ const child_process_1 = require("child_process");
43
45
  const os_1 = __importDefault(require("os"));
44
46
  const mockExistsSync = fs_1.existsSync;
45
47
  const mockMkdirSync = fs_1.mkdirSync;
@@ -47,6 +49,7 @@ const mockCpSync = fs_1.cpSync;
47
49
  const mockReaddirSync = fs_1.readdirSync;
48
50
  const mockReadFileSync = fs_1.readFileSync;
49
51
  const mockWriteFileSync = fs_1.writeFileSync;
52
+ const mockExecSync = child_process_1.execSync;
50
53
  describe('Install Skills Action', () => {
51
54
  const mockCommand = {
52
55
  name: () => 'install-skills',
@@ -55,6 +58,8 @@ describe('Install Skills Action', () => {
55
58
  beforeEach(() => {
56
59
  jest.clearAllMocks();
57
60
  os_1.default.homedir.mockReturnValue('/home/user');
61
+ // Default mock for npm registry - returns a test version
62
+ mockExecSync.mockReturnValue('1.0.25\n');
58
63
  });
59
64
  describe('getSkillsSourcePath', () => {
60
65
  it('should return the path to mcp-server skills directory', () => {
@@ -278,6 +283,81 @@ describe('Install Skills Action', () => {
278
283
  expect(mockWriteFileSync).toHaveBeenCalledWith(expect.stringContaining(install_skills_action_1.VERSION_FILE_NAME), '1.0.25', 'utf-8');
279
284
  });
280
285
  });
286
+ describe('getLatestVersionFromRegistry', () => {
287
+ it('should fetch version from npm registry', () => {
288
+ mockExistsSync.mockReturnValue(false);
289
+ mockExecSync.mockReturnValue('1.0.26\n');
290
+ const version = (0, install_skills_action_1.getLatestVersionFromRegistry)('/dest/skills');
291
+ expect(version).toBe('1.0.26');
292
+ expect(mockExecSync).toHaveBeenCalledWith('npm view @mbc-cqrs-serverless/mcp-server version', expect.objectContaining({ encoding: 'utf-8', timeout: 10000 }));
293
+ });
294
+ it('should use cached version if cache is valid', () => {
295
+ const cacheData = {
296
+ version: '1.0.25',
297
+ checkedAt: new Date().toISOString(), // Fresh cache
298
+ };
299
+ mockExistsSync.mockReturnValue(true);
300
+ mockReadFileSync.mockReturnValue(JSON.stringify(cacheData));
301
+ const version = (0, install_skills_action_1.getLatestVersionFromRegistry)('/dest/skills');
302
+ expect(version).toBe('1.0.25');
303
+ expect(mockExecSync).not.toHaveBeenCalled();
304
+ });
305
+ it('should fetch from registry if cache is expired', () => {
306
+ const cacheData = {
307
+ version: '1.0.24',
308
+ checkedAt: new Date(Date.now() - 25 * 60 * 60 * 1000).toISOString(), // 25 hours ago
309
+ };
310
+ mockExistsSync.mockReturnValue(true);
311
+ mockReadFileSync.mockReturnValue(JSON.stringify(cacheData));
312
+ mockExecSync.mockReturnValue('1.0.26\n');
313
+ const version = (0, install_skills_action_1.getLatestVersionFromRegistry)('/dest/skills');
314
+ expect(version).toBe('1.0.26');
315
+ expect(mockExecSync).toHaveBeenCalled();
316
+ });
317
+ it('should force refresh when forceRefresh is true', () => {
318
+ const cacheData = {
319
+ version: '1.0.24',
320
+ checkedAt: new Date().toISOString(), // Fresh cache
321
+ };
322
+ mockExistsSync.mockReturnValue(true);
323
+ mockReadFileSync.mockReturnValue(JSON.stringify(cacheData));
324
+ mockExecSync.mockReturnValue('1.0.26\n');
325
+ const version = (0, install_skills_action_1.getLatestVersionFromRegistry)('/dest/skills', true);
326
+ expect(version).toBe('1.0.26');
327
+ expect(mockExecSync).toHaveBeenCalled();
328
+ });
329
+ it('should use expired cache as fallback when offline', () => {
330
+ const cacheData = {
331
+ version: '1.0.24',
332
+ checkedAt: new Date(Date.now() - 25 * 60 * 60 * 1000).toISOString(),
333
+ };
334
+ mockExistsSync.mockReturnValue(true);
335
+ mockReadFileSync.mockReturnValue(JSON.stringify(cacheData));
336
+ mockExecSync.mockImplementation(() => {
337
+ throw new Error('Network error');
338
+ });
339
+ const version = (0, install_skills_action_1.getLatestVersionFromRegistry)('/dest/skills');
340
+ expect(version).toBe('1.0.24');
341
+ });
342
+ it('should return null when offline and no cache exists', () => {
343
+ mockExistsSync.mockReturnValue(false);
344
+ mockExecSync.mockImplementation(() => {
345
+ throw new Error('Network error');
346
+ });
347
+ const version = (0, install_skills_action_1.getLatestVersionFromRegistry)('/dest/skills');
348
+ expect(version).toBeNull();
349
+ });
350
+ it('should save version to cache after fetching', () => {
351
+ mockExistsSync.mockReturnValue(true);
352
+ mockExecSync.mockReturnValue('1.0.26\n');
353
+ // Make cache read fail to trigger fetch
354
+ mockReadFileSync.mockImplementation(() => {
355
+ throw new Error('No cache');
356
+ });
357
+ (0, install_skills_action_1.getLatestVersionFromRegistry)('/dest/skills');
358
+ expect(mockWriteFileSync).toHaveBeenCalledWith(expect.stringContaining(install_skills_action_1.VERSION_CACHE_FILE_NAME), expect.stringContaining('"version": "1.0.26"'), 'utf-8');
359
+ });
360
+ });
281
361
  describe('Check option', () => {
282
362
  beforeEach(() => {
283
363
  mockReaddirSync.mockReturnValue([
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mbc-cqrs-serverless/cli",
3
- "version": "1.0.25",
3
+ "version": "1.1.0-beta.0",
4
4
  "description": "a CLI to get started with MBC CQRS serverless framework",
5
5
  "keywords": [
6
6
  "mbc",
@@ -58,5 +58,5 @@
58
58
  "@faker-js/faker": "^8.3.1",
59
59
  "copyfiles": "^2.4.1"
60
60
  },
61
- "gitHead": "26c722916f5c8a71b5c066eca5b8db8c21dc7bcb"
61
+ "gitHead": "b16d8982c02933fd41a455d3e41f9750bbcf668c"
62
62
  }
@@ -12,36 +12,69 @@ COMPOSE_PROJECT_NAME=%%projectName%%
12
12
  LOG_LEVEL=verbose # debug, verbose, info, warn, error, fatal
13
13
  # disable event route for API GW integration
14
14
  EVENT_SOURCE_DISABLED=false
15
+
16
+ # ============================================
17
+ # Local Service Ports
18
+ # Change these if you have port conflicts with other services
19
+ # ============================================
20
+ # Serverless Offline (API Gateway)
21
+ LOCAL_HTTP_PORT=3000
22
+ LOCAL_LAMBDA_PORT=3002
23
+ # DynamoDB Local
24
+ LOCAL_DYNAMODB_PORT=8000
25
+ # MySQL (RDS)
26
+ LOCAL_RDS_PORT=3306
27
+ # LocalStack (S3)
28
+ LOCAL_S3_PORT=4566
29
+ # SNS
30
+ LOCAL_SNS_PORT=4002
31
+ # SQS (ElasticMQ)
32
+ LOCAL_SQS_PORT=9324
33
+ LOCAL_SQS_UI_PORT=9325
34
+ # Step Functions Local
35
+ LOCAL_SFN_PORT=8083
36
+ # Cognito Local
37
+ LOCAL_COGNITO_PORT=9229
38
+ # AppSync Simulator
39
+ LOCAL_APPSYNC_PORT=4001
40
+ # EventBridge
41
+ LOCAL_EVENTBRIDGE_PORT=4010
42
+ LOCAL_EVENTBRIDGE_PUBSUB_PORT=4011
43
+ # SES
44
+ LOCAL_SES_PORT=8005
45
+ # DynamoDB Admin UI
46
+ LOCAL_DDB_ADMIN_PORT=8001
47
+
15
48
  # DynamoDB endpoint, useful for local development
16
- DYNAMODB_ENDPOINT=http://localhost:8000
49
+ DYNAMODB_ENDPOINT=http://localhost:${LOCAL_DYNAMODB_PORT:-8000}
17
50
  DYNAMODB_REGION=ap-northeast-1
18
51
  # set the limit size for `attributes` of object in DDB
19
52
  ATTRIBUTE_LIMIT_SIZE=389120 # bytes, refer to https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/ServiceQuotas.html#limits-attributes
20
53
  # S3 endpoint, useful for local development
21
- S3_ENDPOINT=http://localhost:4566
54
+ S3_ENDPOINT=http://localhost:${LOCAL_S3_PORT:-4566}
22
55
  S3_REGION=ap-northeast-1
23
56
  # save DDB attributes
24
57
  S3_BUCKET_NAME=local-bucket
25
58
  # Step Function endpoint, useful for local development
26
- SFN_ENDPOINT=http://localhost:8083
59
+ SFN_ENDPOINT=http://localhost:${LOCAL_SFN_PORT:-8083}
27
60
  SFN_REGION=ap-northeast-1
28
61
  SFN_COMMAND_ARN=arn:aws:states:ap-northeast-1:101010101010:stateMachine:command
29
62
  SFN_TASK_ARN=arn:aws:states:ap-northeast-1:101010101010:stateMachine:sfn-task
30
63
  # SNS endpoint, useful for local development
31
- SNS_ENDPOINT=http://localhost:4002
64
+ SNS_ENDPOINT=http://localhost:${LOCAL_SNS_PORT:-4002}
32
65
  SNS_REGION=ap-northeast-1
33
66
  SNS_TOPIC_ARN=arn:aws:sns:ap-northeast-1:101010101010:CqrsSnsTopic
34
67
  SNS_ALARM_TOPIC_ARN=arn:aws:sns:ap-northeast-1:101010101010:AlarmSnsTopic
35
68
  # Cognito endpoint, useful for local development
36
- COGNITO_URL=http://localhost:9229
69
+ COGNITO_URL=http://localhost:${LOCAL_COGNITO_PORT:-9229}
37
70
  COGNITO_USER_POOL_ID=local_2G7noHgW
38
71
  COGNITO_USER_POLL_CLIENT_ID=dnk8y7ii3wled35p3lw0l2cd7
39
72
  COGNITO_REGION=ap-northeast-1
40
73
  # AppSync endpoint, useful for local development
41
- APPSYNC_ENDPOINT=http://localhost:4001/graphql
74
+ APPSYNC_ENDPOINT=http://localhost:${LOCAL_APPSYNC_PORT:-4001}/graphql
42
75
  APPSYNC_API_KEY=da2-fakeApiId123456
43
76
  # SES email endpoint, useful for local development
44
- SES_ENDPOINT=http://localhost:8005
77
+ SES_ENDPOINT=http://localhost:${LOCAL_SES_PORT:-8005}
45
78
  SES_REGION=ap-northeast-1
46
79
  SES_FROM_EMAIL=email@example.com
47
80
 
@@ -52,7 +85,7 @@ SES_FROM_EMAIL=email@example.com
52
85
  # Prisma supports the native connection string format for PostgreSQL, MySQL, SQLite, SQL Server, MongoDB and CockroachDB.
53
86
  # See the documentation for all the connection string options: https://pris.ly/d/connection-strings
54
87
 
55
- DATABASE_URL="mysql://root:RootCqrs@localhost:3306/cqrs?schema=public&connection_limit=1"
88
+ DATABASE_URL="mysql://root:RootCqrs@localhost:${LOCAL_RDS_PORT:-3306}/cqrs?schema=public&connection_limit=1"
56
89
 
57
90
  # serverless dynamodb local stream
58
91
  LOCAL_DDB_SAMPLE_STREAM=arn:aws:dynamodb:ddblocal:000000000000:table/local-test-cli-sample-command/stream/2025-01-14T12:05:57.881
@@ -406,7 +406,6 @@
406
406
  "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.67.tgz",
407
407
  "integrity": "sha512-wI8uHusga+0ZugNp0Ol/3BqQfEcCCNfojtO6Oou9iVNGPTL6QNSdnUdqq85fRgIorLhLMuPIKpsN98QE9Nh+KQ==",
408
408
  "license": "MIT",
409
- "peer": true,
410
409
  "dependencies": {
411
410
  "undici-types": "~5.26.4"
412
411
  }
@@ -651,7 +650,6 @@
651
650
  "mime-types"
652
651
  ],
653
652
  "license": "Apache-2.0",
654
- "peer": true,
655
653
  "dependencies": {
656
654
  "@aws-cdk/asset-awscli-v1": "^2.2.208",
657
655
  "@aws-cdk/asset-kubectl-v20": "^2.1.3",
@@ -1544,9 +1542,9 @@
1544
1542
  }
1545
1543
  },
1546
1544
  "node_modules/diff": {
1547
- "version": "4.0.2",
1548
- "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
1549
- "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
1545
+ "version": "4.0.4",
1546
+ "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.4.tgz",
1547
+ "integrity": "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==",
1550
1548
  "license": "BSD-3-Clause",
1551
1549
  "engines": {
1552
1550
  "node": ">=0.3.1"
@@ -2208,7 +2206,6 @@
2208
2206
  "resolved": "https://registry.npmjs.org/graphql/-/graphql-14.7.0.tgz",
2209
2207
  "integrity": "sha512-l0xWZpoPKpppFzMfvVyFmp9vLN7w/ZZJPefUicMCepfJeQ8sMcztloGYY9DfjVPo6tIUDzU5Hw3MUbIjj9AVVA==",
2210
2208
  "license": "MIT",
2211
- "peer": true,
2212
2209
  "dependencies": {
2213
2210
  "iterall": "^1.2.2"
2214
2211
  },
@@ -4293,7 +4290,6 @@
4293
4290
  "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz",
4294
4291
  "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==",
4295
4292
  "license": "Apache-2.0",
4296
- "peer": true,
4297
4293
  "bin": {
4298
4294
  "tsc": "bin/tsc",
4299
4295
  "tsserver": "bin/tsserver"
@@ -3,15 +3,15 @@ services:
3
3
  stepfunctions-local:
4
4
  image: amazon/aws-stepfunctions-local
5
5
  ports:
6
- - 8083:8083
6
+ - ${LOCAL_SFN_PORT:-8083}:8083
7
7
  environment:
8
8
  - AWS_ACCOUNT_ID=101010101010
9
9
  - AWS_DEFAULT_REGION=ap-northeast-1
10
- - LAMBDA_ENDPOINT=http://host.docker.internal:3002
11
- - SQS_ENDPOINT=http://host.docker.internal:9324
12
- - SNS_ENDPOINT=http://host.docker.internal:4002
13
- - DYNAMODB_ENDPOINT=http://host.docker.internal:8000
14
- - STEP_FUNCTIONS_ENDPOINT=http://host.docker.internal:8083
10
+ - LAMBDA_ENDPOINT=http://host.docker.internal:${LOCAL_LAMBDA_PORT:-3002}
11
+ - SQS_ENDPOINT=http://host.docker.internal:${LOCAL_SQS_PORT:-9324}
12
+ - SNS_ENDPOINT=http://host.docker.internal:${LOCAL_SNS_PORT:-4002}
13
+ - DYNAMODB_ENDPOINT=http://host.docker.internal:${LOCAL_DYNAMODB_PORT:-8000}
14
+ - STEP_FUNCTIONS_ENDPOINT=http://host.docker.internal:${LOCAL_SFN_PORT:-8083}
15
15
  - ECS_ENDPOINT=VALUE
16
16
  extra_hosts:
17
17
  - 'host.docker.internal:host-gateway'
@@ -24,12 +24,12 @@ services:
24
24
  volumes:
25
25
  - ./docker-data/mysql:/var/lib/mysql
26
26
  ports:
27
- - 3306:3306
27
+ - ${LOCAL_RDS_PORT:-3306}:3306
28
28
 
29
29
  dynamodb-local:
30
30
  image: amazon/dynamodb-local
31
31
  ports:
32
- - 8000:8000
32
+ - ${LOCAL_DYNAMODB_PORT:-8000}:8000
33
33
  volumes:
34
34
  - ./docker-data/dynamodb-local:/home/dynamodblocal/data
35
35
  working_dir: /home/dynamodblocal
@@ -43,15 +43,15 @@ services:
43
43
  - AWS_ACCESS_KEY_ID=local
44
44
  - AWS_SECRET_ACCESS_KEY=local
45
45
  ports:
46
- - 8001:8001
46
+ - ${LOCAL_DDB_ADMIN_PORT:-8001}:8001
47
47
  depends_on:
48
48
  - dynamodb-local
49
49
 
50
50
  queue:
51
51
  image: softwaremill/elasticmq-native:latest
52
52
  ports:
53
- - 9324:9324 # sqs
54
- - 9325:9325 # ui
53
+ - ${LOCAL_SQS_PORT:-9324}:9324 # sqs
54
+ - ${LOCAL_SQS_UI_PORT:-9325}:9325 # ui
55
55
  volumes:
56
56
  - ./elasticmq.conf:/opt/elasticmq.conf
57
57
  - ./docker-data/elasticmq:/data
@@ -59,7 +59,7 @@ services:
59
59
  localstack:
60
60
  image: localstack/localstack
61
61
  ports:
62
- - '4566:4566'
62
+ - '${LOCAL_S3_PORT:-4566}:4566'
63
63
  - '4510-4559:4510-4559'
64
64
  environment:
65
65
  - SERVICES=s3
@@ -76,7 +76,7 @@ services:
76
76
  context: ./appsync-simulator
77
77
  dockerfile: Dockerfile
78
78
  ports:
79
- - 4001:4001
79
+ - ${LOCAL_APPSYNC_PORT:-4001}:4001
80
80
  environment:
81
81
  - PORT=4001
82
82
  - API_KEY=da2-fakeApiId123456
@@ -86,6 +86,6 @@ services:
86
86
  context: ./cognito-local
87
87
  dockerfile: Dockerfile
88
88
  ports:
89
- - 9229:9229
89
+ - ${LOCAL_COGNITO_PORT:-9229}:9229
90
90
  volumes:
91
91
  - ./docker-data/.cognito:/app/.cognito
@@ -4,9 +4,7 @@ $env:AWS_ACCOUNT_ID = "101010101010"
4
4
  $env:AWS_ACCESS_KEY_ID = "local"
5
5
  $env:AWS_SECRET_ACCESS_KEY = "local"
6
6
 
7
- $endpoint = "http://localhost:8000"
8
-
9
- # Load environment variables from .env file (assuming you have a utility to load it)
7
+ # Load environment variables from .env file
10
8
  Get-Content .env | ForEach-Object {
11
9
  if ($_ -match "^\s*#") {
12
10
  return
@@ -19,6 +17,22 @@ Get-Content .env | ForEach-Object {
19
17
  }
20
18
  }
21
19
 
20
+ # Build table name prefix from environment variables
21
+ # Default: NODE_ENV=local, APP_NAME from .env
22
+ $tablePrefix = if ($env:NODE_ENV) { $env:NODE_ENV } else { "local" }
23
+ $tablePrefix = "$tablePrefix-$env:APP_NAME"
24
+
25
+ # Get ports from environment variables with defaults
26
+ $dynamodbPort = if ($env:LOCAL_DYNAMODB_PORT) { $env:LOCAL_DYNAMODB_PORT } else { "8000" }
27
+ $httpPort = if ($env:LOCAL_HTTP_PORT) { $env:LOCAL_HTTP_PORT } else { "3000" }
28
+
29
+ $endpoint = "http://localhost:$dynamodbPort"
30
+
31
+ Write-Host "Using configuration:"
32
+ Write-Host " TABLE_PREFIX: $tablePrefix"
33
+ Write-Host " DynamoDB endpoint: $endpoint"
34
+ Write-Host " Serverless HTTP port: $httpPort"
35
+
22
36
  Write-Host "Read table name"
23
37
 
24
38
  # Read table names from JSON file
@@ -34,9 +48,8 @@ foreach ($table in $tables) {
34
48
  exit 1
35
49
  }
36
50
 
37
- Write-Host "Check health table local-$env:APP_NAME-$table-command"
38
- Write-Host "local-$env:APP_NAME-$table-command"
39
- $status = aws --endpoint $endpoint dynamodb describe-table --table-name "local-$env:APP_NAME-$table-command" --query "Table.TableStatus"
51
+ Write-Host "Check health table $tablePrefix-$table-command"
52
+ $status = aws --endpoint $endpoint dynamodb describe-table --table-name "$tablePrefix-$table-command" --query "Table.TableStatus"
40
53
 
41
54
  Write-Host "Table status: $status"
42
55
  if ($status -eq '"ACTIVE"') {
@@ -59,7 +72,7 @@ while ($true) {
59
72
  }
60
73
 
61
74
  Write-Host "Check health table tasks"
62
- $status = aws --endpoint $endpoint dynamodb describe-table --table-name "local-$env:APP_NAME-tasks" --query "Table.TableStatus"
75
+ $status = aws --endpoint $endpoint dynamodb describe-table --table-name "$tablePrefix-tasks" --query "Table.TableStatus"
63
76
 
64
77
  Write-Host "Table status: $status"
65
78
  if ($status -eq '"ACTIVE"') {
@@ -82,7 +95,7 @@ while ($true) {
82
95
 
83
96
  Write-Host "Check health serverless"
84
97
  try {
85
- $response = Invoke-WebRequest -Uri "http://localhost:3000" -UseBasicParsing -ErrorAction Stop
98
+ $response = Invoke-WebRequest -Uri "http://localhost:$httpPort" -UseBasicParsing -ErrorAction Stop
86
99
  $status = $response.StatusCode
87
100
  } catch {
88
101
  if ($_.Exception.Response -ne $null) {
@@ -122,12 +135,12 @@ foreach ($table in $tables) {
122
135
 
123
136
  Write-Host "Send a item to trigger command $table"
124
137
 
125
- aws dynamodb put-item --endpoint http://localhost:8000 --table-name "local-$env:APP_NAME-$table-command" --item $escapedJsonItemString
138
+ aws dynamodb put-item --endpoint $endpoint --table-name "$tablePrefix-$table-command" --item $escapedJsonItemString
126
139
  }
127
140
 
128
- # Trigger asks stream
141
+ # Trigger tasks stream
129
142
  Write-Host "Send a command to trigger command stream tasks"
130
143
  $command = @"
131
- aws dynamodb put-item --endpoint http://localhost:8000 --table-name "local-$env:APP_NAME-tasks" --item '{\"input\":{\"M\":{}},\"sk\":{\"S\":\"$timestamp\"},\"pk\":{\"S\":\"test\"}}'
144
+ aws dynamodb put-item --endpoint $endpoint --table-name "$tablePrefix-tasks" --item '{\"input\":{\"M\":{}},\"sk\":{\"S\":\"$timestamp\"},\"pk\":{\"S\":\"test\"}}'
132
145
  "@
133
146
  Invoke-Expression $command
@@ -5,10 +5,24 @@ export AWS_ACCOUNT_ID=101010101010
5
5
  export AWS_ACCESS_KEY_ID=local
6
6
  export AWS_SECRET_ACCESS_KEY=local
7
7
 
8
- endpoint='http://localhost:8000'
9
-
8
+ # Load environment variables from .env file
10
9
  source .env
11
10
 
11
+ # Build table name prefix from environment variables
12
+ # Default: NODE_ENV=local, APP_NAME from .env
13
+ TABLE_PREFIX="${NODE_ENV:-local}-${APP_NAME}"
14
+
15
+ # Get ports from environment variables with defaults
16
+ DYNAMODB_PORT="${LOCAL_DYNAMODB_PORT:-8000}"
17
+ HTTP_PORT="${LOCAL_HTTP_PORT:-3000}"
18
+
19
+ endpoint="http://localhost:${DYNAMODB_PORT}"
20
+
21
+ echo "Using configuration:"
22
+ echo " TABLE_PREFIX: ${TABLE_PREFIX}"
23
+ echo " DynamoDB endpoint: ${endpoint}"
24
+ echo " Serverless HTTP port: ${HTTP_PORT}"
25
+
12
26
  echo "Read table name"
13
27
  declare -a tables
14
28
  while IFS= read -r line; do
@@ -27,7 +41,7 @@ for table in "${tables[@]}"; do
27
41
  fi
28
42
 
29
43
  echo "Check health table ${table}"
30
- status=$(aws --endpoint=${endpoint} dynamodb describe-table --table-name local-${APP_NAME}-${table}-command --query 'Table.TableStatus')
44
+ status=$(aws --endpoint=${endpoint} dynamodb describe-table --table-name ${TABLE_PREFIX}-${table}-command --query 'Table.TableStatus')
31
45
  echo "Table status: ${status}"
32
46
  if [[ "${status}" == "\"ACTIVE\"" ]]; then
33
47
  echo "Table ${table} is ACTIVE"
@@ -48,7 +62,7 @@ while true; do
48
62
  fi
49
63
 
50
64
  echo "Check health table tasks"
51
- status=$(aws --endpoint=${endpoint} dynamodb describe-table --table-name local-${APP_NAME}-tasks --query 'Table.TableStatus')
65
+ status=$(aws --endpoint=${endpoint} dynamodb describe-table --table-name ${TABLE_PREFIX}-tasks --query 'Table.TableStatus')
52
66
  echo "Table status: ${status}"
53
67
  if [[ "${status}" == "\"ACTIVE\"" ]]; then
54
68
  echo "Table tasks is ACTIVE"
@@ -70,7 +84,7 @@ while true; do
70
84
  fi
71
85
 
72
86
  echo "Check health serverless"
73
- status=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:3000)
87
+ status=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:${HTTP_PORT})
74
88
  echo "Serverless status: ${status}"
75
89
  if [[ "${status}" == "200" ]]; then
76
90
  echo "Serverless is ACTIVE"
@@ -85,8 +99,8 @@ done
85
99
  timestamp=$(date +%s)
86
100
  for table in "${tables[@]}"; do
87
101
  echo "Send a command to trigger command stream ${table}"
88
- aws --endpoint=${endpoint} dynamodb put-item --table-name local-${APP_NAME}-${table}-command --item "{\"pk\": {\"S\": \"test\" }, \"sk\": { \"S\": \"${timestamp}\" }}"
102
+ aws --endpoint=${endpoint} dynamodb put-item --table-name ${TABLE_PREFIX}-${table}-command --item "{\"pk\": {\"S\": \"test\" }, \"sk\": { \"S\": \"${timestamp}\" }}"
89
103
  done
90
104
 
91
105
  echo "Send a command to trigger command stream tasks"
92
- aws --endpoint=http://localhost:8000 dynamodb put-item --table-name local-demo-tasks --item "{\"input\":{\"M\":{}},\"sk\":{\"S\":\"${timestamp}\"},\"pk\":{\"S\":\"test\"}}"
106
+ aws --endpoint=${endpoint} dynamodb put-item --table-name ${TABLE_PREFIX}-tasks --item "{\"input\":{\"M\":{}},\"sk\":{\"S\":\"${timestamp}\"},\"pk\":{\"S\":\"test\"}}"
@@ -14,23 +14,35 @@ plugins:
14
14
  - serverless-offline
15
15
 
16
16
  custom:
17
+ # Port configuration - override via environment variables in .env
18
+ httpPort: ${env:LOCAL_HTTP_PORT, 3000}
19
+ lambdaPort: ${env:LOCAL_LAMBDA_PORT, 3002}
20
+ dynamodbPort: ${env:LOCAL_DYNAMODB_PORT, 8000}
21
+ sqsPort: ${env:LOCAL_SQS_PORT, 9324}
22
+ snsPort: ${env:LOCAL_SNS_PORT, 4002}
23
+ eventBridgePort: ${env:LOCAL_EVENTBRIDGE_PORT, 4010}
24
+ sesPort: ${env:LOCAL_SES_PORT, 8005}
25
+ cognitoPort: ${env:LOCAL_COGNITO_PORT, 9229}
26
+
17
27
  serverless-offline:
18
28
  corsAllowOrigin: '*'
19
29
  corsAllowHeaders: '*'
20
30
  host: 0.0.0.0
31
+ httpPort: ${self:custom.httpPort}
32
+ lambdaPort: ${self:custom.lambdaPort}
21
33
  reloadHandler: true
22
34
  ignoreJWTSignature: true
23
35
  serverless-offline-ses-v2:
24
- port: 8005
36
+ port: ${self:custom.sesPort}
25
37
  serverless-offline-sns:
26
- port: 4002 # a free port for the sns server to run on
38
+ port: ${self:custom.snsPort}
27
39
  debug: true
28
40
  subscriptions:
29
41
  - topic:
30
42
  topicName: CqrsSnsTopic
31
43
  rawMessageDelivery: 'true'
32
44
  filterPolicy: { 'action': ['task-execute'] }
33
- queue: http://localhost:9324/101010101010/task-action-queue
45
+ queue: http://localhost:${self:custom.sqsPort}/101010101010/task-action-queue
34
46
  - topic:
35
47
  topicName: CqrsSnsTopic
36
48
  rawMessageDelivery: 'true'
@@ -39,21 +51,21 @@ custom:
39
51
  'action':
40
52
  ['notification-action', 'command-status', 'task-status'],
41
53
  }
42
- queue: http://localhost:9324/101010101010/notification-queue
54
+ queue: http://localhost:${self:custom.sqsPort}/101010101010/notification-queue
43
55
  - topic:
44
56
  topicName: CqrsSnsTopic
45
57
  rawMessageDelivery: 'true'
46
58
  filterPolicy: { 'action': ['sub-task-status'] }
47
- queue: http://localhost:9324/101010101010/sub-task-status-queue
59
+ queue: http://localhost:${self:custom.sqsPort}/101010101010/sub-task-status-queue
48
60
  - topic:
49
61
  topicName: CqrsSnsTopic
50
62
  rawMessageDelivery: 'true'
51
63
  filterPolicy: { 'action': ['import-execute'] }
52
- queue: http://localhost:9324/101010101010/import-action-queue
64
+ queue: http://localhost:${self:custom.sqsPort}/101010101010/import-action-queue
53
65
  - topic:
54
66
  topicName: AlarmSnsTopic
55
67
  rawMessageDelivery: 'true'
56
- queue: http://localhost:9324/101010101010/alarm-queue
68
+ queue: http://localhost:${self:custom.sqsPort}/101010101010/alarm-queue
57
69
  # host: 0.0.0.0 # Optional, defaults to 127.0.0.1 if not provided to serverless-offline
58
70
  # sns-endpoint: http://127.0.0.1:4567 # Optional. Only if you want to use a custom SNS provider endpoint
59
71
  # sns-subscribe-endpoint: http://127.0.0.1:3000 # Optional. Only if you want to use a custom subscribe endpoint from SNS to send messages back to
@@ -63,7 +75,7 @@ custom:
63
75
  accountId: 101010101010
64
76
  debug: true
65
77
  apiVersion: '2012-11-05'
66
- endpoint: http://localhost:9324
78
+ endpoint: http://localhost:${self:custom.sqsPort}
67
79
  region: ap-northeast-1
68
80
  accessKeyId: root
69
81
  secretAccessKey: root
@@ -80,19 +92,19 @@ custom:
80
92
  start:
81
93
  docker: true
82
94
  inMemory: true
83
- port: 8000
95
+ port: ${self:custom.dynamodbPort}
84
96
  noStart: true
85
97
  seed: true
86
98
  migrate: false # create tables on start
87
99
  onStart: false
88
100
  convertEmptyValues: true
89
101
  serverless-offline-dynamodb-streams:
90
- endpoint: http://localhost:8000
102
+ endpoint: http://localhost:${self:custom.dynamodbPort}
91
103
  serverless-offline-aws-eventbridge:
92
- port: 4010 # port to run the eventBridge mock server on
104
+ port: ${self:custom.eventBridgePort}
93
105
  mockEventBridgeServer: true # Set to false if EventBridge is already mocked by another stack
94
106
  hostname: 127.0.0.1 # IP or hostname of existing EventBridge if mocked by another stack
95
- pubSubPort: 4011 # Port to run the MQ server (or just listen if using an EventBridge Mock server from another stack)
107
+ pubSubPort: ${env:LOCAL_EVENTBRIDGE_PUBSUB_PORT, 4011}
96
108
  debug: false # flag to show debug messages
97
109
  account: '' # account id that gets passed to the event
98
110
  maximumRetryAttempts: 10 # maximumRetryAttempts to retry lambda
@@ -113,7 +125,7 @@ provider:
113
125
  localAuthorizer:
114
126
  type: jwt
115
127
  identitySource: $request.header.Authorization
116
- issuerUrl: http://localhost:9229/local_2G7noHgW
128
+ issuerUrl: http://localhost:${self:custom.cognitoPort}/local_2G7noHgW
117
129
  audience:
118
130
  - dnk8y7ii3wled35p3lw0l2cd7
119
131
  # keycloakAuthorizer: