@geek-fun/serverlessinsight 0.4.1 → 0.5.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.
Files changed (164) hide show
  1. package/.gitattributes +1 -0
  2. package/README.md +108 -8
  3. package/README.zh-CN.md +52 -8
  4. package/dist/package.json +37 -35
  5. package/dist/src/commands/deploy.js +17 -7
  6. package/dist/src/commands/destroy.js +27 -4
  7. package/dist/src/commands/forceUnlock.js +61 -0
  8. package/dist/src/commands/index.js +41 -5
  9. package/dist/src/commands/local.js +10 -1
  10. package/dist/src/commands/plan.js +33 -0
  11. package/dist/src/commands/template.js +1 -1
  12. package/dist/src/commands/validate.js +2 -1
  13. package/dist/src/common/aliyunClient/apigwOperations.js +652 -0
  14. package/dist/src/common/aliyunClient/dnsOperations.js +90 -0
  15. package/dist/src/common/aliyunClient/ecsOperations.js +141 -0
  16. package/dist/src/common/aliyunClient/esOperations.js +219 -0
  17. package/dist/src/common/aliyunClient/fc3Operations.js +270 -0
  18. package/dist/src/common/aliyunClient/index.js +141 -0
  19. package/dist/src/common/aliyunClient/nasOperations.js +233 -0
  20. package/dist/src/common/aliyunClient/ossOperations.js +237 -0
  21. package/dist/src/common/aliyunClient/ramOperations.js +205 -0
  22. package/dist/src/common/aliyunClient/rdsOperations.js +206 -0
  23. package/dist/src/common/aliyunClient/slsOperations.js +218 -0
  24. package/dist/src/common/aliyunClient/tablestoreOperations.js +199 -0
  25. package/dist/src/common/aliyunClient/types.js +2 -0
  26. package/dist/src/common/constants.js +6 -1
  27. package/dist/src/common/context.js +13 -3
  28. package/dist/src/common/credentials.js +30 -6
  29. package/dist/src/common/dependencyGraph/graph.js +280 -0
  30. package/dist/src/common/dependencyGraph/index.js +18 -0
  31. package/dist/src/common/dependencyGraph/types.js +2 -0
  32. package/dist/src/common/fileUtils.js +16 -0
  33. package/dist/src/common/hashUtils.js +121 -0
  34. package/dist/src/common/iacHelper.js +25 -97
  35. package/dist/src/common/imsClient.js +4 -0
  36. package/dist/src/common/index.js +6 -2
  37. package/dist/src/common/lockManager.js +212 -0
  38. package/dist/src/common/logger.js +87 -10
  39. package/dist/src/common/providerEnum.js +2 -3
  40. package/dist/src/common/runtimeMapper.js +160 -0
  41. package/dist/src/common/scfClient.js +84 -0
  42. package/dist/src/common/stateManager.js +107 -0
  43. package/dist/src/common/tencentClient/cosOperations.js +287 -0
  44. package/dist/src/common/tencentClient/esOperations.js +156 -0
  45. package/dist/src/common/tencentClient/index.js +116 -0
  46. package/dist/src/common/tencentClient/scfOperations.js +141 -0
  47. package/dist/src/common/tencentClient/tdsqlcOperations.js +211 -0
  48. package/dist/src/common/tencentClient/types.js +17 -0
  49. package/dist/src/lang/en.js +254 -0
  50. package/dist/src/lang/index.js +28 -8
  51. package/dist/src/lang/zh-CN.js +229 -0
  52. package/dist/src/parser/bucketParser.js +25 -12
  53. package/dist/src/parser/databaseParser.js +14 -10
  54. package/dist/src/parser/functionParser.js +19 -6
  55. package/dist/src/parser/parseUtils.js +74 -0
  56. package/dist/src/parser/tableParser.js +19 -17
  57. package/dist/src/stack/aliyunStack/apigwExecutor.js +84 -0
  58. package/dist/src/stack/aliyunStack/apigwPlanner.js +118 -0
  59. package/dist/src/stack/aliyunStack/apigwResource.js +339 -0
  60. package/dist/src/stack/aliyunStack/apigwTypes.js +125 -0
  61. package/dist/src/stack/aliyunStack/databaseExecutor.js +112 -0
  62. package/dist/src/stack/aliyunStack/databasePlanner.js +128 -0
  63. package/dist/src/stack/aliyunStack/databaseResource.js +228 -0
  64. package/dist/src/stack/aliyunStack/deployer.js +133 -0
  65. package/dist/src/stack/aliyunStack/destroyer.js +114 -0
  66. package/dist/src/stack/aliyunStack/esServerlessTypes.js +141 -0
  67. package/dist/src/stack/aliyunStack/fc3Executor.js +91 -0
  68. package/dist/src/stack/aliyunStack/fc3Planner.js +77 -0
  69. package/dist/src/stack/aliyunStack/fc3Resource.js +511 -0
  70. package/dist/src/stack/aliyunStack/fc3Types.js +76 -0
  71. package/dist/src/stack/aliyunStack/index.js +40 -0
  72. package/dist/src/stack/aliyunStack/ossExecutor.js +91 -0
  73. package/dist/src/stack/aliyunStack/ossPlanner.js +76 -0
  74. package/dist/src/stack/aliyunStack/ossResource.js +196 -0
  75. package/dist/src/stack/aliyunStack/ossTypes.js +50 -0
  76. package/dist/src/stack/aliyunStack/planner.js +37 -0
  77. package/dist/src/stack/aliyunStack/rdsTypes.js +217 -0
  78. package/dist/src/stack/aliyunStack/tablestoreExecutor.js +92 -0
  79. package/dist/src/stack/aliyunStack/tablestorePlanner.js +94 -0
  80. package/dist/src/stack/aliyunStack/tablestoreResource.js +120 -0
  81. package/dist/src/stack/aliyunStack/tablestoreTypes.js +77 -0
  82. package/dist/src/stack/bucketTypes.js +17 -0
  83. package/dist/src/stack/deploy.js +24 -77
  84. package/dist/src/stack/localStack/bucket.js +11 -6
  85. package/dist/src/stack/localStack/event.js +10 -5
  86. package/dist/src/stack/localStack/function.js +13 -7
  87. package/dist/src/stack/localStack/functionRunner.js +1 -1
  88. package/dist/src/stack/localStack/localServer.js +7 -6
  89. package/dist/src/stack/scfStack/cosExecutor.js +91 -0
  90. package/dist/src/stack/scfStack/cosPlanner.js +76 -0
  91. package/dist/src/stack/scfStack/cosResource.js +126 -0
  92. package/dist/src/stack/scfStack/cosTypes.js +46 -0
  93. package/dist/src/stack/scfStack/deployer.js +91 -0
  94. package/dist/src/stack/scfStack/destroyer.js +88 -0
  95. package/dist/src/stack/scfStack/esServerlessExecutor.js +105 -0
  96. package/dist/src/stack/scfStack/esServerlessPlanner.js +86 -0
  97. package/dist/src/stack/scfStack/esServerlessResource.js +94 -0
  98. package/dist/src/stack/scfStack/esServerlessTypes.js +48 -0
  99. package/dist/src/stack/scfStack/index.js +35 -0
  100. package/dist/src/stack/scfStack/planner.js +91 -0
  101. package/dist/src/stack/scfStack/scfExecutor.js +91 -0
  102. package/dist/src/stack/scfStack/scfPlanner.js +78 -0
  103. package/dist/src/stack/scfStack/scfResource.js +216 -0
  104. package/dist/src/stack/scfStack/scfTypes.js +41 -0
  105. package/dist/src/stack/scfStack/tdsqlcExecutor.js +105 -0
  106. package/dist/src/stack/scfStack/tdsqlcPlanner.js +90 -0
  107. package/dist/src/stack/scfStack/tdsqlcResource.js +146 -0
  108. package/dist/src/stack/scfStack/tdsqlcTypes.js +59 -0
  109. package/dist/src/types/domains/lock.js +2 -0
  110. package/dist/src/types/domains/resolvable.js +2 -0
  111. package/dist/src/types/domains/state.js +19 -0
  112. package/dist/src/types/index.js +4 -0
  113. package/dist/src/validator/bucketSchema.js +4 -10
  114. package/dist/src/validator/databaseSchema.js +36 -36
  115. package/dist/src/validator/eventSchema.js +3 -2
  116. package/dist/src/validator/functionSchema.js +51 -46
  117. package/dist/src/validator/iacSchema.js +35 -1
  118. package/dist/src/validator/rootSchema.js +1 -1
  119. package/dist/src/validator/tableschema.js +9 -8
  120. package/dist/src/validator/templateRefSchema.js +23 -0
  121. package/dist/tsconfig.tsbuildinfo +1 -1
  122. package/package.json +37 -35
  123. package/samples/README_TENCENT_COS.md +486 -0
  124. package/samples/README_TENCENT_SCF.md +272 -0
  125. package/samples/aliyun-poc-api.yml +1 -1
  126. package/samples/aliyun-poc-bucket.yml +0 -1
  127. package/samples/aliyun-poc-domain.yml +0 -1
  128. package/samples/aliyun-poc-es.yml +4 -7
  129. package/samples/aliyun-poc-rds.yml +0 -2
  130. package/samples/aliyun-poc-table.yml +1 -3
  131. package/samples/tencent-poc-cos.yml +20 -0
  132. package/samples/tencent-poc-scf.yml +36 -0
  133. package/dist/src/commands/index.d.ts +0 -2
  134. package/dist/src/common/index.d.ts +0 -12
  135. package/dist/src/common/rosAssets.js +0 -178
  136. package/dist/src/common/rosClient.js +0 -201
  137. package/dist/src/index.d.ts +0 -1
  138. package/dist/src/lang/index.d.ts +0 -3
  139. package/dist/src/parser/index.d.ts +0 -3
  140. package/dist/src/stack/index.d.ts +0 -1
  141. package/dist/src/stack/localStack/index.d.ts +0 -5
  142. package/dist/src/stack/rfsStack/index.d.ts +0 -9
  143. package/dist/src/stack/rosStack/bootstrap.js +0 -187
  144. package/dist/src/stack/rosStack/bucket.js +0 -127
  145. package/dist/src/stack/rosStack/database.js +0 -313
  146. package/dist/src/stack/rosStack/event.js +0 -143
  147. package/dist/src/stack/rosStack/function.js +0 -259
  148. package/dist/src/stack/rosStack/index.d.ts +0 -7
  149. package/dist/src/stack/rosStack/index.js +0 -75
  150. package/dist/src/stack/rosStack/stage.js +0 -46
  151. package/dist/src/stack/rosStack/table.js +0 -95
  152. package/dist/src/stack/rosStack/tag.js +0 -11
  153. package/dist/src/stack/rosStack/vars.js +0 -49
  154. package/dist/src/types/index.d.ts +0 -55
  155. package/dist/src/types/localStack/index.d.ts +0 -81
  156. package/dist/src/validator/index.d.ts +0 -1
  157. package/layers/si-bootstrap-sdk/Dockerfile-aliyuncli +0 -12
  158. package/layers/si-bootstrap-sdk/README.md +0 -64
  159. package/layers/si-bootstrap-sdk/package-lock.json +0 -881
  160. package/layers/si-bootstrap-sdk/package.json +0 -33
  161. package/layers/si-bootstrap-sdk/support/operation-collection/README.md +0 -47
  162. package/layers/si-bootstrap-sdk/support/operation-collection/package-lock.json +0 -298
  163. package/layers/si-bootstrap-sdk/support/operation-collection/package.json +0 -18
  164. package/layers/si-bootstrap-sdk/support/operation-collection/publish.js +0 -257
package/.gitattributes ADDED
@@ -0,0 +1 @@
1
+ * text=auto eol=lf
package/README.md CHANGED
@@ -47,14 +47,14 @@ Whether you're building on AWS, Alibaba Cloud, Huawei Cloud, or other providers,
47
47
 
48
48
  ServerlessInsight supports the following cloud providers:
49
49
 
50
- | Provider | Functions | API Gateway | Storage | Databases | Status |
51
- |----------|-----------|-------------|---------|-----------|--------|
52
- | **Alibaba Cloud** | ✅ FC3 | ✅ API Gateway | ✅ OSS | ✅ RDS, OTS, ESS | Stable |
53
- | **Huawei Cloud** | ✅ FunctionGraph | 🚧 Coming Soon | 🚧 Coming Soon | 🚧 Coming Soon | Beta |
54
- | **AWS** | 🔜 Planned | 🔜 Planned | 🔜 Planned | 🔜 Planned | Planned |
55
- | **Azure** | 🔜 Planned | 🔜 Planned | 🔜 Planned | 🔜 Planned | Planned |
56
- | **Google Cloud** | 🔜 Planned | 🔜 Planned | 🔜 Planned | 🔜 Planned | Planned |
57
- | **Tencent Cloud** | 🔜 Planned | 🔜 Planned | 🔜 Planned | 🔜 Planned | Planned |
50
+ | Provider | Functions | API Gateway | Storage | Databases | Status |
51
+ | ----------------- | ---------------- | -------------- | -------------- | ---------------- | ------- |
52
+ | **Alibaba Cloud** | ✅ FC3 | ✅ API Gateway | ✅ OSS | ✅ RDS, OTS, ESS | Stable |
53
+ | **Huawei Cloud** | ✅ FunctionGraph | 🚧 Coming Soon | 🚧 Coming Soon | 🚧 Coming Soon | Beta |
54
+ | **AWS** | 🔜 Planned | 🔜 Planned | 🔜 Planned | 🔜 Planned | Planned |
55
+ | **Azure** | 🔜 Planned | 🔜 Planned | 🔜 Planned | 🔜 Planned | Planned |
56
+ | **Google Cloud** | 🔜 Planned | 🔜 Planned | 🔜 Planned | 🔜 Planned | Planned |
57
+ | **Tencent Cloud** | 🔜 Planned | 🔜 Planned | 🔜 Planned | 🔜 Planned | Planned |
58
58
 
59
59
  ---
60
60
 
@@ -188,6 +188,105 @@ curl http://localhost:4567/si_buckets/<bucket_key>/subdir/
188
188
 
189
189
  The bucket handler serves files from the directory specified in your bucket's `website.code` configuration.
190
190
 
191
+ ### Architecture
192
+
193
+ ServerlessInsight follows a **functional client architecture** that cleanly separates cloud provider SDKs from business logic:
194
+
195
+ #### Layered Design
196
+
197
+ ```
198
+ ┌─────────────────────────────────────────┐
199
+ │ Resource / Planner / Executor Layer │ ← Provider-agnostic business logic
200
+ │ (Pure functions, no SDK imports) │
201
+ └─────────────────────────────────────────┘
202
+
203
+ ┌─────────────────────────────────────────┐
204
+ │ Functional Client Layer │ ← Encapsulated SDK operations
205
+ │ (aliyunClient.ts, tencentClient.ts) │
206
+ └─────────────────────────────────────────┘
207
+
208
+ ┌─────────────────────────────────────────┐
209
+ │ Cloud Provider SDKs │ ← @alicloud/*, tencentcloud-*
210
+ └─────────────────────────────────────────┘
211
+ ```
212
+
213
+ #### Key Principles
214
+
215
+ - **Functional & Pure**: All business logic uses pure functions with immutable data
216
+ - **SDK Encapsulation**: Provider SDKs are accessed ONLY through client modules
217
+ - **Operation Functions**: Clients expose high-level operation functions (e.g., `createFunction`, `getFunction`)
218
+ - **No Classes**: Prefer `type` over `interface`, and functional patterns over classes
219
+ - **Minimal Side Effects**: File I/O and other side effects are isolated at boundaries
220
+
221
+ #### Client Pattern Example
222
+
223
+ ```typescript
224
+ // ✅ Client exposes operation functions, not raw SDKs
225
+ export type AliyunClient = {
226
+ fc3: {
227
+ createFunction: (config: Fc3FunctionConfig, codeBase64: string) => Promise<void>;
228
+ getFunction: (functionName: string) => Promise<Fc3FunctionInfo | null>;
229
+ // ... other operations
230
+ };
231
+ // ... other services
232
+ };
233
+
234
+ // ✅ Resource layer uses clean client API
235
+ const client = createAliyunClient(context);
236
+ const codeBase64 = readFileAsBase64(codePath);
237
+ await client.fc3.createFunction(config, codeBase64);
238
+ ```
239
+
240
+ This architecture enables:
241
+
242
+ - **Better Testing**: Mock client operations instead of SDKs
243
+ - **Maintainability**: Provider changes isolated to client layer
244
+ - **Type Safety**: Strong typing throughout the stack
245
+ - **Code Reuse**: Shared client operations across resources
246
+
247
+ ---
248
+
249
+ ## 🗄️ State Management
250
+
251
+ ServerlessInsight uses a state-based deployment model that tracks all deployed resources in a local state file (`.serverlessinsight/state.json`). This enables:
252
+
253
+ - **Incremental deployments** - Only changed resources are updated
254
+ - **Drift detection** - Identifies differences between local state and cloud resources
255
+ - **Safe destruction** - Knows which resources to clean up
256
+
257
+ ### Partial Failure Recovery
258
+
259
+ When deploying multiple resources, if one fails, ServerlessInsight ensures that:
260
+
261
+ 1. **Successfully deployed resources are always saved to state** - Even if subsequent resources fail, your state file will contain all resources that were successfully created/updated/deleted
262
+ 2. **Clear error reporting** - You'll see which resources succeeded and which failed
263
+ 3. **Easy retry** - Simply run `deploy` again to continue from where you left off
264
+
265
+ Example partial failure output:
266
+
267
+ ```
268
+ ⚠️ PARTIAL DEPLOYMENT FAILURE: 2 resource(s) succeeded, but functions.api_handler failed.
269
+ State has been saved for successfully deployed resources. Run deploy again to retry failed resources.
270
+ Next steps: 1) Review the error above, 2) Fix any configuration issues, 3) Run deploy again to continue.
271
+ ```
272
+
273
+ ### State File Location
274
+
275
+ The state file is stored in `.serverlessinsight/state.json` in your project directory. This file:
276
+
277
+ - Should be committed to version control for team collaboration
278
+ - Contains resource metadata and identifiers
279
+ - Is automatically updated after each successful operation
280
+
281
+ ### State Recovery
282
+
283
+ If you encounter state drift (cloud resources exist that aren't in your state file):
284
+
285
+ 1. **Manual import** - Currently, resources must be manually added to the state file
286
+ 2. **Clean start** - Delete the state file and resources in the cloud, then redeploy
287
+
288
+ > 💡 **Tip**: Always backup your state file before making manual modifications.
289
+
191
290
  ---
192
291
 
193
292
  ## 📘 Documentation
@@ -195,6 +294,7 @@ The bucket handler serves files from the directory specified in your bucket's `w
195
294
  For comprehensive documentation, visit [serverlessinsight.geekfun.club](https://serverlessinsight.geekfun.club)
196
295
 
197
296
  Additional resources:
297
+
198
298
  - [Quick Start Guide](https://serverlessinsight.geekfun.club)
199
299
  - [Configuration Reference](https://serverlessinsight.geekfun.club)
200
300
  - [API Documentation](https://serverlessinsight.geekfun.club)
package/README.zh-CN.md CHANGED
@@ -47,14 +47,14 @@ ServerlessInsight 是一个强大的 Serverless 框架,用于跨多个云服
47
47
 
48
48
  ServerlessInsight 支持以下云服务商:
49
49
 
50
- | 云服务商 | 函数 | API 网关 | 存储 | 数据库 | 状态 |
51
- |----------|------|----------|------|--------|------|
52
- | **阿里云** | ✅ FC3 | ✅ API 网关 | ✅ OSS | ✅ RDS, OTS, ESS | 稳定 |
53
- | **华为云** | ✅ FunctionGraph | 🚧 即将推出 | 🚧 即将推出 | 🚧 即将推出 | 测试版 |
54
- | **AWS** | 🔜 计划中 | 🔜 计划中 | 🔜 计划中 | 🔜 计划中 | 计划中 |
55
- | **Azure** | 🔜 计划中 | 🔜 计划中 | 🔜 计划中 | 🔜 计划中 | 计划中 |
56
- | **Google Cloud** | 🔜 计划中 | 🔜 计划中 | 🔜 计划中 | 🔜 计划中 | 计划中 |
57
- | **腾讯云** | 🔜 计划中 | 🔜 计划中 | 🔜 计划中 | 🔜 计划中 | 计划中 |
50
+ | 云服务商 | 函数 | API 网关 | 存储 | 数据库 | 状态 |
51
+ | ---------------- | ---------------- | ----------- | ----------- | ---------------- | ------ |
52
+ | **阿里云** | ✅ FC3 | ✅ API 网关 | ✅ OSS | ✅ RDS, OTS, ESS | 稳定 |
53
+ | **华为云** | ✅ FunctionGraph | 🚧 即将推出 | 🚧 即将推出 | 🚧 即将推出 | 测试版 |
54
+ | **AWS** | 🔜 计划中 | 🔜 计划中 | 🔜 计划中 | 🔜 计划中 | 计划中 |
55
+ | **Azure** | 🔜 计划中 | 🔜 计划中 | 🔜 计划中 | 🔜 计划中 | 计划中 |
56
+ | **Google Cloud** | 🔜 计划中 | 🔜 计划中 | 🔜 计划中 | 🔜 计划中 | 计划中 |
57
+ | **腾讯云** | 🔜 计划中 | 🔜 计划中 | 🔜 计划中 | 🔜 计划中 | 计划中 |
58
58
 
59
59
  ---
60
60
 
@@ -178,6 +178,7 @@ npm run lint:check
178
178
  完整文档请访问 [serverlessinsight.geekfun.club](https://serverlessinsight.geekfun.club)
179
179
 
180
180
  其他资源:
181
+
181
182
  - [快速开始指南](https://serverlessinsight.geekfun.club)
182
183
  - [配置参考](https://serverlessinsight.geekfun.club)
183
184
  - [API 文档](https://serverlessinsight.geekfun.club)
@@ -185,6 +186,49 @@ npm run lint:check
185
186
 
186
187
  ---
187
188
 
189
+ ## 🗄️ 状态管理
190
+
191
+ ServerlessInsight 使用基于状态的部署模型,在本地状态文件 (`.serverlessinsight/state.json`) 中跟踪所有已部署的资源。这使得:
192
+
193
+ - **增量部署** - 只有更改的资源才会被更新
194
+ - **漂移检测** - 识别本地状态与云资源之间的差异
195
+ - **安全销毁** - 知道要清理哪些资源
196
+
197
+ ### 部分失败恢复
198
+
199
+ 当部署多个资源时,如果其中一个失败,ServerlessInsight 确保:
200
+
201
+ 1. **成功部署的资源始终保存到状态** - 即使后续资源失败,您的状态文件也将包含所有成功创建/更新/删除的资源
202
+ 2. **清晰的错误报告** - 您将看到哪些资源成功,哪些资源失败
203
+ 3. **轻松重试** - 只需再次运行 `deploy` 即可从中断的地方继续
204
+
205
+ 部分失败输出示例:
206
+
207
+ ```
208
+ ⚠️ 部署部分失败: 2 个资源成功,但 functions.api_handler 失败。
209
+ 已成功部署的资源状态已保存。再次运行 deploy 以重试失败的资源。
210
+ 后续步骤: 1) 查看上面的错误, 2) 修复配置问题, 3) 再次运行 deploy 继续。
211
+ ```
212
+
213
+ ### 状态文件位置
214
+
215
+ 状态文件存储在项目目录的 `.serverlessinsight/state.json` 中。此文件:
216
+
217
+ - 应提交到版本控制以便团队协作
218
+ - 包含资源元数据和标识符
219
+ - 在每次成功操作后自动更新
220
+
221
+ ### 状态恢复
222
+
223
+ 如果遇到状态漂移(云资源存在但不在状态文件中):
224
+
225
+ 1. **手动导入** - 目前,资源必须手动添加到状态文件
226
+ 2. **重新开始** - 删除状态文件和云中的资源,然后重新部署
227
+
228
+ > 💡 **提示**:在进行手动修改之前,请始终备份您的状态文件。
229
+
230
+ ---
231
+
188
232
  ## 🤝 贡献
189
233
 
190
234
  我们欢迎社区的贡献!以下是您可以帮助的方式:
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@geek-fun/serverlessinsight",
3
- "version": "0.4.1",
3
+ "version": "0.5.0",
4
4
  "description": "Full life cycle cross providers serverless application management for your fast-growing business.",
5
5
  "homepage": "https://serverlessinsight.geekfun.club",
6
6
  "main": "dist/src/index.js",
@@ -9,7 +9,7 @@
9
9
  "si": "dist/src/commands/index.js"
10
10
  },
11
11
  "scripts": {
12
- "test": "DEBUG=ServerlessInsight jest --runInBand --detectOpenHandles --coverage --coverageReporters json-summary text html lcov",
12
+ "test": "cross-env DEBUG=ServerlessInsight jest --runInBand --detectOpenHandles --coverage --coverageReporters json-summary text html lcov",
13
13
  "test:ci": "jest --runInBand --ci --coverage --coverageReporters json-summary text html lcov",
14
14
  "build": "tsc --build",
15
15
  "lint:fix": "eslint --fix ./",
@@ -49,54 +49,56 @@
49
49
  "function"
50
50
  ],
51
51
  "dependencies": {
52
- "@alicloud/ims20190815": "^2.3.2",
52
+ "@alicloud/alidns20150109": "^4.3.1",
53
+ "@alicloud/cloudapi20160714": "^4.7.9",
54
+ "@alicloud/ecs20140526": "^7.6.0",
55
+ "@alicloud/es-serverless20230627": "^2.3.0",
56
+ "@alicloud/fc20230330": "^4.6.8",
57
+ "@alicloud/ims20190815": "^2.3.3",
58
+ "@alicloud/nas20170626": "^3.3.1",
53
59
  "@alicloud/openapi-client": "^0.4.15",
54
- "@alicloud/ros-cdk-apigateway": "^1.11.0",
55
- "@alicloud/ros-cdk-core": "^1.11.0",
56
- "@alicloud/ros-cdk-dns": "^1.11.0",
57
- "@alicloud/ros-cdk-ecs": "^1.11.0",
58
- "@alicloud/ros-cdk-elasticsearchserverless": "^1.11.0",
59
- "@alicloud/ros-cdk-fc3": "^1.11.0",
60
- "@alicloud/ros-cdk-nas": "^1.11.0",
61
- "@alicloud/ros-cdk-oss": "^1.11.0",
62
- "@alicloud/ros-cdk-ossdeployment": "^1.11.0",
63
- "@alicloud/ros-cdk-ots": "^1.11.0",
64
- "@alicloud/ros-cdk-ram": "^1.11.0",
65
- "@alicloud/ros-cdk-rds": "^1.11.0",
66
- "@alicloud/ros-cdk-ros": "^1.11.0",
67
- "@alicloud/ros-cdk-sls": "^1.11.0",
68
- "@alicloud/ros-cdk-vpc": "^1.11.0",
69
- "@alicloud/ros20190910": "^3.6.0",
70
- "ajv": "^8.17.1",
60
+ "@alicloud/ram20150501": "^1.2.0",
61
+ "@alicloud/rds20140815": "^15.5.1",
62
+ "@alicloud/sls20201230": "^5.9.0",
63
+ "ajv": "^8.18.0",
71
64
  "ali-oss": "^6.23.0",
72
- "chalk": "^5.6.2",
73
- "commander": "^14.0.2",
74
- "i": "^0.3.7",
65
+ "commander": "^14.0.3",
66
+ "cos-nodejs-sdk-v5": "^2.16.0-beta.8",
75
67
  "i18n": "^0.15.3",
68
+ "iconv-lite": "^0.7.2",
76
69
  "jszip": "^3.10.1",
77
- "lodash": "^4.17.21",
78
- "npm": "^11.7.0",
79
- "pino": "^10.1.0",
70
+ "lodash": "^4.17.23",
71
+ "pino": "^10.3.1",
80
72
  "pino-pretty": "^13.1.3",
73
+ "tablestore": "^5.6.3",
74
+ "tencentcloud-sdk-nodejs-cynosdb": "^4.1.188",
75
+ "tencentcloud-sdk-nodejs-es": "^4.1.183",
76
+ "tencentcloud-sdk-nodejs-scf": "^4.1.168",
81
77
  "yaml": "^2.8.2"
82
78
  },
83
79
  "devDependencies": {
84
- "@types/ali-oss": "^6.16.13",
80
+ "@eslint/eslintrc": "^3.3.4",
81
+ "@eslint/js": "^10.0.1",
82
+ "@types/ali-oss": "^6.23.3",
85
83
  "@types/i18n": "^0.13.12",
86
84
  "@types/jest": "^30.0.0",
87
- "@types/lodash": "^4.17.21",
88
- "@types/node": "^25.0.3",
89
- "@typescript-eslint/eslint-plugin": "^8.50.0",
90
- "@typescript-eslint/parser": "^8.50.0",
91
- "eslint": "^9.39.2",
85
+ "@types/lodash": "^4.17.24",
86
+ "@types/node": "^25.3.2",
87
+ "@typescript-eslint/eslint-plugin": "^8.56.1",
88
+ "@typescript-eslint/parser": "^8.56.1",
89
+ "cross-env": "^10.1.0",
90
+ "eslint": "^10.0.2",
92
91
  "eslint-config-prettier": "^10.1.8",
93
- "eslint-plugin-prettier": "^5.5.4",
94
- "globals": "^16.5.0",
92
+ "eslint-plugin-prettier": "^5.5.5",
93
+ "globals": "^17.3.0",
95
94
  "husky": "^9.1.7",
96
95
  "jest": "^30.2.0",
97
- "prettier": "^3.7.4",
96
+ "prettier": "^3.8.1",
98
97
  "ts-jest": "^29.4.6",
99
98
  "ts-node": "^10.9.2",
100
99
  "typescript": "^5.9.3"
100
+ },
101
+ "overrides": {
102
+ "fast-xml-parser": ">=5.3.8"
101
103
  }
102
104
  }
@@ -4,13 +4,23 @@ exports.deploy = void 0;
4
4
  const stack_1 = require("../stack");
5
5
  const common_1 = require("../common");
6
6
  const parser_1 = require("../parser");
7
+ const lang_1 = require("../lang");
7
8
  const deploy = async (stackName, options) => {
8
- common_1.logger.info('Validating yaml...');
9
- const iac = (0, parser_1.parseYaml)((0, common_1.getIacLocation)(options.location));
10
- common_1.logger.info('Yaml is valid! 🎉');
11
- await (0, common_1.setContext)({ ...options, stackName, iacProvider: iac.provider }, true);
12
- common_1.logger.info('Deploying stack...');
13
- await (0, stack_1.deployStack)(stackName, iac);
14
- common_1.logger.info('Stack deployed! 🎉');
9
+ common_1.logger.info(lang_1.lang.__('VALIDATING_YAML'));
10
+ const iacLocation = (0, common_1.getIacLocation)(options.location);
11
+ const rawIac = (0, parser_1.parseYaml)(iacLocation);
12
+ common_1.logger.info(lang_1.lang.__('YAML_VALID'));
13
+ await (0, common_1.setContext)({ ...options, stackName, iacProvider: rawIac.provider, stages: rawIac.stages }, true);
14
+ const context = (0, common_1.getContext)();
15
+ const iac = (0, parser_1.revalYaml)(iacLocation, context);
16
+ // Store IAC in context for access by all functions
17
+ (0, common_1.setIac)(iac);
18
+ common_1.logger.info(lang_1.lang.__('DEPLOYING_STACK'));
19
+ // Acquire lock for the deployment operation
20
+ const statePath = (0, common_1.getStatePath)();
21
+ await (0, common_1.withLock)(statePath, 'deploy', async () => {
22
+ await (0, stack_1.deployStack)(stackName, iac);
23
+ });
24
+ common_1.logger.info(lang_1.lang.__('STACK_DEPLOYED'));
15
25
  };
16
26
  exports.deploy = deploy;
@@ -3,11 +3,34 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.destroyStack = void 0;
4
4
  const common_1 = require("../common");
5
5
  const parser_1 = require("../parser");
6
+ const lang_1 = require("../lang");
7
+ const scfStack_1 = require("../stack/scfStack");
8
+ const aliyunStack_1 = require("../stack/aliyunStack");
6
9
  const destroyStack = async (stackName, options) => {
7
- const iac = (0, parser_1.parseYaml)((0, common_1.getIacLocation)(options.location));
8
- await (0, common_1.setContext)({ stackName, ...options, iacProvider: iac.provider }, true);
10
+ const iacLocation = (0, common_1.getIacLocation)(options.location);
11
+ const rawIac = (0, parser_1.parseYaml)(iacLocation);
12
+ await (0, common_1.setContext)({ stackName, ...options, iacProvider: rawIac.provider, stages: rawIac.stages }, true);
9
13
  const context = (0, common_1.getContext)();
10
- common_1.logger.info(`Destroying stack: ${stackName}, provider: ${context.provider}, region: ${context.region}...`);
11
- await (0, common_1.rosStackDelete)(context);
14
+ const iac = (0, parser_1.revalYaml)(iacLocation, context);
15
+ // Store IAC in context for access by all functions
16
+ (0, common_1.setIac)(iac);
17
+ common_1.logger.info(lang_1.lang.__('DESTROYING_STACK', {
18
+ stackName,
19
+ provider: context.provider,
20
+ region: context.region,
21
+ }));
22
+ // Acquire lock for the destroy operation
23
+ const statePath = (0, common_1.getStatePath)();
24
+ await (0, common_1.withLock)(statePath, 'destroy', async () => {
25
+ if (iac.provider.name === common_1.ProviderEnum.TENCENT) {
26
+ await (0, scfStack_1.destroyTencentStack)();
27
+ }
28
+ else if (iac.provider.name === common_1.ProviderEnum.ALIYUN) {
29
+ await (0, aliyunStack_1.destroyAliyunStack)();
30
+ }
31
+ else {
32
+ throw new Error(`Unsupported provider: ${iac.provider.name}`);
33
+ }
34
+ });
12
35
  };
13
36
  exports.destroyStack = destroyStack;
@@ -0,0 +1,61 @@
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.forceUnlockCommand = void 0;
7
+ const node_readline_1 = __importDefault(require("node:readline"));
8
+ const common_1 = require("../common");
9
+ const lang_1 = require("../lang");
10
+ const askConfirmation = (question) => {
11
+ const rl = node_readline_1.default.createInterface({
12
+ input: process.stdin,
13
+ output: process.stdout,
14
+ });
15
+ return new Promise((resolve) => {
16
+ rl.question(question, (answer) => {
17
+ rl.close();
18
+ resolve(answer.toLowerCase().trim() === 'yes');
19
+ });
20
+ });
21
+ };
22
+ const forceUnlockCommand = async (lockId) => {
23
+ const statePath = (0, common_1.getStatePath)();
24
+ // Check if lock exists
25
+ const existingLock = (0, common_1.readLockFileForCommand)(statePath);
26
+ if (!existingLock) {
27
+ common_1.logger.error(lang_1.lang.__('NO_LOCK_FOUND'));
28
+ throw new Error(lang_1.lang.__('NO_LOCK_FOUND'));
29
+ }
30
+ // Verify lock ID matches
31
+ if (existingLock.id !== lockId) {
32
+ common_1.logger.error(lang_1.lang.__('LOCK_ID_MISMATCH', {
33
+ providedId: lockId,
34
+ actualId: existingLock.id,
35
+ }));
36
+ throw new Error(lang_1.lang.__('LOCK_ID_MISMATCH', {
37
+ providedId: lockId,
38
+ actualId: existingLock.id,
39
+ }));
40
+ }
41
+ // Show lock info
42
+ common_1.logger.info(lang_1.lang.__('CURRENT_LOCK_INFO'));
43
+ common_1.logger.info((0, common_1.formatLockInfo)(existingLock));
44
+ // Ask for confirmation
45
+ common_1.logger.warn(lang_1.lang.__('FORCE_UNLOCK_WARNING'));
46
+ const confirmed = await askConfirmation(lang_1.lang.__('FORCE_UNLOCK_CONFIRM'));
47
+ if (!confirmed) {
48
+ common_1.logger.info(lang_1.lang.__('FORCE_UNLOCK_CANCELLED'));
49
+ return;
50
+ }
51
+ // Force unlock
52
+ const success = (0, common_1.forceUnlock)(statePath, lockId);
53
+ if (success) {
54
+ common_1.logger.info(lang_1.lang.__('LOCK_RELEASED'));
55
+ }
56
+ else {
57
+ common_1.logger.error(lang_1.lang.__('FAILED_TO_RELEASE_LOCK'));
58
+ throw new Error(lang_1.lang.__('FAILED_TO_RELEASE_LOCK'));
59
+ }
60
+ };
61
+ exports.forceUnlockCommand = forceUnlockCommand;
@@ -8,12 +8,21 @@ const deploy_1 = require("./deploy");
8
8
  const template_1 = require("./template");
9
9
  const destroy_1 = require("./destroy");
10
10
  const local_1 = require("./local");
11
+ const plan_1 = require("./plan");
12
+ const forceUnlock_1 = require("./forceUnlock");
13
+ const lang_1 = require("../lang");
11
14
  // Global error handler
12
15
  const handleCommandError = (error, commandName) => {
13
- // Log error message as string to preserve newlines
14
- common_1.logger.error(`Command '${commandName}' failed with error:\n${error?.message || 'Unknown error occurred'}`);
16
+ // Skip logging if already logged by handlePartialFailure
17
+ if (!error?.isPartialFailure) {
18
+ // Log error message as string to preserve newlines
19
+ common_1.logger.error(lang_1.lang.__('COMMAND_FAILED', {
20
+ commandName,
21
+ error: error?.message || 'Unknown error occurred',
22
+ }));
23
+ }
15
24
  if (error?.stack && process.env.DEBUG) {
16
- common_1.logger.debug(`Stack trace:\n${error.stack}`);
25
+ common_1.logger.debug(lang_1.lang.__('STACK_TRACE', { stack: error.stack }));
17
26
  }
18
27
  let exitCode = 1;
19
28
  if (error?.code) {
@@ -53,7 +62,7 @@ program
53
62
  await (0, common_1.setContext)({ ...options });
54
63
  const context = (0, common_1.getContext)();
55
64
  const result = await (0, common_1.getIamInfo)(context);
56
- console.log('result:', JSON.stringify(result));
65
+ console.log(lang_1.lang.__('RESULT', { result: JSON.stringify(result) }));
57
66
  }));
58
67
  program
59
68
  .command('validate [stackName]')
@@ -61,9 +70,30 @@ program
61
70
  .option('-f, --file <path>', 'specify the yaml file')
62
71
  .option('-s, --stage <stage>', 'specify the stage')
63
72
  .action(actionWrapper('validate', async (stackName, { file, stage }) => {
64
- common_1.logger.debug('log command info');
73
+ common_1.logger.debug(lang_1.lang.__('LOG_COMMAND_INFO'));
65
74
  await (0, validate_1.validate)(stackName, { stage, location: file });
66
75
  }));
76
+ program
77
+ .command('plan <stackName>')
78
+ .description('generate and show deployment plan (Tencent provider only)')
79
+ .option('-f, --file <path>', 'specify the yaml file')
80
+ .option('-s, --stage <stage>', 'specify the stage')
81
+ .option('-r, --region <region>', 'specify the region')
82
+ .option('-v, --provider <provider>', 'specify the provider')
83
+ .option('-k, --accessKeyId <accessKeyId>', 'specify the AccessKeyId')
84
+ .option('-x, --accessKeySecret <accessKeySecret>', 'specify the AccessKeySecret')
85
+ .option('-n, --securityToken <securityToken>', 'specify the SecurityToken')
86
+ .action(actionWrapper('plan', async (stackName, { stage, file, region, provider, accessKeyId, accessKeySecret, securityToken }) => {
87
+ await (0, plan_1.plan)(stackName, {
88
+ stage,
89
+ location: file,
90
+ region,
91
+ provider,
92
+ accessKeyId,
93
+ accessKeySecret,
94
+ securityToken,
95
+ });
96
+ }));
67
97
  program
68
98
  .command('deploy <stackName>')
69
99
  .description('deploy serverless Iac yaml')
@@ -136,4 +166,10 @@ program
136
166
  location: file,
137
167
  });
138
168
  }));
169
+ program
170
+ .command('force-unlock <lockId>')
171
+ .description('manually remove a stuck lock (use with caution)')
172
+ .action(actionWrapper('force-unlock', async (lockId) => {
173
+ await (0, forceUnlock_1.forceUnlockCommand)(lockId);
174
+ }));
139
175
  program.parse();
@@ -4,12 +4,21 @@ exports.runLocal = void 0;
4
4
  const common_1 = require("../common");
5
5
  const localStack_1 = require("../stack/localStack");
6
6
  const parser_1 = require("../parser");
7
+ const lang_1 = require("../lang");
7
8
  const runLocal = async (stackName, opts) => {
8
9
  const { stage, port, debug, watch, location } = opts;
9
10
  await (0, common_1.setContext)({ stage, location });
10
11
  const ctx = (0, common_1.getContext)();
11
12
  const iac = (0, parser_1.revalYaml)((0, common_1.getIacLocation)(location), ctx);
12
- common_1.logger.info(`run-local starting: stack=${stackName} stage=${stage} port=${port} debug=${debug} watch=${watch}`);
13
+ // Store IAC in context for access by all functions
14
+ (0, common_1.setIac)(iac);
15
+ common_1.logger.info(lang_1.lang.__('RUN_LOCAL_STARTING', {
16
+ stackName,
17
+ stage,
18
+ port: String(port),
19
+ debug: String(debug),
20
+ watch: String(watch),
21
+ }));
13
22
  await (0, localStack_1.startLocalStack)(iac);
14
23
  // if (watch) {
15
24
  // const cwd = process.cwd();
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.plan = void 0;
4
+ const common_1 = require("../common");
5
+ const parser_1 = require("../parser");
6
+ const scfStack_1 = require("../stack/scfStack");
7
+ const aliyunStack_1 = require("../stack/aliyunStack");
8
+ const lang_1 = require("../lang");
9
+ const plan = async (stackName, options) => {
10
+ common_1.logger.info(lang_1.lang.__('VALIDATING_YAML'));
11
+ const iacLocation = (0, common_1.getIacLocation)(options.location);
12
+ const rawIac = (0, parser_1.parseYaml)(iacLocation);
13
+ common_1.logger.info(lang_1.lang.__('YAML_VALID'));
14
+ await (0, common_1.setContext)({ ...options, stackName, iacProvider: rawIac.provider, stages: rawIac.stages }, true);
15
+ const context = (0, common_1.getContext)();
16
+ const iac = (0, parser_1.revalYaml)(iacLocation, context);
17
+ // Store IAC in context for access by all functions
18
+ (0, common_1.setIac)(iac);
19
+ common_1.logger.info(lang_1.lang.__('GENERATING_PLAN_FOR_SCF'));
20
+ let planResult;
21
+ if (iac.provider.name === common_1.ProviderEnum.TENCENT) {
22
+ planResult = await (0, scfStack_1.generateTencentPlan)(iac);
23
+ }
24
+ else if (iac.provider.name === common_1.ProviderEnum.ALIYUN) {
25
+ planResult = await (0, aliyunStack_1.generateAliyunPlan)(iac);
26
+ }
27
+ else {
28
+ common_1.logger.error(lang_1.lang.__('PLAN_COMMAND_NOT_SUPPORTED'));
29
+ throw new Error(lang_1.lang.__('PLAN_COMMAND_NOT_SUPPORTED'));
30
+ }
31
+ (0, scfStack_1.displayPlan)(planResult);
32
+ };
33
+ exports.plan = plan;
@@ -11,7 +11,7 @@ const common_1 = require("../common");
11
11
  const parser_1 = require("../parser");
12
12
  const template = async (stackName, options) => {
13
13
  const iac = (0, parser_1.parseYaml)((0, common_1.getIacLocation)(options.location));
14
- const credentials = (0, common_1.getCredentials)();
14
+ const credentials = (0, common_1.getCredentials)(undefined, iac.provider.name);
15
15
  const shouldFetchIamInfo = (0, common_1.hasCredentials)(credentials);
16
16
  await (0, common_1.setContext)({ ...options, stackName, provider: iac.provider.name }, shouldFetchIamInfo);
17
17
  const { template } = (0, deploy_1.generateStackTemplate)(stackName, iac);
@@ -3,10 +3,11 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.validate = void 0;
4
4
  const common_1 = require("../common");
5
5
  const parser_1 = require("../parser");
6
+ const lang_1 = require("../lang");
6
7
  const validate = async (stackName, options) => {
7
8
  await (0, common_1.setContext)({ stackName, ...options });
8
9
  const context = (0, common_1.getContext)();
9
10
  (0, parser_1.parseYaml)(context.iacLocation);
10
- common_1.logger.info('Yaml is valid! 🎉');
11
+ common_1.logger.info(lang_1.lang.__('YAML_VALID'));
11
12
  };
12
13
  exports.validate = validate;