@lark-apaas/fullstack-cli 1.1.22-alpha.24 → 1.1.22-alpha.26

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.
@@ -120,100 +120,4 @@ export const customTimestamptz = customType<{
120
120
  if(value instanceof Date) return value;
121
121
  return new Date(value);
122
122
  },
123
- });
124
-
125
- /**
126
- * User type marker for user_profile_domain
127
- * Simple marker type, no conversion needed - passes through as string
128
- */
129
- export const userType = customType<{
130
- data: string;
131
- driverData: string;
132
- }>({
133
- dataType() {
134
- return 'user_profile_domain';
135
- },
136
- toDriver(value: string) {
137
- return value;
138
- },
139
- fromDriver(value: string) {
140
- return value;
141
- },
142
- });
143
-
144
- /**
145
- * File type marker for file_attachment_domain
146
- * Simple marker type, no conversion needed - passes through as string
147
- */
148
- export const fileType = customType<{
149
- data: string;
150
- driverData: string;
151
- }>({
152
- dataType() {
153
- return 'file_attachment_domain';
154
- },
155
- toDriver(value: string) {
156
- return value;
157
- },
158
- fromDriver(value: string) {
159
- return value;
160
- },
161
- });
162
-
163
- /**
164
- * User type array for user_profile_domain[]
165
- */
166
- export const userTypeArray = customType<{
167
- data: string[];
168
- driverData: string;
169
- }>({
170
- dataType() {
171
- return 'user_profile_domain[]';
172
- },
173
- toDriver(value: string[]) {
174
- return sql`${value}`;
175
- },
176
- fromDriver(value: string): string[] {
177
- if (!value || value === '{}') return [];
178
- // Parse PostgreSQL array format: {"item1","item2"}
179
- const inner = value.slice(1, -1);
180
- if (!inner) return [];
181
- return inner.split(',').map(item => {
182
- // Handle quoted strings
183
- const trimmed = item.trim();
184
- if (trimmed.startsWith('"') && trimmed.endsWith('"')) {
185
- return trimmed.slice(1, -1).replace(/\\"/g, '"');
186
- }
187
- return trimmed;
188
- });
189
- },
190
- });
191
-
192
- /**
193
- * File type array for file_attachment_domain[]
194
- */
195
- export const fileTypeArray = customType<{
196
- data: string[];
197
- driverData: string;
198
- }>({
199
- dataType() {
200
- return 'file_attachment_domain[]';
201
- },
202
- toDriver(value: string[]) {
203
- return sql`${value}`;
204
- },
205
- fromDriver(value: string): string[] {
206
- if (!value || value === '{}') return [];
207
- // Parse PostgreSQL array format: {"item1","item2"}
208
- const inner = value.slice(1, -1);
209
- if (!inner) return [];
210
- return inner.split(',').map(item => {
211
- // Handle quoted strings
212
- const trimmed = item.trim();
213
- if (trimmed.startsWith('"') && trimmed.endsWith('"')) {
214
- return trimmed.slice(1, -1).replace(/\\"/g, '"');
215
- }
216
- return trimmed;
217
- });
218
- },
219
123
  });
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  // src/index.ts
2
- import fs25 from "fs";
3
- import path21 from "path";
2
+ import fs26 from "fs";
3
+ import path22 from "path";
4
4
  import { fileURLToPath as fileURLToPath5 } from "url";
5
5
  import { config as dotenvConfig } from "dotenv";
6
6
 
@@ -469,18 +469,8 @@ var patchDefectsTransform = {
469
469
  // src/commands/db/gen-dbschema/transforms/ast/replace-unknown.ts
470
470
  import { Node as Node5 } from "ts-morph";
471
471
  var KNOWN_TYPE_FACTORIES = {
472
- // Domain types (new)
473
- user_profile_domain: "userType",
474
- file_attachment_domain: "fileType",
475
- // Domain array types (new)
476
- "user_profile_domain[]": "userTypeArray",
477
- "file_attachment_domain[]": "fileTypeArray",
478
- // Legacy composite types (deprecated but kept for compatibility)
479
472
  user_profile: "userProfile",
480
- file_attachment: "fileAttachment",
481
- // Legacy array types
482
- "user_profile[]": "userProfile",
483
- "file_attachment[]": "fileAttachment"
473
+ file_attachment: "fileAttachment"
484
474
  };
485
475
  var KNOWN_ARRAY_TYPE_FACTORIES = {
486
476
  user_profile: "userProfileArray",
@@ -510,11 +500,10 @@ var replaceUnknownTransform = {
510
500
  const line = lines[i];
511
501
  const todoMatch = line.match(/\/\/ TODO: failed to parse database type '(?:\w+\.)?([\w_]+)(\[\])?'/);
512
502
  if (todoMatch) {
513
- const baseTypeName = todoMatch[1];
503
+ const typeName = todoMatch[1];
514
504
  isArrayType = todoMatch[2] === "[]";
515
- const typeName = baseTypeName + (todoMatch[2] || "");
516
- if (isArrayType && KNOWN_ARRAY_TYPE_FACTORIES[baseTypeName]) {
517
- factoryName = KNOWN_ARRAY_TYPE_FACTORIES[baseTypeName];
505
+ if (isArrayType && KNOWN_ARRAY_TYPE_FACTORIES[typeName]) {
506
+ factoryName = KNOWN_ARRAY_TYPE_FACTORIES[typeName];
518
507
  foundKnownType = true;
519
508
  } else if (KNOWN_TYPE_FACTORIES[typeName]) {
520
509
  factoryName = KNOWN_TYPE_FACTORIES[typeName];
@@ -4296,7 +4285,7 @@ var PROMPT_PATTERNS = [
4296
4285
  { pattern: /proceed\?/i, answer: "y\n" }
4297
4286
  ];
4298
4287
  async function executeShadcnAdd(registryItemPath) {
4299
- return new Promise((resolve) => {
4288
+ return new Promise((resolve2) => {
4300
4289
  let output = "";
4301
4290
  const args = ["--yes", "shadcn@3.8.2", "add", registryItemPath];
4302
4291
  const ptyProcess = pty.spawn("npx", args, {
@@ -4322,7 +4311,7 @@ async function executeShadcnAdd(registryItemPath) {
4322
4311
  });
4323
4312
  const timeoutId = setTimeout(() => {
4324
4313
  ptyProcess.kill();
4325
- resolve({
4314
+ resolve2({
4326
4315
  success: false,
4327
4316
  files: [],
4328
4317
  error: "\u6267\u884C\u8D85\u65F6"
@@ -4333,7 +4322,7 @@ async function executeShadcnAdd(registryItemPath) {
4333
4322
  const success = exitCode === 0;
4334
4323
  const filePaths = parseOutput(output);
4335
4324
  const files = filePaths.map(toFileInfo);
4336
- resolve({
4325
+ resolve2({
4337
4326
  success,
4338
4327
  files,
4339
4328
  error: success ? void 0 : output || `Process exited with code ${exitCode}`
@@ -4344,12 +4333,12 @@ async function executeShadcnAdd(registryItemPath) {
4344
4333
 
4345
4334
  // src/commands/component/add.handler.ts
4346
4335
  function runActionPluginInit() {
4347
- return new Promise((resolve) => {
4336
+ return new Promise((resolve2) => {
4348
4337
  execFile("fullstack-cli", ["action-plugin", "init"], { cwd: process.cwd(), stdio: "ignore" }, (error) => {
4349
4338
  if (error) {
4350
4339
  debug("action-plugin init \u5931\u8D25: %s", error.message);
4351
4340
  }
4352
- resolve();
4341
+ resolve2();
4353
4342
  });
4354
4343
  });
4355
4344
  }
@@ -7185,7 +7174,45 @@ async function genArtifactUploadCredential(appId, body) {
7185
7174
  const response = await client.post(url, body);
7186
7175
  if (!response.ok || response.status !== 200) {
7187
7176
  throw new Error(
7188
- `API request failed: ${response.status} ${response.statusText}`
7177
+ `gen_artifact_upload_credential \u8BF7\u6C42\u5931\u8D25: ${response.status} ${response.statusText}`
7178
+ );
7179
+ }
7180
+ return response.json();
7181
+ }
7182
+ async function getDefaultBucketId(appId) {
7183
+ const client = getHttpClient();
7184
+ const url = `/b/${appId}/get_published_v2`;
7185
+ const response = await client.get(url);
7186
+ if (!response.ok || response.status !== 200) {
7187
+ throw new Error(
7188
+ `get_published_v2 \u8BF7\u6C42\u5931\u8D25: ${response.status} ${response.statusText}`
7189
+ );
7190
+ }
7191
+ const data = await response.json();
7192
+ const bucketId = data?.data?.app_runtime_extra?.bucket?.default_bucket_id;
7193
+ if (!bucketId) {
7194
+ throw new Error(`\u672A\u627E\u5230\u5E94\u7528 ${appId} \u7684\u9ED8\u8BA4\u5B58\u50A8\u6876`);
7195
+ }
7196
+ return bucketId;
7197
+ }
7198
+ async function preUploadStaticAttachment(appId, bucketId) {
7199
+ const client = getHttpClient();
7200
+ const url = `/v1/app/${appId}/storage/bucket/${bucketId}/preUploadStatic`;
7201
+ const response = await client.post(url, {});
7202
+ if (!response.ok || response.status !== 200) {
7203
+ throw new Error(
7204
+ `preUploadStatic \u8BF7\u6C42\u5931\u8D25: ${response.status} ${response.statusText}`
7205
+ );
7206
+ }
7207
+ return response.json();
7208
+ }
7209
+ async function uploadStaticAttachmentCallback(appId, bucketId, body) {
7210
+ const client = getHttpClient();
7211
+ const url = `/v1/app/${appId}/storage/bucket/${bucketId}/object/callbackStatic`;
7212
+ const response = await client.post(url, body);
7213
+ if (!response.ok || response.status !== 200) {
7214
+ throw new Error(
7215
+ `callbackStatic \u8BF7\u6C42\u5931\u8D25: ${response.status} ${response.statusText}`
7189
7216
  );
7190
7217
  }
7191
7218
  return response.json();
@@ -7223,6 +7250,133 @@ function camelToKebab(str) {
7223
7250
  return str.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
7224
7251
  }
7225
7252
 
7253
+ // src/commands/build/upload-static.handler.ts
7254
+ import * as fs25 from "fs";
7255
+ import * as path21 from "path";
7256
+ import { execSync as execSync3 } from "child_process";
7257
+ var LOG_PREFIX = "[upload-static]";
7258
+ async function uploadStatic(options) {
7259
+ try {
7260
+ const {
7261
+ appId,
7262
+ staticDir = "shared/static",
7263
+ tosutilPath = "/workspace/tosutil",
7264
+ tosutilUrl,
7265
+ endpoint = "tos-cn-beijing.volces.com",
7266
+ region = "cn-beijing"
7267
+ } = options;
7268
+ const resolvedStaticDir = path21.resolve(staticDir);
7269
+ if (!fs25.existsSync(resolvedStaticDir)) {
7270
+ console.error(`${LOG_PREFIX} \u76EE\u5F55\u4E0D\u5B58\u5728: ${resolvedStaticDir}\uFF0C\u8DF3\u8FC7\u4E0A\u4F20`);
7271
+ return;
7272
+ }
7273
+ if (isDirEmpty(resolvedStaticDir)) {
7274
+ console.error(`${LOG_PREFIX} \u76EE\u5F55\u4E3A\u7A7A: ${resolvedStaticDir}\uFF0C\u8DF3\u8FC7\u4E0A\u4F20`);
7275
+ return;
7276
+ }
7277
+ const resolvedTosutilPath = ensureTosutil(tosutilPath, tosutilUrl);
7278
+ const bucketId = await resolveBucketId(appId);
7279
+ console.error(`${LOG_PREFIX} \u8C03\u7528 preUploadStatic...`);
7280
+ const preUploadResp = await fetchPreUpload(appId, bucketId);
7281
+ const { uploadPrefix, uploadID, uploadCredential } = preUploadResp.data;
7282
+ console.error(`${LOG_PREFIX} \u4E0A\u4F20\u76EE\u6807: ${uploadPrefix}`);
7283
+ console.error(`${LOG_PREFIX} \u914D\u7F6E tosutil...`);
7284
+ configureTosutil(resolvedTosutilPath, {
7285
+ endpoint,
7286
+ region,
7287
+ accessKeyID: uploadCredential.accessKeyID,
7288
+ secretAccessKey: uploadCredential.secretAccessKey,
7289
+ sessionToken: uploadCredential.sessionToken
7290
+ });
7291
+ console.error(`${LOG_PREFIX} \u4E0A\u4F20 ${resolvedStaticDir} -> ${uploadPrefix}`);
7292
+ uploadToTos(resolvedTosutilPath, resolvedStaticDir, uploadPrefix);
7293
+ console.error(`${LOG_PREFIX} tosutil \u4E0A\u4F20\u5B8C\u6210`);
7294
+ console.error(`${LOG_PREFIX} \u8C03\u7528 callbackStatic (uploadID: ${uploadID})...`);
7295
+ const callbackResp = await uploadStaticAttachmentCallback(appId, bucketId, { uploadID });
7296
+ if (callbackResp.status_code !== "0") {
7297
+ throw new Error(`callbackStatic \u8FD4\u56DE\u5F02\u5E38, status_code: ${callbackResp.status_code}`);
7298
+ }
7299
+ const attachments = callbackResp.data?.attachments || [];
7300
+ console.error(`${LOG_PREFIX} \u4E0A\u4F20\u5B8C\u6210\uFF0C\u5171 ${attachments.length} \u4E2A\u6587\u4EF6`);
7301
+ console.log(JSON.stringify(callbackResp));
7302
+ } catch (error) {
7303
+ const message = error instanceof Error ? error.message : String(error);
7304
+ console.error(`${LOG_PREFIX} Error: ${message}`);
7305
+ process.exit(1);
7306
+ }
7307
+ }
7308
+ async function fetchPreUpload(appId, bucketId) {
7309
+ const response = await preUploadStaticAttachment(appId, bucketId);
7310
+ if (response.status_code !== "0") {
7311
+ throw new Error(`preUploadStatic \u8FD4\u56DE\u5F02\u5E38, status_code: ${response.status_code}`);
7312
+ }
7313
+ const { uploadPrefix, uploadID, uploadCredential } = response.data || {};
7314
+ if (!uploadPrefix || !uploadID) {
7315
+ throw new Error("preUploadStatic \u8FD4\u56DE\u6570\u636E\u4E0D\u5B8C\u6574\uFF0C\u7F3A\u5C11 uploadPrefix \u6216 uploadID");
7316
+ }
7317
+ if (!uploadCredential?.accessKeyID || !uploadCredential?.secretAccessKey || !uploadCredential?.sessionToken) {
7318
+ throw new Error("preUploadStatic \u8FD4\u56DE\u7684\u51ED\u8BC1\u5B57\u6BB5\u4E0D\u5B8C\u6574");
7319
+ }
7320
+ return response;
7321
+ }
7322
+ function ensureTosutil(tosutilPath, tosutilUrl) {
7323
+ if (isTosutilAvailable(tosutilPath)) {
7324
+ console.error(`${LOG_PREFIX} tosutil \u5DF2\u5C31\u7EEA: ${tosutilPath}`);
7325
+ return tosutilPath;
7326
+ }
7327
+ if (!tosutilUrl) {
7328
+ throw new Error(
7329
+ `tosutil \u4E0D\u53EF\u7528 (\u8DEF\u5F84: ${tosutilPath})\uFF0C\u4E14\u672A\u63D0\u4F9B --tosutil-url \u4E0B\u8F7D\u5730\u5740\u3002\u8BF7\u786E\u4FDD tosutil \u5DF2\u5B58\u5728\uFF0C\u6216\u901A\u8FC7 --tosutil-url \u6307\u5B9A\u4E0B\u8F7D\u5730\u5740\u3002`
7330
+ );
7331
+ }
7332
+ console.error(`${LOG_PREFIX} tosutil \u4E0D\u5B58\u5728\uFF0C\u4ECE ${tosutilUrl} \u4E0B\u8F7D...`);
7333
+ downloadTosutil(tosutilUrl, tosutilPath);
7334
+ if (!isTosutilAvailable(tosutilPath)) {
7335
+ throw new Error(`tosutil \u4E0B\u8F7D\u540E\u4ECD\u4E0D\u53EF\u7528 (\u8DEF\u5F84: ${tosutilPath})`);
7336
+ }
7337
+ console.error(`${LOG_PREFIX} tosutil \u4E0B\u8F7D\u5B8C\u6210: ${tosutilPath}`);
7338
+ return tosutilPath;
7339
+ }
7340
+ function isTosutilAvailable(tosutilPath) {
7341
+ try {
7342
+ execSync3(`${tosutilPath} --version`, { stdio: "pipe" });
7343
+ return true;
7344
+ } catch {
7345
+ return false;
7346
+ }
7347
+ }
7348
+ function downloadTosutil(url, destPath) {
7349
+ const destDir = path21.dirname(destPath);
7350
+ if (!fs25.existsSync(destDir)) {
7351
+ fs25.mkdirSync(destDir, { recursive: true });
7352
+ }
7353
+ execSync3(`curl -L --fail -o "${destPath}" "${url}"`, { stdio: "pipe" });
7354
+ execSync3(`chmod +x "${destPath}"`, { stdio: "pipe" });
7355
+ }
7356
+ function configureTosutil(tosutilPath, config) {
7357
+ const { endpoint, region, accessKeyID, secretAccessKey, sessionToken } = config;
7358
+ execSync3(
7359
+ `${tosutilPath} config -e ${endpoint} -i ${accessKeyID} -k ${secretAccessKey} -t ${sessionToken} -re ${region}`,
7360
+ { stdio: "pipe" }
7361
+ );
7362
+ }
7363
+ function uploadToTos(tosutilPath, sourceDir, destUrl) {
7364
+ execSync3(
7365
+ `${tosutilPath} cp "${sourceDir}" "${destUrl}" -r -flat -j 5 -p 3 -ps 10485760 -f`,
7366
+ { stdio: "inherit" }
7367
+ );
7368
+ }
7369
+ async function resolveBucketId(appId) {
7370
+ console.error(`${LOG_PREFIX} \u83B7\u53D6\u9ED8\u8BA4\u5B58\u50A8\u6876...`);
7371
+ const bucketId = await getDefaultBucketId(appId);
7372
+ console.error(`${LOG_PREFIX} \u9ED8\u8BA4\u5B58\u50A8\u6876: ${bucketId}`);
7373
+ return bucketId;
7374
+ }
7375
+ function isDirEmpty(dirPath) {
7376
+ const entries = fs25.readdirSync(dirPath);
7377
+ return entries.length === 0;
7378
+ }
7379
+
7226
7380
  // src/commands/build/index.ts
7227
7381
  var getTokenCommand = {
7228
7382
  name: "get-token",
@@ -7233,10 +7387,19 @@ var getTokenCommand = {
7233
7387
  });
7234
7388
  }
7235
7389
  };
7390
+ var uploadStaticCommand = {
7391
+ name: "upload-static",
7392
+ description: "Upload shared/static files to TOS",
7393
+ register(program) {
7394
+ program.command(this.name).description(this.description).requiredOption("--app-id <id>", "Application ID").option("--static-dir <dir>", "Static files directory", "shared/static").option("--tosutil-path <path>", "Path to tosutil binary", "/workspace/tosutil").option("--tosutil-url <url>", "URL to download tosutil if not present").option("--endpoint <endpoint>", "TOS endpoint", "tos-cn-beijing.volces.com").option("--region <region>", "TOS region", "cn-beijing").action(async (options) => {
7395
+ await uploadStatic(options);
7396
+ });
7397
+ }
7398
+ };
7236
7399
  var buildCommandGroup = {
7237
7400
  name: "build",
7238
7401
  description: "Build related commands",
7239
- commands: [getTokenCommand]
7402
+ commands: [getTokenCommand, uploadStaticCommand]
7240
7403
  };
7241
7404
 
7242
7405
  // src/commands/index.ts
@@ -7253,12 +7416,12 @@ var commands = [
7253
7416
  ];
7254
7417
 
7255
7418
  // src/index.ts
7256
- var envPath = path21.join(process.cwd(), ".env");
7257
- if (fs25.existsSync(envPath)) {
7419
+ var envPath = path22.join(process.cwd(), ".env");
7420
+ if (fs26.existsSync(envPath)) {
7258
7421
  dotenvConfig({ path: envPath });
7259
7422
  }
7260
- var __dirname = path21.dirname(fileURLToPath5(import.meta.url));
7261
- var pkg = JSON.parse(fs25.readFileSync(path21.join(__dirname, "../package.json"), "utf-8"));
7423
+ var __dirname = path22.dirname(fileURLToPath5(import.meta.url));
7424
+ var pkg = JSON.parse(fs26.readFileSync(path22.join(__dirname, "../package.json"), "utf-8"));
7262
7425
  var cli = new FullstackCLI(pkg.version);
7263
7426
  cli.useAll(commands);
7264
7427
  cli.run();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lark-apaas/fullstack-cli",
3
- "version": "1.1.22-alpha.24",
3
+ "version": "1.1.22-alpha.26",
4
4
  "description": "CLI tool for fullstack template management",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -3,7 +3,7 @@
3
3
  "collection": "@nestjs/schematics",
4
4
  "sourceRoot": "server",
5
5
  "compilerOptions": {
6
- "deleteOutDir": true,
6
+ "deleteOutDir": false,
7
7
  "tsConfigPath": "tsconfig.node.json",
8
8
  "assets": [
9
9
  {
@@ -19,28 +19,28 @@ print_time() {
19
19
  }
20
20
 
21
21
  # ==================== 步骤 0 ====================
22
- echo "🗑️ [0/5] 安装插件"
22
+ echo "🗑️ [0/6] 安装插件"
23
23
  STEP_START=$(node -e "console.log(Date.now())")
24
24
  npx fullstack-cli action-plugin init
25
25
  print_time $STEP_START
26
26
  echo ""
27
27
 
28
28
  # ==================== 步骤 1 ====================
29
- echo "📝 [1/5] 更新 openapi 代码"
29
+ echo "📝 [1/6] 更新 openapi 代码"
30
30
  STEP_START=$(node -e "console.log(Date.now())")
31
31
  npm run gen:openapi
32
32
  print_time $STEP_START
33
33
  echo ""
34
34
 
35
35
  # ==================== 步骤 2 ====================
36
- echo "🗑️ [2/5] 清理 dist 目录"
36
+ echo "🗑️ [2/6] 清理 dist 目录"
37
37
  STEP_START=$(node -e "console.log(Date.now())")
38
38
  rm -rf "$ROOT_DIR/dist"
39
39
  print_time $STEP_START
40
40
  echo ""
41
41
 
42
42
  # ==================== 步骤 3 ====================
43
- echo "🔨 [3/5] 并行构建 server 和 client"
43
+ echo "🔨 [3/6] 并行构建 server 和 client"
44
44
  STEP_START=$(node -e "console.log(Date.now())")
45
45
 
46
46
  # 并行构建
@@ -78,7 +78,7 @@ print_time $STEP_START
78
78
  echo ""
79
79
 
80
80
  # ==================== 步骤 4 ====================
81
- echo "📦 [4/5] 准备 server 依赖产物"
81
+ echo "📦 [4/6] 准备 server 依赖产物"
82
82
  STEP_START=$(node -e "console.log(Date.now())")
83
83
 
84
84
  mkdir -p "$OUT_DIR/dist/client"
@@ -97,7 +97,7 @@ print_time $STEP_START
97
97
  echo ""
98
98
 
99
99
  # ==================== 步骤 5 ====================
100
- echo "✂️ [5/5] 智能依赖裁剪"
100
+ echo "✂️ [5/6] 智能依赖裁剪"
101
101
  STEP_START=$(node -e "console.log(Date.now())")
102
102
 
103
103
  # 分析实际依赖、复制并裁剪 node_modules、生成精简的 package.json
@@ -106,6 +106,25 @@ node "$ROOT_DIR/scripts/prune-smart.js"
106
106
  print_time $STEP_START
107
107
  echo ""
108
108
 
109
+ # ==================== 步骤 6 ====================
110
+ echo "☁️ [6/6] 上传静态资源到 TOS"
111
+ STEP_START=$(node -e "console.log(Date.now())")
112
+
113
+ # 环境变量(与产物上传脚本共享同一 CI 环境):
114
+ # app_id - 应用 ID
115
+ # tosutil_url - tosutil 下载地址
116
+ # bucket_id 由 CLI 自动通过 get_published_v2 获取,也可通过 --bucket-id 显式指定
117
+ if [ -d "$ROOT_DIR/shared/static" ] && [ "$(ls -A "$ROOT_DIR/shared/static" 2>/dev/null)" ]; then
118
+ npx fullstack-cli build upload-static \
119
+ --app-id "${app_id}" \
120
+ --tosutil-url "${tosutil_url}"
121
+ else
122
+ echo " ⏭️ shared/static 目录为空或不存在,跳过上传"
123
+ fi
124
+
125
+ print_time $STEP_START
126
+ echo ""
127
+
109
128
  # 总耗时
110
129
  echo "构建完成"
111
130
  print_time $TOTAL_START