befly 3.13.9 → 3.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/befly.js CHANGED
@@ -7590,25 +7590,25 @@ async function checkApi(apis) {
7590
7590
  hasError = true;
7591
7591
  continue;
7592
7592
  }
7593
- if (typeof api?.routePath !== "string" || api.routePath.trim() === "") {
7594
- Logger.warn(Object.assign({}, omit(api, ["handler"]), { msg: "\u63A5\u53E3\u7684 routePath \u5C5E\u6027\u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32\uFF08\u7531\u7CFB\u7EDF\u751F\u6210\uFF09" }));
7593
+ if (typeof api?.path !== "string" || api.path.trim() === "") {
7594
+ Logger.warn(Object.assign({}, omit(api, ["handler"]), { msg: "\u63A5\u53E3\u7684 path \u5C5E\u6027\u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32\uFF08\u7531\u7CFB\u7EDF\u751F\u6210\uFF09" }));
7595
7595
  hasError = true;
7596
7596
  } else {
7597
- const routePath = api.routePath.trim();
7598
- if (/^(GET|POST|PUT|PATCH|DELETE|OPTIONS|HEAD)\b/i.test(routePath)) {
7599
- Logger.warn(Object.assign({}, omit(api, ["handler"]), { msg: "\u63A5\u53E3\u7684 routePath \u4E0D\u5141\u8BB8\u5305\u542B method \u524D\u7F00\uFF0C\u5E94\u4E3A url.pathname\uFF08\u4F8B\u5982 /api/app/xxx\uFF09" }));
7597
+ const path = api.path.trim();
7598
+ if (/^(GET|POST|PUT|PATCH|DELETE|OPTIONS|HEAD)\b/i.test(path)) {
7599
+ Logger.warn(Object.assign({}, omit(api, ["handler"]), { msg: "\u63A5\u53E3\u7684 path \u4E0D\u5141\u8BB8\u5305\u542B method \u524D\u7F00\uFF0C\u5E94\u4E3A url.pathname\uFF08\u4F8B\u5982 /api/app/xxx\uFF09" }));
7600
7600
  hasError = true;
7601
7601
  }
7602
- if (!routePath.startsWith("/api/")) {
7603
- Logger.warn(Object.assign({}, omit(api, ["handler"]), { msg: "\u63A5\u53E3\u7684 routePath \u5FC5\u987B\u4EE5 /api/ \u5F00\u5934" }));
7602
+ if (!path.startsWith("/api/")) {
7603
+ Logger.warn(Object.assign({}, omit(api, ["handler"]), { msg: "\u63A5\u53E3\u7684 path \u5FC5\u987B\u4EE5 /api/ \u5F00\u5934" }));
7604
7604
  hasError = true;
7605
7605
  }
7606
- if (routePath.includes(" ")) {
7607
- Logger.warn(Object.assign({}, omit(api, ["handler"]), { msg: "\u63A5\u53E3\u7684 routePath \u4E0D\u5141\u8BB8\u5305\u542B\u7A7A\u683C" }));
7606
+ if (path.includes(" ")) {
7607
+ Logger.warn(Object.assign({}, omit(api, ["handler"]), { msg: "\u63A5\u53E3\u7684 path \u4E0D\u5141\u8BB8\u5305\u542B\u7A7A\u683C" }));
7608
7608
  hasError = true;
7609
7609
  }
7610
- if (routePath.includes("/api//")) {
7611
- Logger.warn(Object.assign({}, omit(api, ["handler"]), { msg: "\u63A5\u53E3\u7684 routePath \u4E0D\u5141\u8BB8\u51FA\u73B0 /api//\uFF08\u91CD\u590D\u659C\u6760\uFF09" }));
7610
+ if (path.includes("/api//")) {
7611
+ Logger.warn(Object.assign({}, omit(api, ["handler"]), { msg: "\u63A5\u53E3\u7684 path \u4E0D\u5141\u8BB8\u51FA\u73B0 /api//\uFF08\u91CD\u590D\u659C\u6760\uFF09" }));
7612
7612
  hasError = true;
7613
7613
  }
7614
7614
  }
@@ -8320,7 +8320,7 @@ var presetFields = {
8320
8320
  };
8321
8321
 
8322
8322
  // utils/processAtSymbol.ts
8323
- function processAtSymbol(fields, apiName, routePath) {
8323
+ function processAtSymbol(fields, apiName, path) {
8324
8324
  if (!fields || typeof fields !== "object")
8325
8325
  return fields;
8326
8326
  const processed = {};
@@ -8331,7 +8331,7 @@ function processAtSymbol(fields, apiName, routePath) {
8331
8331
  continue;
8332
8332
  }
8333
8333
  const validKeys = Object.keys(presetFields).join(", ");
8334
- throw new Error(`API [${apiName}] (${routePath}) \u5B57\u6BB5 [${key}] \u5F15\u7528\u4E86\u672A\u5B9A\u4E49\u7684\u9884\u8BBE\u5B57\u6BB5 "${value}"\u3002\u53EF\u7528\u7684\u9884\u8BBE\u5B57\u6BB5\u6709: ${validKeys}`);
8334
+ throw new Error(`API [${apiName}] (${path}) \u5B57\u6BB5 [${key}] \u5F15\u7528\u4E86\u672A\u5B9A\u4E49\u7684\u9884\u8BBE\u5B57\u6BB5 "${value}"\u3002\u53EF\u7528\u7684\u9884\u8BBE\u5B57\u6BB5\u6709: ${validKeys}`);
8335
8335
  }
8336
8336
  processed[key] = value;
8337
8337
  }
@@ -8342,14 +8342,13 @@ function processAtSymbol(fields, apiName, routePath) {
8342
8342
  async function loadApis(apis) {
8343
8343
  const apisMap = new Map;
8344
8344
  for (const api of apis) {
8345
- const apiType = api.type;
8346
- if (apiType && apiType !== "api") {
8345
+ if (api.type !== "api") {
8347
8346
  continue;
8348
8347
  }
8349
8348
  try {
8350
8349
  const apiRoute = api;
8351
- apiRoute.fields = processAtSymbol(apiRoute.fields || {}, apiRoute.name, apiRoute.routePath);
8352
- apisMap.set(apiRoute.routePath, apiRoute);
8350
+ apiRoute.fields = processAtSymbol(apiRoute.fields || {}, apiRoute.name, apiRoute.path);
8351
+ apisMap.set(apiRoute.path, apiRoute);
8353
8352
  } catch (error) {
8354
8353
  Logger.error({ err: error, api: api.relativePath, file: api.filePath, msg: "\u63A5\u53E3\u52A0\u8F7D\u5931\u8D25" });
8355
8354
  throw error;
@@ -8783,6 +8782,15 @@ function staticHandler(corsConfig = undefined) {
8783
8782
  // sync/syncApi.ts
8784
8783
  init_logger();
8785
8784
  init_util();
8785
+ var getApiParentPath = (apiPath) => {
8786
+ const segments = apiPath.split("/").map((s) => s.trim()).filter((s) => s.length > 0);
8787
+ const seg2 = segments[1] || "";
8788
+ const take = seg2 === "addon" ? 4 : 3;
8789
+ const parentSegments = segments.slice(0, Math.min(take, segments.length));
8790
+ if (parentSegments.length === 0)
8791
+ return "";
8792
+ return `/${parentSegments.join("/")}`;
8793
+ };
8786
8794
  async function syncApi(ctx, apis) {
8787
8795
  const tableName = "addon_admin_api";
8788
8796
  if (!ctx.db) {
@@ -8797,55 +8805,61 @@ async function syncApi(ctx, apis) {
8797
8805
  }
8798
8806
  const allDbApis = await ctx.db.getAll({
8799
8807
  table: tableName,
8800
- fields: ["id", "routePath", "name", "addonName", "auth", "state"],
8808
+ fields: ["id", "path", "parentPath", "name", "addonName", "auth", "state"],
8801
8809
  where: { state$gte: 0 }
8802
8810
  });
8803
8811
  const dbLists = allDbApis.data.lists || [];
8804
- const allDbApiMap = keyBy(dbLists, (item) => item.routePath);
8812
+ const allDbApiMap = keyBy(dbLists, (item) => item.path);
8805
8813
  const insData = [];
8806
8814
  const updData = [];
8807
8815
  const delData = [];
8808
8816
  const apiRouteKeys = new Set;
8809
8817
  for (const api of apis) {
8810
- const apiType = api.type;
8811
- if (apiType && apiType !== "api") {
8818
+ if (api.type !== "api") {
8812
8819
  continue;
8813
8820
  }
8814
- const normalizedAuth = api.auth === 0 || api.auth === false ? 0 : 1;
8815
- const normalizedApi = {
8816
- name: api.name,
8817
- routePath: api.routePath,
8818
- addonName: api.addonName,
8819
- auth: normalizedAuth
8820
- };
8821
- const routePath = normalizedApi.routePath;
8822
- apiRouteKeys.add(routePath);
8823
- const item = allDbApiMap[routePath];
8821
+ const auth = api.auth === false || api.auth === 0 ? 0 : 1;
8822
+ const parentPath = getApiParentPath(api.path);
8823
+ apiRouteKeys.add(api.path);
8824
+ const item = allDbApiMap[api.path];
8824
8825
  if (item) {
8825
- const dbAuth = item.auth === 0 || item.auth === false ? 0 : 1;
8826
- const shouldUpdate = normalizedApi.name !== item.name || normalizedApi.routePath !== item.routePath || normalizedApi.addonName !== item.addonName || normalizedAuth !== dbAuth;
8826
+ const shouldUpdate = api.name !== item.name || api.path !== item.path || api.addonName !== item.addonName || parentPath !== item.parentPath || auth !== item.auth;
8827
8827
  if (shouldUpdate) {
8828
- updData.push({ id: item.id, api: normalizedApi });
8828
+ updData.push({
8829
+ id: item.id,
8830
+ name: api.name,
8831
+ path: api.path,
8832
+ parentPath,
8833
+ addonName: api.addonName,
8834
+ auth
8835
+ });
8829
8836
  }
8830
8837
  } else {
8831
- insData.push(normalizedApi);
8838
+ insData.push({
8839
+ name: api.name,
8840
+ path: api.path,
8841
+ parentPath,
8842
+ addonName: api.addonName,
8843
+ auth
8844
+ });
8832
8845
  }
8833
8846
  }
8834
8847
  for (const record of dbLists) {
8835
- if (!apiRouteKeys.has(record.routePath)) {
8848
+ if (!apiRouteKeys.has(record.path)) {
8836
8849
  delData.push(record.id);
8837
8850
  }
8838
8851
  }
8839
8852
  if (updData.length > 0) {
8840
8853
  try {
8841
- await ctx.db.updBatch(tableName, updData.map((item) => {
8854
+ await ctx.db.updBatch(tableName, updData.map((api) => {
8842
8855
  return {
8843
- id: item.id,
8856
+ id: api.id,
8844
8857
  data: {
8845
- name: item.api.name,
8846
- routePath: item.api.routePath,
8847
- addonName: item.api.addonName,
8848
- auth: item.api.auth === 0 || item.api.auth === false ? 0 : 1
8858
+ name: api.name,
8859
+ path: api.path,
8860
+ parentPath: api.parentPath,
8861
+ addonName: api.addonName,
8862
+ auth: api.auth
8849
8863
  }
8850
8864
  };
8851
8865
  }));
@@ -8858,9 +8872,10 @@ async function syncApi(ctx, apis) {
8858
8872
  await ctx.db.insBatch(tableName, insData.map((api) => {
8859
8873
  return {
8860
8874
  name: api.name,
8861
- routePath: api.routePath,
8875
+ path: api.path,
8876
+ parentPath: api.parentPath,
8862
8877
  addonName: api.addonName,
8863
- auth: api.auth === 0 || api.auth === false ? 0 : 1
8878
+ auth: api.auth
8864
8879
  };
8865
8880
  }));
8866
8881
  } catch (error) {
@@ -9000,7 +9015,6 @@ async function syncDev(ctx, config2 = {}) {
9000
9015
  if (!config2.devPassword) {
9001
9016
  return;
9002
9017
  }
9003
- const devEmail = typeof config2.devEmail === "string" && config2.devEmail.length > 0 ? config2.devEmail : "dev@qq.com";
9004
9018
  if (!ctx.db) {
9005
9019
  throw new Error("syncDev: ctx.db \u672A\u521D\u59CB\u5316\uFF08Db \u63D2\u4EF6\u672A\u52A0\u8F7D\u6216\u6CE8\u5165\u5931\u8D25\uFF09");
9006
9020
  }
@@ -9019,140 +9033,130 @@ async function syncDev(ctx, config2 = {}) {
9019
9033
  Logger.debug(`addon_admin_menu \u8868\u4E0D\u5B58\u5728`);
9020
9034
  return;
9021
9035
  }
9036
+ if (!(await ctx.db.tableExists("addon_admin_api")).data) {
9037
+ Logger.debug(`addon_admin_api \u8868\u4E0D\u5B58\u5728`);
9038
+ return;
9039
+ }
9022
9040
  const allMenus = await ctx.db.getAll({
9023
9041
  table: "addon_admin_menu",
9024
9042
  fields: ["path"],
9025
9043
  where: { state$gte: 0 },
9026
9044
  orderBy: ["id#ASC"]
9027
9045
  });
9028
- const menuPaths = Array.from(new Set((allMenus.data.lists || []).map((m) => typeof m?.path === "string" ? m.path.trim() : "").filter((p) => p.length > 0)));
9029
- const existApi = await ctx.db.tableExists("addon_admin_api");
9030
- let apiPaths = [];
9031
- if (existApi.data) {
9032
- const allApis = await ctx.db.getAll({
9033
- table: "addon_admin_api",
9034
- fields: ["routePath"],
9035
- where: { state$gte: 0 },
9036
- orderBy: ["id#ASC"]
9037
- });
9038
- apiPaths = Array.from(new Set((allApis.data.lists || []).map((a) => {
9039
- if (typeof a?.routePath !== "string") {
9040
- throw new Error("syncDev: addon_admin_api.routePath \u5FC5\u987B\u662F\u5B57\u7B26\u4E32");
9041
- }
9042
- const routePath = a.routePath.trim();
9043
- if (routePath.length === 0) {
9044
- throw new Error("syncDev: addon_admin_api.routePath \u4E0D\u5141\u8BB8\u4E3A\u7A7A\u5B57\u7B26\u4E32");
9046
+ const allApis = await ctx.db.getAll({
9047
+ table: "addon_admin_api",
9048
+ fields: ["path"],
9049
+ where: { state$gte: 0 },
9050
+ orderBy: ["id#ASC"]
9051
+ });
9052
+ const devRole = await ctx.db.getOne({
9053
+ table: "addon_admin_role",
9054
+ where: { code: "dev" }
9055
+ });
9056
+ const devRoleData = {
9057
+ code: "dev",
9058
+ name: "\u5F00\u53D1\u8005\u89D2\u8272",
9059
+ description: "\u62E5\u6709\u6240\u6709\u83DC\u5355\u548C\u63A5\u53E3\u6743\u9650\u7684\u5F00\u53D1\u8005\u89D2\u8272",
9060
+ menus: allMenus.data.lists.map((item) => item.path).filter((v) => v),
9061
+ apis: allApis.data.lists.map((item) => item.path).filter((v) => v),
9062
+ sort: 0
9063
+ };
9064
+ if (devRole.data) {
9065
+ await ctx.db.updData({
9066
+ table: "addon_admin_role",
9067
+ where: { code: "dev" },
9068
+ data: {
9069
+ name: devRoleData.name,
9070
+ description: devRoleData.description,
9071
+ menus: devRoleData.menus,
9072
+ apis: devRoleData.apis,
9073
+ sort: devRoleData.sort
9045
9074
  }
9046
- if (!routePath.startsWith("/")) {
9047
- throw new Error(`syncDev: addon_admin_api.routePath \u5FC5\u987B\u662F pathname\uFF08\u4EE5 / \u5F00\u5934\uFF09\uFF0C\u5F53\u524D\u503C=${routePath}`);
9075
+ });
9076
+ } else {
9077
+ await ctx.db.insData({
9078
+ table: "addon_admin_role",
9079
+ data: devRoleData
9080
+ });
9081
+ }
9082
+ const devAdminData = {
9083
+ nickname: "\u5F00\u53D1\u8005",
9084
+ email: config2.devEmail || "dev@qq.com",
9085
+ username: "dev",
9086
+ password: await Cipher.hashPassword(Cipher.sha256(config2.devPassword + "befly")),
9087
+ roleCode: "dev",
9088
+ roleType: "admin"
9089
+ };
9090
+ const devAdmin = await ctx.db.getOne({
9091
+ table: "addon_admin_admin",
9092
+ where: { username: "dev" }
9093
+ });
9094
+ if (devAdmin.data) {
9095
+ await ctx.db.updData({
9096
+ table: "addon_admin_admin",
9097
+ where: { username: "dev" },
9098
+ data: {
9099
+ nickname: devAdminData.nickname,
9100
+ email: devAdminData.email,
9101
+ username: devAdminData.username,
9102
+ password: devAdminData.password,
9103
+ roleCode: devAdminData.roleCode,
9104
+ roleType: devAdminData.roleType
9048
9105
  }
9049
- return routePath;
9050
- })));
9106
+ });
9107
+ } else {
9108
+ await ctx.db.insData({
9109
+ table: "addon_admin_admin",
9110
+ data: devAdminData
9111
+ });
9051
9112
  }
9052
9113
  const roles = [
9053
- {
9054
- code: "dev",
9055
- name: "\u5F00\u53D1\u8005\u89D2\u8272",
9056
- description: "\u62E5\u6709\u6240\u6709\u83DC\u5355\u548C\u63A5\u53E3\u6743\u9650\u7684\u5F00\u53D1\u8005\u89D2\u8272",
9057
- menus: menuPaths,
9058
- apis: apiPaths,
9059
- sort: 0
9060
- },
9061
9114
  {
9062
9115
  code: "user",
9063
9116
  name: "\u7528\u6237\u89D2\u8272",
9064
9117
  description: "\u666E\u901A\u7528\u6237\u89D2\u8272",
9065
- menus: [],
9066
- apis: [],
9067
9118
  sort: 1
9068
9119
  },
9069
9120
  {
9070
9121
  code: "admin",
9071
9122
  name: "\u7BA1\u7406\u5458\u89D2\u8272",
9072
9123
  description: "\u7BA1\u7406\u5458\u89D2\u8272",
9073
- menus: [],
9074
- apis: [],
9075
9124
  sort: 2
9076
9125
  },
9077
9126
  {
9078
9127
  code: "guest",
9079
9128
  name: "\u8BBF\u5BA2\u89D2\u8272",
9080
9129
  description: "\u8BBF\u5BA2\u89D2\u8272",
9081
- menus: [],
9082
- apis: [],
9083
9130
  sort: 3
9084
9131
  }
9085
9132
  ];
9086
- let devRole = null;
9087
9133
  for (const roleConfig of roles) {
9088
9134
  const existingRole = await ctx.db.getOne({
9089
9135
  table: "addon_admin_role",
9090
9136
  where: { code: roleConfig.code }
9091
9137
  });
9092
- if (existingRole.data) {
9093
- const nextMenus = roleConfig.menus;
9094
- const nextApis = roleConfig.apis;
9095
- const existingMenus = Array.isArray(existingRole.data.menus) ? existingRole.data.menus : [];
9096
- const existingApis = Array.isArray(existingRole.data.apis) ? existingRole.data.apis : [];
9097
- const menusChanged = existingMenus.length !== nextMenus.length || existingMenus.some((v, i) => v !== nextMenus[i]);
9098
- const apisChanged = existingApis.length !== nextApis.length || existingApis.some((v, i) => v !== nextApis[i]);
9099
- const hasChanges = existingRole.data.name !== roleConfig.name || existingRole.data.description !== roleConfig.description || menusChanged || apisChanged || existingRole.data.sort !== roleConfig.sort;
9100
- if (hasChanges) {
9101
- await ctx.db.updData({
9102
- table: "addon_admin_role",
9103
- where: { code: roleConfig.code },
9104
- data: {
9105
- name: roleConfig.name,
9106
- description: roleConfig.description,
9107
- menus: roleConfig.menus,
9108
- apis: roleConfig.apis,
9109
- sort: roleConfig.sort
9110
- }
9111
- });
9112
- }
9113
- if (roleConfig.code === "dev") {
9114
- devRole = existingRole.data;
9115
- }
9138
+ if (existingRole.data?.id) {
9139
+ await ctx.db.updData({
9140
+ table: "addon_admin_role",
9141
+ where: { code: roleConfig.code },
9142
+ data: {
9143
+ name: roleConfig.name,
9144
+ description: roleConfig.description,
9145
+ sort: roleConfig.sort
9146
+ }
9147
+ });
9116
9148
  } else {
9117
- const roleId = await ctx.db.insData({
9149
+ await ctx.db.insData({
9118
9150
  table: "addon_admin_role",
9119
- data: roleConfig
9151
+ data: {
9152
+ code: roleConfig.code,
9153
+ name: roleConfig.name,
9154
+ description: roleConfig.description,
9155
+ sort: roleConfig.sort
9156
+ }
9120
9157
  });
9121
- if (roleConfig.code === "dev") {
9122
- devRole = { id: roleId.data };
9123
- }
9124
9158
  }
9125
9159
  }
9126
- if (!devRole) {
9127
- Logger.error("dev \u89D2\u8272\u4E0D\u5B58\u5728\uFF0C\u65E0\u6CD5\u521B\u5EFA\u5F00\u53D1\u8005\u8D26\u53F7");
9128
- return;
9129
- }
9130
- const sha256Hashed = Cipher.sha256(config2.devPassword + "befly");
9131
- const hashed = await Cipher.hashPassword(sha256Hashed);
9132
- const devData = {
9133
- nickname: "\u5F00\u53D1\u8005",
9134
- email: devEmail,
9135
- username: "dev",
9136
- password: hashed,
9137
- roleCode: "dev",
9138
- roleType: "admin"
9139
- };
9140
- const existing = await ctx.db.getOne({
9141
- table: "addon_admin_admin",
9142
- where: { email: devEmail }
9143
- });
9144
- if (existing.data) {
9145
- await ctx.db.updData({
9146
- table: "addon_admin_admin",
9147
- where: { email: devEmail },
9148
- data: devData
9149
- });
9150
- } else {
9151
- await ctx.db.insData({
9152
- table: "addon_admin_admin",
9153
- data: devData
9154
- });
9155
- }
9156
9160
  }
9157
9161
 
9158
9162
  // sync/syncMenu.ts
@@ -15179,7 +15183,7 @@ async function scanFiles(dir, source, type, pattern) {
15179
15183
  });
15180
15184
  if (type === "api") {
15181
15185
  base.routePrefix = source === "app" ? "/app" : `/addon/${addonName}`;
15182
- base.routePath = `/api${base.routePrefix}/${relativePath}`;
15186
+ base.path = `/api${base.routePrefix}/${relativePath}`;
15183
15187
  }
15184
15188
  results.push(base);
15185
15189
  }
@@ -15232,13 +15236,12 @@ class Befly {
15232
15236
  hooks = [];
15233
15237
  context = {};
15234
15238
  config = null;
15235
- async start(env) {
15239
+ async start(env = {}) {
15236
15240
  try {
15237
15241
  const serverStartTime = Bun.nanoseconds();
15238
- const runtimeEnv = env || {};
15239
- this.config = await loadBeflyConfig(runtimeEnv.NODE_ENV);
15242
+ this.config = await loadBeflyConfig(env.NODE_ENV || "development");
15240
15243
  this.context.config = this.config;
15241
- this.context.env = runtimeEnv;
15244
+ this.context.env = env;
15242
15245
  const { apis, tables, plugins, hooks, addons } = await scanSources();
15243
15246
  this.context.addons = addons;
15244
15247
  await checkApi(apis);
@@ -15298,7 +15301,7 @@ class Befly {
15298
15301
  }
15299
15302
  });
15300
15303
  const finalStartupTime = calcPerfTime(serverStartTime);
15301
- const processRole = getProcessRole(runtimeEnv);
15304
+ const processRole = getProcessRole(env);
15302
15305
  const roleLabel = processRole.role === "primary" ? "\u4E3B\u8FDB\u7A0B" : `\u5DE5\u4F5C\u8FDB\u7A0B #${processRole.instanceId}`;
15303
15306
  const envLabel = processRole.env === "standalone" ? "" : ` [${processRole.env}]`;
15304
15307
  Logger.info(`${this.config.appName} \u542F\u52A8\u6210\u529F! (${roleLabel}${envLabel})`);