@geek-fun/serverlessinsight 0.6.11 → 0.6.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@geek-fun/serverlessinsight",
3
- "version": "0.6.11",
3
+ "version": "0.6.13",
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",
@@ -40,27 +40,19 @@ const createOssOperations = (ossClient, region, dnsOps) => {
40
40
  const useBucket = (bucketName) => {
41
41
  ossClient.useBucket(bucketName);
42
42
  };
43
- const getBucketExtranetEndpoint = async (bucketName) => {
43
+ const getBucketCnameEndpoint = async (bucketName) => {
44
44
  const infoResult = await ossClient.getBucketInfo(bucketName);
45
- const endpoint = infoResult.bucket.ExtranetEndpoint;
46
- if (!endpoint) {
47
- throw new Error(lang_1.lang.__('OSS_BUCKET_EXTRANET_ENDPOINT_NOT_FOUND', { bucketName }));
45
+ const bucket = infoResult.bucket;
46
+ const actualBucketName = bucket.Name || bucketName;
47
+ const location = bucket.Location;
48
+ if (!location) {
49
+ throw new Error(lang_1.lang.__('OSS_BUCKET_LOCATION_NOT_FOUND', { bucketName }));
48
50
  }
49
- return endpoint;
50
- };
51
- const getCnameEndpointFromExtranet = (extranetEndpoint) => {
52
- const suffix = '.aliyuncs.com';
53
- if (!extranetEndpoint.endsWith(suffix)) {
54
- throw new Error(lang_1.lang.__('OSS_BUCKET_ENDPOINT_INVALID_FORMAT', { extranetEndpoint }));
55
- }
56
- const withoutAliyun = extranetEndpoint.slice(0, -suffix.length);
57
- const lastDotIndex = withoutAliyun.lastIndexOf('.');
58
- if (lastDotIndex <= 0) {
59
- throw new Error(lang_1.lang.__('OSS_BUCKET_ENDPOINT_INVALID_FORMAT', { extranetEndpoint }));
60
- }
61
- const bucketPart = withoutAliyun.slice(0, lastDotIndex);
62
- const regionWithout = withoutAliyun.slice(lastDotIndex + 1).replace(/^oss-/, '');
63
- return `${bucketPart}.${regionWithout}.taihangcda.cn`;
51
+ const derivedRegion = location.replace(/^oss-/, '');
52
+ const isChinaRegion = derivedRegion.startsWith('cn-');
53
+ return isChinaRegion
54
+ ? `${actualBucketName}.${derivedRegion}.taihangcda.cn`
55
+ : `${actualBucketName}.oss-${derivedRegion}.aliyuncs.com`;
64
56
  };
65
57
  const buildCertificateXml = (cert) => {
66
58
  if (!cert)
@@ -244,8 +236,7 @@ const createOssOperations = (ossClient, region, dnsOps) => {
244
236
  const normalizedDomain = (0, domainUtils_1.normalizeDomain)(domain);
245
237
  const mainDomain = (0, domainUtils_1.extractMainDomain)(normalizedDomain);
246
238
  const hostRecord = (0, domainUtils_1.extractHostRecord)(normalizedDomain, mainDomain);
247
- const extranetEndpoint = await getBucketExtranetEndpoint(bucketName);
248
- const ossEndpoint = getCnameEndpointFromExtranet(extranetEndpoint);
239
+ const ossEndpoint = await getBucketCnameEndpoint(bucketName);
249
240
  let cnameResult = await putBucketCname(bucketName, normalizedDomain, certificate);
250
241
  let txtRecordId;
251
242
  if (cnameResult.needVerification) {
@@ -276,7 +267,7 @@ const createOssOperations = (ossClient, region, dnsOps) => {
276
267
  const tokenInfo = await createCnameToken(bucketName, domain);
277
268
  const txtRecordName = '_dnsauth';
278
269
  const hostRecord = (0, domainUtils_1.extractHostRecord)(domain, (0, domainUtils_1.extractMainDomain)(domain));
279
- const fullTxtRecord = hostRecord ? `${txtRecordName}.${hostRecord}` : txtRecordName;
270
+ const fullTxtRecord = hostRecord && hostRecord !== '@' ? `${txtRecordName}.${hostRecord}` : txtRecordName;
280
271
  logger_1.logger.info(lang_1.lang.__('OSS_CNAME_VERIFICATION_TOKEN_CREATED', {
281
272
  domain,
282
273
  txtRecord: fullTxtRecord,
@@ -398,7 +389,7 @@ const createOssOperations = (ossClient, region, dnsOps) => {
398
389
  }
399
390
  const hostRecord = (0, domainUtils_1.extractHostRecord)(domain, (0, domainUtils_1.extractMainDomain)(domain));
400
391
  const txtRecordName = '_dnsauth';
401
- const fullTxtRecord = hostRecord ? `${txtRecordName}.${hostRecord}` : txtRecordName;
392
+ const fullTxtRecord = hostRecord && hostRecord !== '@' ? `${txtRecordName}.${hostRecord}` : txtRecordName;
402
393
  logger_1.logger.warn(lang_1.lang.__('OSS_CNAME_VERIFICATION_RETRY_FAILED', {
403
394
  domain,
404
395
  txtRecord: `${fullTxtRecord}.${(0, domainUtils_1.extractMainDomain)(domain)}`,
@@ -3,20 +3,41 @@ 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.diffAttributes = exports.attributesEqual = exports.computeFileHash = void 0;
6
+ exports.diffAttributes = exports.attributesEqual = exports.computeDirectoryHash = exports.computeFileHash = void 0;
7
7
  const node_crypto_1 = __importDefault(require("node:crypto"));
8
8
  const node_fs_1 = __importDefault(require("node:fs"));
9
- /**
10
- * Compute SHA-256 hash of a file.
11
- * Used for tracking external artifacts like function code zip files.
12
- * @param filePath - Path to the file to hash
13
- * @returns Hex-encoded SHA-256 hash of the file contents
14
- */
9
+ const node_path_1 = __importDefault(require("node:path"));
15
10
  const computeFileHash = (filePath) => {
16
11
  const fileBuffer = node_fs_1.default.readFileSync(filePath);
17
12
  return node_crypto_1.default.createHash('sha256').update(fileBuffer).digest('hex');
18
13
  };
19
14
  exports.computeFileHash = computeFileHash;
15
+ const computeDirectoryHash = (dirPath) => {
16
+ const files = [];
17
+ const collectFiles = (currentPath) => {
18
+ const entries = node_fs_1.default.readdirSync(currentPath, { withFileTypes: true });
19
+ for (const entry of entries) {
20
+ const fullPath = node_path_1.default.join(currentPath, entry.name);
21
+ if (entry.isDirectory()) {
22
+ collectFiles(fullPath);
23
+ }
24
+ else if (entry.isFile()) {
25
+ files.push(fullPath);
26
+ }
27
+ }
28
+ };
29
+ collectFiles(dirPath);
30
+ files.sort();
31
+ const hash = node_crypto_1.default.createHash('sha256');
32
+ for (const file of files) {
33
+ const relativePath = node_path_1.default.relative(dirPath, file).split(node_path_1.default.sep).join('/');
34
+ hash.update(relativePath);
35
+ const content = node_fs_1.default.readFileSync(file);
36
+ hash.update(content);
37
+ }
38
+ return hash.digest('hex');
39
+ };
40
+ exports.computeDirectoryHash = computeDirectoryHash;
20
41
  /**
21
42
  * Deep equality comparison for two values.
22
43
  * Handles primitives, objects, arrays, null, and undefined.
@@ -330,7 +330,7 @@ exports.en = {
330
330
  OSS_BUCKET_PUBLIC_ACCESS_BLOCK_DISABLE_FAILED: 'Failed to disable Block Public Access for bucket {{bucketName}}: {{error}}. ' +
331
331
  'You may need to manually disable Block Public Access in the Alibaba Cloud OSS Console.',
332
332
  OSS_BUCKET_EXTRANET_ENDPOINT_NOT_FOUND: 'ExtranetEndpoint not found for bucket: {{bucketName}}. The bucket may not have public access enabled.',
333
- OSS_BUCKET_ENDPOINT_INVALID_FORMAT: 'Invalid OSS endpoint format: {{extranetEndpoint}}. Expected format: {bucket}.oss-{region}.aliyuncs.com',
333
+ OSS_BUCKET_LOCATION_NOT_FOUND: 'Location not found for bucket: {{bucketName}}',
334
334
  // Tencent COS DNS messages
335
335
  COS_DNS_CNAME_CREATED: 'Created DNS CNAME record: {{domain}} -> {{cname}}',
336
336
  COS_DNS_CNAME_EXISTS: 'DNS CNAME record already exists: {{domain}} -> {{cname}}',
@@ -328,7 +328,7 @@ exports.zhCN = {
328
328
  OSS_BUCKET_PUBLIC_ACCESS_BLOCK_DISABLED: '已为存储桶禁用公共访问阻止: {{bucketName}}',
329
329
  OSS_BUCKET_PUBLIC_ACCESS_BLOCK_DISABLE_FAILED: '禁用存储桶 {{bucketName}} 的公共访问阻止失败:{{error}}。您可能需要在阿里云 OSS 控制台手动禁用公共访问阻止。',
330
330
  OSS_BUCKET_EXTRANET_ENDPOINT_NOT_FOUND: '存储桶 {{bucketName}} 未找到外网访问端点。该存储桶可能未启用公共访问。',
331
- OSS_BUCKET_ENDPOINT_INVALID_FORMAT: 'OSS 端点格式无效:{{extranetEndpoint}}。预期格式:{bucket}.oss-{region}.aliyuncs.com',
331
+ OSS_BUCKET_LOCATION_NOT_FOUND: '存储桶 {{bucketName}} 未找到地域信息',
332
332
  // Tencent COS DNS messages
333
333
  COS_DNS_CNAME_CREATED: '已创建 DNS CNAME 记录: {{domain}} -> {{cname}}',
334
334
  COS_DNS_CNAME_EXISTS: 'DNS CNAME 记录已存在: {{domain}} -> {{cname}}',
@@ -5,6 +5,32 @@ const common_1 = require("../../common");
5
5
  const aliyunClient_1 = require("../../common/aliyunClient");
6
6
  const fc3Types_1 = require("./fc3Types");
7
7
  const lang_1 = require("../../lang");
8
+ /**
9
+ * Provider-managed logConfig fields that are set by the system after creation.
10
+ * These should not be compared when detecting changes.
11
+ */
12
+ const PROVIDER_MANAGED_LOG_CONFIG_FIELDS = ['project', 'logstore', 'logBeginRule'];
13
+ /**
14
+ * Normalize definition for comparison by excluding provider-managed fields.
15
+ * This prevents false-positive change detection when the system populates
16
+ * fields like logConfig.project and logConfig.logstore after creation.
17
+ */
18
+ const normalizeDefinitionForComparison = (definition) => {
19
+ const { logConfig, ...rest } = definition;
20
+ if (!logConfig || typeof logConfig !== 'object') {
21
+ return definition;
22
+ }
23
+ const normalizedLogConfig = {};
24
+ for (const [key, value] of Object.entries(logConfig)) {
25
+ if (!PROVIDER_MANAGED_LOG_CONFIG_FIELDS.includes(key)) {
26
+ normalizedLogConfig[key] = value;
27
+ }
28
+ }
29
+ return {
30
+ ...rest,
31
+ logConfig: Object.keys(normalizedLogConfig).length > 0 ? normalizedLogConfig : null,
32
+ };
33
+ };
8
34
  const isSecurityGroupId = (value) => value.startsWith('sg-');
9
35
  const resolveSecurityGroupId = async (context, securityGroupName, vpcId) => {
10
36
  if (isSecurityGroupId(securityGroupName)) {
@@ -74,7 +100,7 @@ const generateFunctionPlan = async (context, state, functions) => {
74
100
  };
75
101
  }
76
102
  const currentDefinition = currentState.definition || {};
77
- const definitionChanged = !(0, common_1.attributesEqual)(currentDefinition, desiredDefinition);
103
+ const definitionChanged = !(0, common_1.attributesEqual)(normalizeDefinitionForComparison(currentDefinition), normalizeDefinitionForComparison(desiredDefinition));
78
104
  if (definitionChanged) {
79
105
  return {
80
106
  logicalId,
@@ -358,6 +358,7 @@ const createResource = async (context, fn, state) => {
358
358
  project: dependentResources.logConfig.project,
359
359
  logstore: dependentResources.logConfig.logstore,
360
360
  enableRequestMetrics: true,
361
+ enableInstanceMetrics: true,
361
362
  },
362
363
  };
363
364
  }
@@ -547,6 +548,7 @@ const updateResource = async (context, fn, state) => {
547
548
  project: logConfig.project,
548
549
  logstore: logConfig.logstore,
549
550
  enableRequestMetrics: true,
551
+ enableInstanceMetrics: true,
550
552
  },
551
553
  };
552
554
  }
@@ -1,6 +1,10 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
6
  exports.generateBucketPlan = void 0;
7
+ const node_path_1 = __importDefault(require("node:path"));
4
8
  const aliyunClient_1 = require("../../common/aliyunClient");
5
9
  const ossTypes_1 = require("./ossTypes");
6
10
  const stateManager_1 = require("../../common/stateManager");
@@ -24,7 +28,17 @@ const generateBucketPlan = async (context, state, buckets) => {
24
28
  const logicalId = `buckets.${bucket.key}`;
25
29
  const currentState = (0, stateManager_1.getResource)(state, logicalId);
26
30
  const config = (0, ossTypes_1.bucketToOssBucketConfig)(bucket);
27
- const desiredDefinition = (0, ossTypes_1.extractOssBucketDefinition)(config);
31
+ const websiteCodeHash = (() => {
32
+ if (!bucket.website?.code)
33
+ return undefined;
34
+ try {
35
+ return (0, hashUtils_1.computeDirectoryHash)(node_path_1.default.resolve(process.cwd(), bucket.website.code));
36
+ }
37
+ catch {
38
+ return null;
39
+ }
40
+ })();
41
+ const desiredDefinition = (0, ossTypes_1.extractOssBucketDefinition)(config, websiteCodeHash);
28
42
  if (!currentState) {
29
43
  return {
30
44
  logicalId,
@@ -136,11 +136,14 @@ const createBucketResource = async (context, bucket, state) => {
136
136
  const sid = (0, common_1.buildSid)('aliyun', 'oss', context.stage, config.bucketName);
137
137
  const logicalId = `buckets.${bucket.key}`;
138
138
  const instances = [buildOssInstanceFromProvider(bucketInfo, sid)];
139
+ const websiteCodeHash = bucket.website?.code
140
+ ? (0, common_1.computeDirectoryHash)(node_path_1.default.resolve(process.cwd(), bucket.website.code))
141
+ : undefined;
139
142
  const partialResourceState = {
140
143
  mode: 'managed',
141
144
  region: context.region,
142
145
  definition: {
143
- ...(0, ossTypes_1.extractOssBucketDefinition)(config),
146
+ ...(0, ossTypes_1.extractOssBucketDefinition)(config, websiteCodeHash),
144
147
  ...(bucket.website?.domain != null ? { domainBound: null } : {}),
145
148
  },
146
149
  instances,
@@ -224,7 +227,7 @@ const createBucketResource = async (context, bucket, state) => {
224
227
  mode: 'managed',
225
228
  region: context.region,
226
229
  definition: {
227
- ...(0, ossTypes_1.extractOssBucketDefinition)(config),
230
+ ...(0, ossTypes_1.extractOssBucketDefinition)(config, websiteCodeHash),
228
231
  ...(bucket.website?.domain != null
229
232
  ? { domainBound: cnameInfo?.bucketCnameBound ?? null }
230
233
  : {}),
@@ -260,6 +263,9 @@ const updateBucketResource = async (context, bucket, state) => {
260
263
  const sid = (0, common_1.buildSid)('aliyun', 'oss', context.stage, config.bucketName);
261
264
  const logicalId = `buckets.${bucket.key}`;
262
265
  const instances = [buildOssInstanceFromProvider(bucketInfo, sid)];
266
+ const websiteCodeHash = bucket.website?.code
267
+ ? (0, common_1.computeDirectoryHash)(node_path_1.default.resolve(process.cwd(), bucket.website.code))
268
+ : undefined;
263
269
  const existingState = state.resources[logicalId];
264
270
  const existingDnsInstances = existingState?.instances?.filter((i) => i.type === types_1.ResourceTypeEnum.ALIYUN_OSS_DNS_CNAME);
265
271
  const existingPrimaryDnsInstance = existingDnsInstances?.find((i) => !i.isWwwVariant);
@@ -338,7 +344,7 @@ const updateBucketResource = async (context, bucket, state) => {
338
344
  mode: 'managed',
339
345
  region: context.region,
340
346
  definition: {
341
- ...(0, ossTypes_1.extractOssBucketDefinition)(config),
347
+ ...(0, ossTypes_1.extractOssBucketDefinition)(config, websiteCodeHash),
342
348
  ...(bucket.website?.domain != null
343
349
  ? { domainBound: cnameInfo?.bucketCnameBound ?? null }
344
350
  : {}),
@@ -57,7 +57,7 @@ const bucketToOssBucketConfig = (bucket) => {
57
57
  return config;
58
58
  };
59
59
  exports.bucketToOssBucketConfig = bucketToOssBucketConfig;
60
- const extractOssBucketDefinition = (config) => {
60
+ const extractOssBucketDefinition = (config, websiteCodeHash) => {
61
61
  return {
62
62
  bucketName: config.bucketName,
63
63
  acl: config.acl ?? null,
@@ -67,6 +67,7 @@ const extractOssBucketDefinition = (config) => {
67
67
  errorDocument: config.websiteConfig.errorDocument ?? null,
68
68
  }
69
69
  : {},
70
+ websiteCodeHash: websiteCodeHash ?? null,
70
71
  storageClass: config.storageClass ?? null,
71
72
  domain: config.domain ?? null,
72
73
  wwwBindApex: config.wwwBindApex ?? false,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@geek-fun/serverlessinsight",
3
- "version": "0.6.11",
3
+ "version": "0.6.13",
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",