Skip to content

fix: layer publish support code is http url,model remove#144

Merged
rsonghuster merged 1 commit intomasterfrom
layer-publish
Mar 9, 2026
Merged

fix: layer publish support code is http url,model remove#144
rsonghuster merged 1 commit intomasterfrom
layer-publish

Conversation

@mozhou52
Copy link
Collaborator

@mozhou52 mozhou52 commented Mar 6, 2026

Summary by CodeRabbit

  • New Features

    • Added support for deploying functions and layers using URL-based code sources.
  • Refactor

    • Consolidated internal download utilities across deployment workflows.
  • Tests

    • Enhanced test setup with additional module mocking for improved isolation.

@coderabbitai
Copy link

coderabbitai bot commented Mar 6, 2026

Warning

Rate limit exceeded

@mozhou52 has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 21 minutes and 42 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a6dd27b9-e0b0-46a4-b616-247b8448891c

📥 Commits

Reviewing files that changed from the base of the PR and between ba4ca27 and 1a9529b.

📒 Files selected for processing (7)
  • __tests__/ut/commands/invoke_test.ts
  • __tests__/ut/utils/utils_test.ts
  • package.json
  • src/subCommands/deploy/impl/function.ts
  • src/subCommands/layer/index.ts
  • src/subCommands/model/model.ts
  • src/utils/index.ts
📝 Walkthrough

Walkthrough

This PR centralizes URL download functionality by extracting it into a shared utility function, integrates the utility into layer and function deployment workflows, adds test mocks for the downloads module, and implements cleanup logic for temporary downloaded files.

Changes

Cohort / File(s) Summary
Test Mocks for Downloads Module
__tests__/ut/commands/invoke_test.ts, __tests__/ut/utils/utils_test.ts
Adds Jest mocks for the @serverless-devs/downloads module in test setup to prevent import errors, with no changes to runtime behavior.
Shared Download Utility
src/utils/index.ts
Adds new _downloadFromUrl() function to download files from URLs into temporary directories, with directory creation, file naming, and error handling with cleanup. Renames parameter in checkFcDir() from path to inputPath.
Function Deployer Refactoring
src/subCommands/deploy/impl/function.ts
Replaces private _downloadFromUrl() method with import from shared utils, updates temp file variable naming (downloadedTempDirdownloadedTempFile), and adjusts cleanup logic to use imported downloader.
Layer Deployment Enhancement
src/subCommands/layer/index.ts
Adds URL-based codeUri support using imported _downloadFromUrl(), extends safe_publish_layer() signature with downloadedTempFile parameter, implements cleanup for temporary downloaded files in two code paths, and manages temp file lifecycle in publish workflow.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 A downloads dance, refactored with care,
Layers now leap where URLs dare,
Shared utils rise, the code blooms bright,
One function serves all, what a delight! ✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title addresses the main change—adding HTTP URL support for layer publish—but is grammatically unclear and somewhat vague about what 'model remove' means.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch layer-publish

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/subCommands/model/model.ts (1)

120-126: ⚠️ Potential issue | 🟡 Minor

Inconsistent null guard for nasMountPoints.

Line 120 adds a guard nasMountPoints && before accessing nasMountPoints[0], but line 122 still accesses nasMountPoints[0] directly. If nasMountPoints is undefined or null, line 122 will throw a TypeError.

Consider applying the same guard consistently:

🛡️ Proposed fix
-    if (storage === 'nas' && nasMountPoints[0] && nasPath?.trim() === '/') {
+    if (storage === 'nas' && nasMountPoints && nasMountPoints[0] && nasPath?.trim() === '/') {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/subCommands/model/model.ts` around lines 120 - 126, The null-guard for
nasMountPoints is inconsistent: you compute nasPath using nasMountPoints &&
nasMountPoints[0] but then access nasMountPoints[0] again in the if condition
which can throw when nasMountPoints is null/undefined; update the check so both
uses are guarded (e.g. use nasMountPoints?.[0] or a local const like const
firstNas = nasMountPoints?.[0] and then check storage === 'nas' && firstNas &&
nasPath?.trim() === '/') to avoid a TypeError when nasMountPoints is missing.
🧹 Nitpick comments (1)
src/subCommands/layer/index.ts (1)

58-65: Consider extracting duplicated cleanup logic.

The cleanup code for downloadedTempFile is duplicated at lines 58-65 and 90-97. Consider extracting it to a small helper function for maintainability.

♻️ Optional refactor to reduce duplication
+function cleanupTempFile(filePath: string): void {
+  if (!filePath) return;
+  try {
+    logger.debug(`Removing temp download file: ${filePath}`);
+    fs.rmSync(filePath, { recursive: true, force: true });
+  } catch (ex) {
+    logger.debug(`Unable to remove temp download file: ${filePath}`);
+  }
+}
+
 export default class Layer {
   static async safe_publish_layer(
     // ...
       if (latestLayer.codeChecksum === crc64Value) {
         // ...
-        if (downloadedTempFile) {
-          try {
-            logger.debug(`Removing temp download dir: ${downloadedTempFile}`);
-            fs.rmSync(downloadedTempFile, { recursive: true, force: true });
-          } catch (ex) {
-            logger.debug(`Unable to remove temp download dir: ${downloadedTempFile}`);
-          }
-        }
+        cleanupTempFile(downloadedTempFile);
         return latestLayer;
       }
     }
     // ... later ...
-    if (downloadedTempFile) {
-      try {
-        logger.debug(`Removing temp download file: ${downloadedTempFile}`);
-        fs.rmSync(downloadedTempFile, { recursive: true, force: true });
-      } catch (ex) {
-        logger.debug(`Unable to remove temp download file: ${downloadedTempFile}`);
-      }
-    }
+    cleanupTempFile(downloadedTempFile);

Also applies to: 89-97

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/subCommands/layer/index.ts` around lines 58 - 65, Extract the duplicated
cleanup block that removes downloadedTempFile into a small helper function
(e.g., removeTempDownload or cleanupTempDir) and call it from both locations;
the helper should accept the path (downloadedTempFile), call fs.rmSync(path, {
recursive: true, force: true }) inside a try/catch, and use logger.debug to log
both the start ("Removing temp download dir: ...") and the failure ("Unable to
remove temp download dir: ...") messages so the two duplicated blocks around
downloadedTempFile are replaced by a single reusable function.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/utils/index.ts`:
- Around line 329-344: The returned downloadPath points to a different filename
than the one passed to downloads(): you compute both filename
(path.basename(urlPath)) and parsedPathName (path.parse(urlPath).name) but use
filename for downloadPath and parsedPathName for downloads(); fix by choosing a
single canonical name and using it in both places — e.g., compute a single
variable (use either basename or parsedName+path.extname(urlPath)) and replace
both uses of filename and parsedPathName so downloadPath and the downloads(...)
filename option reference the exact same string (ensure tempDir and URL parsing
logic remain unchanged).
- Around line 345-357: The cleanup currently removes the entire shared tempDir
(tempDir) which can disrupt concurrent operations; instead, in the catch block
only remove the specific downloaded file/temporary artifact (e.g., the variable
that holds the downloaded file path such as tempFilePath or
downloadedArchivePath) by checking fs.existsSync(tempFilePath) and calling
fs.unlinkSync(tempFilePath) (or async equivalent), leaving tempDir intact;
update the logger.debug messages to reference the specific file and preserve the
existing cleanupError logging behavior (logger.debug with cleanupError.message)
and then rethrow the original error as before.

---

Outside diff comments:
In `@src/subCommands/model/model.ts`:
- Around line 120-126: The null-guard for nasMountPoints is inconsistent: you
compute nasPath using nasMountPoints && nasMountPoints[0] but then access
nasMountPoints[0] again in the if condition which can throw when nasMountPoints
is null/undefined; update the check so both uses are guarded (e.g. use
nasMountPoints?.[0] or a local const like const firstNas = nasMountPoints?.[0]
and then check storage === 'nas' && firstNas && nasPath?.trim() === '/') to
avoid a TypeError when nasMountPoints is missing.

---

Nitpick comments:
In `@src/subCommands/layer/index.ts`:
- Around line 58-65: Extract the duplicated cleanup block that removes
downloadedTempFile into a small helper function (e.g., removeTempDownload or
cleanupTempDir) and call it from both locations; the helper should accept the
path (downloadedTempFile), call fs.rmSync(path, { recursive: true, force: true
}) inside a try/catch, and use logger.debug to log both the start ("Removing
temp download dir: ...") and the failure ("Unable to remove temp download dir:
...") messages so the two duplicated blocks around downloadedTempFile are
replaced by a single reusable function.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b7df18c9-6a61-45b6-94eb-f2a0a0d25200

📥 Commits

Reviewing files that changed from the base of the PR and between 35a061a and 8c14b69.

📒 Files selected for processing (7)
  • __tests__/ut/commands/invoke_test.ts
  • __tests__/ut/utils/utils_test.ts
  • package.json
  • src/subCommands/deploy/impl/function.ts
  • src/subCommands/layer/index.ts
  • src/subCommands/model/model.ts
  • src/utils/index.ts

Comment on lines +329 to +344
// 从URL获取文件名
const urlPath = new URL(url).pathname;
const parsedPathName = path.parse(urlPath).name;
const filename = path.basename(urlPath) || `downloaded_code_${Date.now()}`;
downloadPath = path.join(tempDir, filename);

await downloads(url, {
dest: tempDir,
filename: parsedPathName,
extract: false,
});

logger.debug(`Downloaded file to: ${downloadPath}`);

// 返回下载文件路径,由主流程决定是否需要压缩
return downloadPath;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Critical: Mismatched filename causes returned path to reference a non-existent file.

The function computes two different filenames:

  • filename = path.basename(urlPath) → e.g., "code.zip"
  • parsedPathName = path.parse(urlPath).name → e.g., "code" (without extension)

The downloadPath is computed using filename, but downloads() saves the file using parsedPathName. This causes the returned path to point to a file that doesn't exist.

For example, with URL https://example.com/code.zip:

  • Returns: /tmp/fc_code_download/code.zip
  • Actual file: /tmp/fc_code_download/code
🐛 Proposed fix
     // 从URL获取文件名
     const urlPath = new URL(url).pathname;
-    const parsedPathName = path.parse(urlPath).name;
     const filename = path.basename(urlPath) || `downloaded_code_${Date.now()}`;
     downloadPath = path.join(tempDir, filename);
 
     await downloads(url, {
       dest: tempDir,
-      filename: parsedPathName,
+      filename: filename,
       extract: false,
     });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// 从URL获取文件名
const urlPath = new URL(url).pathname;
const parsedPathName = path.parse(urlPath).name;
const filename = path.basename(urlPath) || `downloaded_code_${Date.now()}`;
downloadPath = path.join(tempDir, filename);
await downloads(url, {
dest: tempDir,
filename: parsedPathName,
extract: false,
});
logger.debug(`Downloaded file to: ${downloadPath}`);
// 返回下载文件路径,由主流程决定是否需要压缩
return downloadPath;
// 从URL获取文件名
const urlPath = new URL(url).pathname;
const filename = path.basename(urlPath) || `downloaded_code_${Date.now()}`;
downloadPath = path.join(tempDir, filename);
await downloads(url, {
dest: tempDir,
filename: filename,
extract: false,
});
logger.debug(`Downloaded file to: ${downloadPath}`);
// 返回下载文件路径,由主流程决定是否需要压缩
return downloadPath;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/utils/index.ts` around lines 329 - 344, The returned downloadPath points
to a different filename than the one passed to downloads(): you compute both
filename (path.basename(urlPath)) and parsedPathName (path.parse(urlPath).name)
but use filename for downloadPath and parsedPathName for downloads(); fix by
choosing a single canonical name and using it in both places — e.g., compute a
single variable (use either basename or parsedName+path.extname(urlPath)) and
replace both uses of filename and parsedPathName so downloadPath and the
downloads(...) filename option reference the exact same string (ensure tempDir
and URL parsing logic remain unchanged).

Comment on lines +345 to +357
} catch (error) {
// 如果下载失败,清理临时目录
try {
if (fs.existsSync(tempDir)) {
fs.rmSync(tempDir, { recursive: true, force: true });
logger.debug(`Cleaned up temporary directory after error: ${tempDir}`);
}
} catch (cleanupError) {
logger.debug(`Failed to clean up temporary directory: ${cleanupError.message}`);
}

throw new Error(`Failed to download code from URL: ${error.message}`);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Error cleanup removes entire temp directory, potentially affecting concurrent operations.

On error, the function removes the entire fc_code_download directory. Since this is a shared temp directory path (os.tmpdir()/fc_code_download), concurrent downloads from different processes or calls could have their files deleted unexpectedly.

Consider cleaning up only the specific downloaded file rather than the entire directory:

🛡️ Proposed fix
   } catch (error) {
-    // 如果下载失败,清理临时目录
+    // 如果下载失败,清理下载的文件
     try {
-      if (fs.existsSync(tempDir)) {
-        fs.rmSync(tempDir, { recursive: true, force: true });
-        logger.debug(`Cleaned up temporary directory after error: ${tempDir}`);
+      if (downloadPath && fs.existsSync(downloadPath)) {
+        fs.rmSync(downloadPath, { force: true });
+        logger.debug(`Cleaned up downloaded file after error: ${downloadPath}`);
       }
     } catch (cleanupError) {
-      logger.debug(`Failed to clean up temporary directory: ${cleanupError.message}`);
+      logger.debug(`Failed to clean up downloaded file: ${cleanupError.message}`);
     }
 
     throw new Error(`Failed to download code from URL: ${error.message}`);
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/utils/index.ts` around lines 345 - 357, The cleanup currently removes the
entire shared tempDir (tempDir) which can disrupt concurrent operations;
instead, in the catch block only remove the specific downloaded file/temporary
artifact (e.g., the variable that holds the downloaded file path such as
tempFilePath or downloadedArchivePath) by checking fs.existsSync(tempFilePath)
and calling fs.unlinkSync(tempFilePath) (or async equivalent), leaving tempDir
intact; update the logger.debug messages to reference the specific file and
preserve the existing cleanupError logging behavior (logger.debug with
cleanupError.message) and then rethrow the original error as before.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (3)
src/subCommands/layer/index.ts (1)

58-66: Log message inconsistency: says "dir" but should say "file".

Lines 90-97 correctly use "file" in the log messages, but this block uses "dir". Align both cleanup blocks for consistency.

📝 Suggested fix
         if (downloadedTempFile) {
           try {
-            logger.debug(`Removing temp download dir: ${downloadedTempFile}`);
+            logger.debug(`Removing temp download file: ${downloadedTempFile}`);
             fs.rmSync(downloadedTempFile, { recursive: true, force: true });
           } catch (ex) {
-            logger.debug(`Unable to remove temp download dir: ${downloadedTempFile}`);
+            logger.debug(`Unable to remove temp download file: ${downloadedTempFile}`);
           }
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/subCommands/layer/index.ts` around lines 58 - 66, The cleanup logs for
the temporary download use "dir" incorrectly; update the logger.debug messages
in the block that checks downloadedTempFile (around the fs.rmSync call) to say
"file" instead of "dir" so they match the other cleanup block and the actual
variable downloadedTempFile; adjust both the success message (`Removing temp
download ...`) and the catch message (`Unable to remove temp download ...`) in
src/subCommands/layer/index.ts where downloadedTempFile, fs.rmSync and
latestLayer are used.
src/subCommands/deploy/impl/function.ts (2)

328-335: Log message says "dir" but cleanup targets a file.

The variable downloadedTempFile holds a file path (per the _downloadFromUrl utility), but log messages reference "temp download dir". This is misleading during debugging.

📝 Suggested fix
         if (downloadedTempFile) {
           try {
-            logger.debug(`Removing temp download dir: ${downloadedTempFile}`);
+            logger.debug(`Removing temp download file: ${downloadedTempFile}`);
             fs.rmSync(downloadedTempFile, { recursive: true, force: true });
           } catch (ex) {
-            logger.debug(`Unable to remove temp download dir: ${downloadedTempFile}`);
+            logger.debug(`Unable to remove temp download file: ${downloadedTempFile}`);
           }
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/subCommands/deploy/impl/function.ts` around lines 328 - 335, The cleanup
log incorrectly calls the path in downloadedTempFile a "dir" even though
_downloadFromUrl returns a file path; update the messages in the cleanup block
that references downloadedTempFile to say "file" (e.g., "Removing temp download
file" and "Unable to remove temp download file") so logs accurately reflect
what's being removed; locate the cleanup code that checks downloadedTempFile in
function.ts and change both logger.debug strings accordingly.

353-360: Same log message inconsistency as the early return path.

Apply the same fix here: change "dir" to "file" in the log messages for consistency.

📝 Suggested fix
     if (downloadedTempFile) {
       try {
-        logger.debug(`Removing temp download dir: ${downloadedTempFile}`);
+        logger.debug(`Removing temp download file: ${downloadedTempFile}`);
         fs.rmSync(downloadedTempFile, { recursive: true, force: true });
       } catch (ex) {
-        logger.debug(`Unable to remove temp download dir: ${downloadedTempFile}`);
+        logger.debug(`Unable to remove temp download file: ${downloadedTempFile}`);
       }
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/subCommands/deploy/impl/function.ts` around lines 353 - 360, The cleanup
block that removes downloadedTempFile uses logger.debug messages that say "dir"
but should say "file" for consistency; update the two logger.debug calls in the
block that references downloadedTempFile (the try/catch around fs.rmSync) to use
"file" instead of "dir" so both the success and error logs read "Removing temp
download file: ${downloadedTempFile}" and "Unable to remove temp download file:
${downloadedTempFile}" respectively.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/subCommands/deploy/impl/function.ts`:
- Around line 328-335: The cleanup log incorrectly calls the path in
downloadedTempFile a "dir" even though _downloadFromUrl returns a file path;
update the messages in the cleanup block that references downloadedTempFile to
say "file" (e.g., "Removing temp download file" and "Unable to remove temp
download file") so logs accurately reflect what's being removed; locate the
cleanup code that checks downloadedTempFile in function.ts and change both
logger.debug strings accordingly.
- Around line 353-360: The cleanup block that removes downloadedTempFile uses
logger.debug messages that say "dir" but should say "file" for consistency;
update the two logger.debug calls in the block that references
downloadedTempFile (the try/catch around fs.rmSync) to use "file" instead of
"dir" so both the success and error logs read "Removing temp download file:
${downloadedTempFile}" and "Unable to remove temp download file:
${downloadedTempFile}" respectively.

In `@src/subCommands/layer/index.ts`:
- Around line 58-66: The cleanup logs for the temporary download use "dir"
incorrectly; update the logger.debug messages in the block that checks
downloadedTempFile (around the fs.rmSync call) to say "file" instead of "dir" so
they match the other cleanup block and the actual variable downloadedTempFile;
adjust both the success message (`Removing temp download ...`) and the catch
message (`Unable to remove temp download ...`) in src/subCommands/layer/index.ts
where downloadedTempFile, fs.rmSync and latestLayer are used.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0877b626-2df7-4de8-b1dd-f58a3e5aa83c

📥 Commits

Reviewing files that changed from the base of the PR and between 8c14b69 and ba4ca27.

📒 Files selected for processing (5)
  • __tests__/ut/commands/invoke_test.ts
  • __tests__/ut/utils/utils_test.ts
  • src/subCommands/deploy/impl/function.ts
  • src/subCommands/layer/index.ts
  • src/utils/index.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/utils/index.ts
  • tests/ut/utils/utils_test.ts
  • tests/ut/commands/invoke_test.ts

@mozhou52 mozhou52 requested a review from rsonghuster March 6, 2026 08:47
@rsonghuster rsonghuster merged commit 63b39be into master Mar 9, 2026
6 of 9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants