sy-lowcode-workspace-tools 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,482 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { getApiBaseUrl } from "./load-config.mjs";
4
+
5
+ function readExportedStringConstant(filePath, exportName) {
6
+ if (!filePath || !fs.existsSync(filePath)) return "";
7
+ const content = fs.readFileSync(filePath, "utf-8");
8
+ const escaped = exportName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
9
+ const match = content.match(
10
+ new RegExp(`export\\s+const\\s+${escaped}\\s*=\\s*(['"])(.*?)\\1`),
11
+ );
12
+ return match?.[2]?.trim() || "";
13
+ }
14
+
15
+ function resolveRelativeImportPath(schemaPath, source) {
16
+ if (!source.startsWith(".")) return "";
17
+ const basePath = path.resolve(path.dirname(schemaPath), source);
18
+ const candidates = [
19
+ basePath,
20
+ `${basePath}.ts`,
21
+ `${basePath}.tsx`,
22
+ `${basePath}.js`,
23
+ `${basePath}.mjs`,
24
+ path.join(basePath, "index.ts"),
25
+ path.join(basePath, "index.tsx"),
26
+ path.join(basePath, "index.js"),
27
+ ];
28
+ return candidates.find((candidate) => fs.existsSync(candidate)) || "";
29
+ }
30
+
31
+ function readImportedStringConstant(content, schemaPath, identifier) {
32
+ if (!schemaPath || !identifier) return "";
33
+ const importRegex = /import\s*\{([^}]+)\}\s*from\s*["']([^"']+)["']/g;
34
+ let match;
35
+ while ((match = importRegex.exec(content))) {
36
+ const specifiers = match[1]
37
+ .split(",")
38
+ .map((item) => item.trim())
39
+ .filter(Boolean);
40
+ const sourcePath = resolveRelativeImportPath(schemaPath, match[2]);
41
+ if (!sourcePath) continue;
42
+
43
+ for (const specifier of specifiers) {
44
+ const aliasMatch = specifier.match(
45
+ /^([A-Za-z_$][\w$]*)(?:\s+as\s+([A-Za-z_$][\w$]*))?$/,
46
+ );
47
+ if (!aliasMatch) continue;
48
+ const exportedName = aliasMatch[1];
49
+ const localName = aliasMatch[2] || exportedName;
50
+ if (localName !== identifier) continue;
51
+ const value = readExportedStringConstant(sourcePath, exportedName);
52
+ if (value) return value;
53
+ }
54
+ }
55
+ return "";
56
+ }
57
+
58
+ function readStringProp(content, propName, schemaPath) {
59
+ const escaped = propName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
60
+ const literalMatch = content.match(
61
+ new RegExp(`${escaped}\\s*:\\s*(['"])(.*?)\\1`),
62
+ );
63
+ if (literalMatch) return literalMatch[2]?.trim() || "";
64
+
65
+ const identifierMatch = content.match(
66
+ new RegExp(`${escaped}\\s*:\\s*([A-Za-z_$][\\w$]*)`),
67
+ );
68
+ const identifier = identifierMatch?.[1];
69
+ if (!identifier) return "";
70
+
71
+ const localConstMatch = content.match(
72
+ new RegExp(`(?:export\\s+)?const\\s+${identifier}\\s*=\\s*(['"])(.*?)\\1`),
73
+ );
74
+ if (localConstMatch) return localConstMatch[2]?.trim() || "";
75
+
76
+ return readImportedStringConstant(content, schemaPath, identifier);
77
+ }
78
+
79
+ function hasObjectProp(content, propName) {
80
+ const escaped = propName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
81
+ return new RegExp(`${escaped}\\s*:`).test(content);
82
+ }
83
+
84
+ export function readSchemaMeta(schemaPath) {
85
+ const content = fs.readFileSync(schemaPath, "utf-8");
86
+
87
+ return {
88
+ content,
89
+ hasFormUuidProp: hasObjectProp(content, "formUuid"),
90
+ formUuid: readStringProp(content, "formUuid", schemaPath),
91
+ appType: readStringProp(content, "appType", schemaPath),
92
+ title: readStringProp(content, "title", schemaPath),
93
+ formType: readStringProp(content, "formType", schemaPath) || "receipt",
94
+ relateUuid: readStringProp(content, "relateUuid", schemaPath),
95
+ };
96
+ }
97
+
98
+ export function writeSchemaFormUuid(schemaPath, formUuid) {
99
+ const content = fs.readFileSync(schemaPath, "utf-8");
100
+ const nextContent = content.replace(
101
+ /formUuid\s*:\s*(['"])([^'"]*)\1/,
102
+ (_match, quote) => `formUuid: ${quote}${formUuid}${quote}`,
103
+ );
104
+
105
+ if (nextContent === content) {
106
+ throw new Error("schema.ts 中未找到 formUuid 字段,无法回写");
107
+ }
108
+
109
+ fs.writeFileSync(schemaPath, nextContent, "utf-8");
110
+ }
111
+
112
+ export function assertRequiredOpenApiConfig(config) {
113
+ const missing = [];
114
+ if (!config.appType) missing.push("APP_TYPE");
115
+ if (!config.appKey) missing.push("APP_KEY");
116
+ if (!config.appSecret) missing.push("APP_SECRET");
117
+ if (!config.userId) missing.push("APP_USER_ID");
118
+
119
+ if (missing.length > 0) {
120
+ throw new Error(`缺少必要配置: ${missing.join(", ")}`);
121
+ }
122
+ }
123
+
124
+ export async function getOpenApiAccessToken(config) {
125
+ assertRequiredOpenApiConfig(config);
126
+
127
+ const apiBase = getApiBaseUrl(config);
128
+ const res = await fetch(`${apiBase}/dingtalk-api/v1.0/oauth2/accessToken`, {
129
+ method: "POST",
130
+ headers: {
131
+ "Content-Type": "application/json",
132
+ },
133
+ body: JSON.stringify({
134
+ appKey: config.appKey,
135
+ appSecret: config.appSecret,
136
+ }),
137
+ });
138
+
139
+ let body = null;
140
+ try {
141
+ body = await res.json();
142
+ } catch {
143
+ body = null;
144
+ }
145
+
146
+ if (!res.ok || !body?.accessToken) {
147
+ const message = body?.message || res.statusText || "unknown error";
148
+ throw new Error(`获取 accessToken 失败: HTTP ${res.status} ${message}`);
149
+ }
150
+
151
+ return body.accessToken;
152
+ }
153
+
154
+ export async function createOpenApiForm(config, accessToken, formOptions) {
155
+ assertRequiredOpenApiConfig(config);
156
+
157
+ const apiBase = getApiBaseUrl(config);
158
+ const payload = {
159
+ userId: config.userId,
160
+ name: formOptions.name,
161
+ appType: formOptions.appType || config.appType,
162
+ formType: formOptions.formType || "receipt",
163
+ relateUuid: formOptions.relateUuid || "",
164
+ };
165
+ const builderVersion =
166
+ formOptions.builderVersion ||
167
+ config.defaults?.formBuilderVersion ||
168
+ "2.0";
169
+ if (
170
+ builderVersion === "2.0" &&
171
+ ["receipt", "process"].includes(String(payload.formType || ""))
172
+ ) {
173
+ payload.builderVersion = "2.0";
174
+ }
175
+ if (formOptions.formUuid) {
176
+ payload.formUuid = formOptions.formUuid;
177
+ }
178
+
179
+ const res = await fetch(`${apiBase}/dingtalk-api/v1.0/forms/createForm`, {
180
+ method: "POST",
181
+ headers: {
182
+ "Content-Type": "application/json",
183
+ "x-acs-dingtalk-access-token": accessToken,
184
+ },
185
+ body: JSON.stringify(payload),
186
+ });
187
+
188
+ let body = null;
189
+ try {
190
+ body = await res.json();
191
+ } catch {
192
+ body = null;
193
+ }
194
+
195
+ if (!res.ok || body?.code !== 200) {
196
+ const message = body?.message || res.statusText || "unknown error";
197
+ throw new Error(`创建表单失败: HTTP ${res.status} ${message}`);
198
+ }
199
+
200
+ const formUuid = body?.data?.formUuid || body?.formUuid;
201
+ if (!formUuid) {
202
+ throw new Error("创建表单成功但响应中没有 formUuid");
203
+ }
204
+
205
+ return {
206
+ ...body.data,
207
+ formUuid,
208
+ };
209
+ }
210
+
211
+ export async function listOpenApiForms(config, accessToken, appType) {
212
+ assertRequiredOpenApiConfig(config);
213
+
214
+ const apiBase = getApiBaseUrl(config);
215
+ const res = await fetch(`${apiBase}/dingtalk-api/v1.0/forms/getFormList`, {
216
+ method: "POST",
217
+ headers: {
218
+ "Content-Type": "application/json",
219
+ "x-acs-dingtalk-access-token": accessToken,
220
+ },
221
+ body: JSON.stringify({
222
+ userId: config.userId,
223
+ appType: appType || config.appType,
224
+ }),
225
+ });
226
+
227
+ let body = null;
228
+ try {
229
+ body = await res.json();
230
+ } catch {
231
+ body = null;
232
+ }
233
+
234
+ if (!res.ok || body?.code !== 200) {
235
+ const message = body?.message || res.statusText || "unknown error";
236
+ throw new Error(`查询表单失败: HTTP ${res.status} ${message}`);
237
+ }
238
+
239
+ return Array.isArray(body?.data) ? body.data : [];
240
+ }
241
+
242
+ export async function getOpenApiForm(
243
+ config,
244
+ accessToken,
245
+ { appType, formUuid },
246
+ ) {
247
+ if (!formUuid) return null;
248
+ const forms = await listOpenApiForms(
249
+ config,
250
+ accessToken,
251
+ appType || config.appType,
252
+ );
253
+ return (
254
+ forms.find(
255
+ (item) =>
256
+ item?.formUuid === formUuid &&
257
+ (!appType || !item?.appType || item.appType === appType),
258
+ ) || null
259
+ );
260
+ }
261
+
262
+ export async function listOpenApiMenus(config, accessToken, appType) {
263
+ assertRequiredOpenApiConfig(config);
264
+
265
+ const apiBase = getApiBaseUrl(config);
266
+ const res = await fetch(
267
+ `${apiBase}/dingtalk-api/v1.0/forms/getMenusByAppType`,
268
+ {
269
+ method: "POST",
270
+ headers: {
271
+ "Content-Type": "application/json",
272
+ "x-acs-dingtalk-access-token": accessToken,
273
+ },
274
+ body: JSON.stringify({
275
+ userId: config.userId,
276
+ appType: appType || config.appType,
277
+ }),
278
+ },
279
+ );
280
+
281
+ let body = null;
282
+ try {
283
+ body = await res.json();
284
+ } catch {
285
+ body = null;
286
+ }
287
+
288
+ if (!res.ok || body?.code !== 200) {
289
+ const message = body?.message || res.statusText || "unknown error";
290
+ throw new Error(`查询菜单失败: HTTP ${res.status} ${message}`);
291
+ }
292
+
293
+ return Array.isArray(body?.data) ? body.data : [];
294
+ }
295
+
296
+ function flattenMenus(menus) {
297
+ const result = [];
298
+ const visit = (menu) => {
299
+ result.push(menu);
300
+ if (Array.isArray(menu.children)) {
301
+ for (const child of menu.children) {
302
+ visit(child);
303
+ }
304
+ }
305
+ };
306
+
307
+ for (const menu of menus) {
308
+ visit(menu);
309
+ }
310
+
311
+ return result;
312
+ }
313
+
314
+ export async function ensureOpenApiMenuForForm(
315
+ config,
316
+ accessToken,
317
+ { appType, formUuid, name, formType = "receipt", parentId = "", icon = "" },
318
+ ) {
319
+ assertRequiredOpenApiConfig(config);
320
+
321
+ const targetAppType = appType || config.appType;
322
+ const menus = flattenMenus(
323
+ await listOpenApiMenus(config, accessToken, targetAppType),
324
+ );
325
+ const existing = menus.find((menu) => menu.formUuid === formUuid);
326
+ if (existing) {
327
+ return {
328
+ created: false,
329
+ menu: existing,
330
+ };
331
+ }
332
+
333
+ const apiBase = getApiBaseUrl(config);
334
+ const payload = {
335
+ userId: config.userId,
336
+ appType: targetAppType,
337
+ name,
338
+ type: formType || "receipt",
339
+ formUuid,
340
+ parentId: parentId || null,
341
+ icon: icon || null,
342
+ };
343
+
344
+ const res = await fetch(`${apiBase}/dingtalk-api/v1.0/forms/menu/create`, {
345
+ method: "POST",
346
+ headers: {
347
+ "Content-Type": "application/json",
348
+ "x-acs-dingtalk-access-token": accessToken,
349
+ },
350
+ body: JSON.stringify(payload),
351
+ });
352
+
353
+ let body = null;
354
+ try {
355
+ body = await res.json();
356
+ } catch {
357
+ body = null;
358
+ }
359
+
360
+ if (!res.ok || body?.code !== 200) {
361
+ const message = body?.message || res.statusText || "unknown error";
362
+ throw new Error(`创建菜单失败: HTTP ${res.status} ${message}`);
363
+ }
364
+
365
+ return {
366
+ created: true,
367
+ menu: body.data,
368
+ };
369
+ }
370
+
371
+ export async function ensureSchemaFormUuid({
372
+ config,
373
+ schemaPath,
374
+ formName,
375
+ accessToken,
376
+ dryRun = false,
377
+ ensureMenu = true,
378
+ }) {
379
+ const meta = readSchemaMeta(schemaPath);
380
+ if (meta.formUuid) {
381
+ let created = false;
382
+ if (!dryRun && ensureMenu) {
383
+ const token = accessToken || (await getOpenApiAccessToken(config));
384
+ const appType = meta.appType || config.appType;
385
+ const existingForm = await getOpenApiForm(config, token, {
386
+ appType,
387
+ formUuid: meta.formUuid,
388
+ });
389
+
390
+ if (!existingForm) {
391
+ await createOpenApiForm(config, token, {
392
+ name: meta.title || formName,
393
+ appType,
394
+ formType: meta.formType || "receipt",
395
+ relateUuid: meta.relateUuid,
396
+ formUuid: meta.formUuid,
397
+ builderVersion: config.defaults?.formBuilderVersion || "2.0",
398
+ });
399
+ created = true;
400
+ console.log(
401
+ ` ✓ ${formName}: 已按 schema formUuid 创建表单 ${meta.formUuid}`,
402
+ );
403
+ }
404
+
405
+ const menuResult = await ensureOpenApiMenuForForm(config, token, {
406
+ appType,
407
+ formUuid: meta.formUuid,
408
+ name: meta.title || formName,
409
+ formType: meta.formType || "receipt",
410
+ parentId: config.menu?.parentId,
411
+ icon: config.menu?.icon,
412
+ });
413
+
414
+ if (menuResult.created) {
415
+ console.log(` ✓ ${formName}: 已创建菜单 ${menuResult.menu?.id || ""}`);
416
+ }
417
+ }
418
+
419
+ return {
420
+ ...meta,
421
+ created,
422
+ dryRunCreated: false,
423
+ };
424
+ }
425
+
426
+ if (!meta.hasFormUuidProp) {
427
+ throw new Error(
428
+ `${formName}: schema.ts 中未找到 formUuid 字段,无法自动创建并回写`,
429
+ );
430
+ }
431
+
432
+ const createOptions = {
433
+ name: meta.title || formName,
434
+ appType: meta.appType || config.appType,
435
+ formType: meta.formType || "receipt",
436
+ relateUuid: meta.relateUuid,
437
+ formUuid: meta.formUuid || undefined,
438
+ builderVersion: config.defaults?.formBuilderVersion || "2.0",
439
+ };
440
+
441
+ if (dryRun) {
442
+ console.log(
443
+ ` [dry-run] ${formName}: formUuid 为空,将创建表单和菜单 "${createOptions.name}"`,
444
+ );
445
+ return {
446
+ ...meta,
447
+ ...createOptions,
448
+ formUuid: "<auto-created-formUuid>",
449
+ created: false,
450
+ dryRunCreated: true,
451
+ };
452
+ }
453
+
454
+ const token = accessToken || (await getOpenApiAccessToken(config));
455
+ const createdForm = await createOpenApiForm(config, token, createOptions);
456
+ writeSchemaFormUuid(schemaPath, createdForm.formUuid);
457
+ console.log(` ✓ ${formName}: 已创建表单 ${createdForm.formUuid}`);
458
+
459
+ const menuResult = await ensureOpenApiMenuForForm(config, token, {
460
+ appType: createOptions.appType,
461
+ formUuid: createdForm.formUuid,
462
+ name: createOptions.name,
463
+ formType: createOptions.formType,
464
+ parentId: config.menu?.parentId,
465
+ icon: config.menu?.icon,
466
+ });
467
+
468
+ if (menuResult.created) {
469
+ console.log(` ✓ ${formName}: 已创建菜单 ${menuResult.menu?.id || ""}`);
470
+ } else {
471
+ console.log(` ✓ ${formName}: 菜单已存在 ${menuResult.menu?.id || ""}`);
472
+ }
473
+
474
+ return {
475
+ ...meta,
476
+ ...createOptions,
477
+ formUuid: createdForm.formUuid,
478
+ created: true,
479
+ menuCreated: menuResult.created,
480
+ dryRunCreated: false,
481
+ };
482
+ }
@@ -0,0 +1,210 @@
1
+ import { config as dotenvConfig } from "dotenv";
2
+ import { resolve } from "path";
3
+ import { pathToFileURL } from "url";
4
+
5
+ export const rootDir = resolve(process.env.LOWCODE_WORKSPACE_ROOT || process.cwd());
6
+
7
+ /**
8
+ * 标准化 OSS 路径前缀,去除前后斜杠
9
+ * @param {string|undefined} pathPrefix
10
+ * @returns {string}
11
+ */
12
+ function normalizePathPrefix(pathPrefix) {
13
+ return String(pathPrefix || "app-workspace").replace(/^\/+|\/+$/g, "");
14
+ }
15
+
16
+ /**
17
+ * 解析逗号分隔的字符串为数组
18
+ * @param {string|undefined} value
19
+ * @param {string} [fallback]
20
+ * @returns {string[]}
21
+ */
22
+ function parseCsv(value, fallback) {
23
+ return String(value || fallback || "")
24
+ .split(",")
25
+ .map((item) => item.trim())
26
+ .filter(Boolean);
27
+ }
28
+
29
+ function getDefaultCorsOrigin(platformUrl) {
30
+ try {
31
+ return new URL(platformUrl).origin;
32
+ } catch {
33
+ return "*";
34
+ }
35
+ }
36
+
37
+ function createBuildId() {
38
+ return new Date().toISOString().replace(/[-:TZ.]/g, "").slice(0, 14);
39
+ }
40
+
41
+ function normalizeBaseUrl(value) {
42
+ return String(value || "").replace(/\/+$/, "");
43
+ }
44
+
45
+ async function loadConfigModule() {
46
+ const configPath = resolve(rootDir, "app-workspace.config.ts");
47
+ const loaded = await import(pathToFileURL(configPath).href);
48
+ return loaded.default || loaded;
49
+ }
50
+
51
+ /**
52
+ * 加载并合并应用配置(文件 + 环境变量)
53
+ * @returns {Promise<object>} 合并后的应用配置对象
54
+ */
55
+ export async function loadConfig() {
56
+ const mode = process.env.APP_MODE || "development";
57
+
58
+ if (mode !== "development") {
59
+ dotenvConfig({ path: resolve(rootDir, `.env.${mode}`) });
60
+ }
61
+ dotenvConfig({ path: resolve(rootDir, ".env") });
62
+
63
+ const source = await loadConfigModule();
64
+ const platformUrl = source.platformUrl || process.env.APP_PLATFORM_URL;
65
+ const explicitBuildId = Boolean(source.buildId || process.env.APP_BUILD_ID);
66
+
67
+ const normalized = {
68
+ ...source,
69
+ appType: source.appType || process.env.APP_TYPE,
70
+ appName: source.appName || process.env.APP_NAME || "低代码应用",
71
+ platformUrl,
72
+ servicePrefix: source.servicePrefix || process.env.APP_SERVICE_PREFIX || "/service",
73
+ appKey: source.appKey || process.env.APP_KEY,
74
+ appSecret: source.appSecret || process.env.APP_SECRET,
75
+ userId: source.userId || process.env.APP_USER_ID,
76
+ version: source.version || process.env.APP_VERSION || "0.1.0",
77
+ buildId: source.buildId || process.env.APP_BUILD_ID || createBuildId(),
78
+ buildIdExplicit: explicitBuildId,
79
+ oss: {
80
+ ...(source.oss || {}),
81
+ region: source.oss?.region || process.env.APP_OSS_REGION,
82
+ bucket: source.oss?.bucket || process.env.APP_OSS_BUCKET,
83
+ accessKeyId:
84
+ source.oss?.accessKeyId || process.env.APP_OSS_ACCESS_KEY_ID,
85
+ accessKeySecret:
86
+ source.oss?.accessKeySecret || process.env.APP_OSS_ACCESS_KEY_SECRET,
87
+ pathPrefix: normalizePathPrefix(
88
+ source.oss?.pathPrefix || process.env.APP_OSS_PATH_PREFIX,
89
+ ),
90
+ corsOrigins: parseCsv(
91
+ process.env.APP_OSS_CORS_ORIGINS,
92
+ getDefaultCorsOrigin(platformUrl),
93
+ ),
94
+ skipCors: process.env.APP_OSS_SKIP_CORS === "1" || source.oss?.skipCors,
95
+ },
96
+ defaults: {
97
+ protocolVersion:
98
+ source.defaults?.protocolVersion ||
99
+ process.env.APP_PAGE_PROTOCOL_VERSION ||
100
+ "1.0",
101
+ frameworkVersion:
102
+ source.defaults?.frameworkVersion ||
103
+ process.env.APP_FRAMEWORK_VERSION ||
104
+ "19.0.0",
105
+ cssIsolation:
106
+ source.defaults?.cssIsolation === "none" ||
107
+ process.env.APP_PAGE_CSS_ISOLATION === "none"
108
+ ? "none"
109
+ : "shadow",
110
+ formMenuParentId:
111
+ source.defaults?.formMenuParentId ||
112
+ process.env.APP_FORM_MENU_PARENT_ID ||
113
+ "",
114
+ formMenuIcon:
115
+ source.defaults?.formMenuIcon || process.env.APP_FORM_MENU_ICON || "",
116
+ pageMenuParentId:
117
+ source.defaults?.pageMenuParentId ||
118
+ process.env.APP_PAGE_MENU_PARENT_ID ||
119
+ "",
120
+ pageMenuIcon:
121
+ source.defaults?.pageMenuIcon || process.env.APP_PAGE_MENU_ICON || "",
122
+ formBuilderVersion:
123
+ source.defaults?.formBuilderVersion ||
124
+ process.env.APP_FORM_BUILDER_VERSION ||
125
+ "2.0",
126
+ },
127
+ };
128
+
129
+ return {
130
+ ...normalized,
131
+ menu: {
132
+ parentId: normalized.defaults.formMenuParentId,
133
+ icon: normalized.defaults.formMenuIcon,
134
+ },
135
+ };
136
+ }
137
+
138
+ /**
139
+ * 获取后端 API 基础 URL
140
+ * @param {object} config - 应用配置对象
141
+ * @returns {string}
142
+ */
143
+ export function getApiBaseUrl(config) {
144
+ return `${normalizeBaseUrl(config.platformUrl)}${config.servicePrefix}`;
145
+ }
146
+
147
+ /**
148
+ * 获取 OSS 公网访问基础 URL
149
+ * @param {object} config - 应用配置对象
150
+ * @returns {string}
151
+ */
152
+ export function getPublicBaseUrl(config) {
153
+ return `https://${config.oss.bucket}.${config.oss.region}.aliyuncs.com/${config.oss.pathPrefix}/${config.version}/${config.buildId}`;
154
+ }
155
+
156
+ /**
157
+ * 获取表单 bundle 文件的公网 URL
158
+ * @param {object} config - 应用配置对象
159
+ * @param {string} formName - 表单名
160
+ * @param {string} fileName - 文件名
161
+ * @returns {string}
162
+ */
163
+ export function getFormBundleUrl(config, formName, fileName) {
164
+ return `${getPublicBaseUrl(config)}/forms/${formName}/${fileName}`;
165
+ }
166
+
167
+ /**
168
+ * 获取表单共享 runtime 文件的公网 URL
169
+ * @param {object} config - 应用配置对象
170
+ * @param {string} fileName - 文件名
171
+ * @returns {string}
172
+ */
173
+ export function getFormRuntimeUrl(config, fileName) {
174
+ return `${getPublicBaseUrl(config)}/form-runtime/${fileName}`;
175
+ }
176
+
177
+ /**
178
+ * 获取代码页资源基础 URL
179
+ * @param {object} config - 应用配置对象
180
+ * @param {string} pageCode - 页面编码
181
+ * @returns {string}
182
+ */
183
+ export function getPageAssetBaseUrl(config, pageCode) {
184
+ return `${getPublicBaseUrl(config)}/pages/${pageCode}`;
185
+ }
186
+
187
+ /**
188
+ * 获取代码页指定资源的公网 URL
189
+ * @param {object} config - 应用配置对象
190
+ * @param {string} pageCode - 页面编码
191
+ * @param {string} fileName - 文件名
192
+ * @returns {string}
193
+ */
194
+ export function getPageAssetUrl(config, pageCode, fileName) {
195
+ return `${getPageAssetBaseUrl(config, pageCode)}/${fileName}`;
196
+ }
197
+
198
+ /**
199
+ * 获取代码页共享 runtime 文件的公网 URL
200
+ * @param {object} config - 应用配置对象
201
+ * @param {string} fileName - 文件名
202
+ * @returns {string}
203
+ */
204
+ export function getPageRuntimeUrl(config, fileName) {
205
+ return `${getPublicBaseUrl(config)}/page-runtime/${fileName}`;
206
+ }
207
+
208
+ export function getBundleOSSUrl(config, formName, fileName) {
209
+ return getFormBundleUrl(config, formName, fileName);
210
+ }