Skip to content

Conversation

@andrewjschuang
Copy link
Contributor

@andrewjschuang andrewjschuang commented Mar 14, 2025

Merging helper functions and formatting components

Summary by CodeRabbit

  • Documentation
    • Introduced a comprehensive README that explains key functionalities and use cases.
  • New Features
    • Launched numerous actions for data transformations and conversions, including text (HTML/Markdown, case changes, whitespace trimming, URL encode/decode, text replacement), dates (addition/subtraction, formatting, comparison, timezone conversion), and numbers (currency conversion/formatting, random integer generation).
    • Added utilities for data extraction (emails, phone numbers, URLs, JSON/XML conversion, CSV processing, array comparisons) and workflow integrations (RSS feed retrieval, file downloads, scheduling tasks, triggering workflows).
    • Enhanced date handling with input and output format specifications.
    • Updated version of the pipedream_utils package and added new dependencies for enhanced functionality.

@vercel
Copy link

vercel bot commented Mar 14, 2025

The latest updates on your projects. Learn more about Vercel for Git ↗︎

3 Skipped Deployments
Name Status Preview Comments Updated (UTC)
docs-v2 ⬜️ Ignored (Inspect) Visit Preview Mar 14, 2025 3:30pm
pipedream-docs ⬜️ Ignored (Inspect) Mar 14, 2025 3:30pm
pipedream-docs-redirect-do-not-edit ⬜️ Ignored (Inspect) Mar 14, 2025 3:30pm

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 14, 2025

Walkthrough

The pull request introduces an extensive set of new modules and actions to the Pipedream Utils app. These include utilities for date and time manipulation, text and JSON conversions, currency handling, RSS feed retrieval, file operations, email and S3 integrations, random generation, and workflow triggering and scheduling. Additionally, new common utilities for handling dates, numbers, and text operations have been added. The package configuration was updated with new dependencies and version bumps, and an obsolete app file was removed.

Changes

File(s) Change Summary
components/pipedream_utils/README.md Added a README with an overview and example use cases for Pipedream Utils.
components/pipedream_utils/actions/add-subtract-time/add-subtract-time.mjs, compare-dates.mjs, date-time-format.mjs, format-iso8601-datetime.mjs, format-number.mjs, get-current-time-in-specific-timezone.mjs, get-iso-string-n-days-ago.mjs, get-time-in-specific-timezone.mjs New modules for time arithmetic, date comparison, and formatting including timezone conversions.
components/pipedream_utils/actions/base64-decode-string/base64-decode-string.mjs New module for Base64 decoding functionality.
components/pipedream_utils/actions/compare-arrays/compare-arrays.mjs New module for comparing arrays using difference, union, intersection, and symmetric difference operations.
components/pipedream_utils/actions/convert-html-to-markdown/convert-html-to-markdown.mjs, convert-html-to-slack-mrkdwn/convert-html-to-slack-mrkdwn.mjs, convert-html-to-text/convert-html-to-text.mjs, html-to-markdown/html-to-markdown.mjs New modules for converting HTML to Markdown, Slack mrkdwn, and plain text.
components/pipedream_utils/actions/convert-currency/convert-currency.mjs, format-currency.mjs, get-coutry-name-by-code-iso.mjs New modules for currency conversion, formatting, and lookup based on ISO codes.
components/pipedream_utils/actions/convert-json-to-string/convert-json-to-string.mjs, convert-object-to-json-string/convert-object-to-json-string.mjs, parse-json/parse-json.mjs, prettify-json/prettify-json.mjs New modules for JSON stringification, object conversion, parsing, and pretty-printing.
components/pipedream_utils/actions/csv-file-to-objects/csv-file-to-objects.mjs New module to convert CSV files into arrays of objects with configurable parsing options.
components/pipedream_utils/actions/send-email-with-nodemailer/send-email-with-nodemailer.mjs New action to send emails via Nodemailer using SMTP configurations.
components/pipedream_utils/actions/send-to-s3/send-to-s3.mjs New module for sending data to Amazon S3 with bucket and prefix support.
components/pipedream_utils/actions/export-variables/export-variables.mjs New action for exporting validated workflow variables with JSON parsing and error handling.
components/pipedream_utils/actions/extract-by-regular-expression/extract-by-regular-expression.mjs, extract-email-address/extract-email-address.mjs, extract-number/extract-number.mjs, extract-phone-number/extract-phone-number.mjs, extract-url/extract-url.mjs, replace-text/replace-text.mjs, url-decode/url-decode.mjs, url-encode/url-encode.mjs New modules for extracting text elements (regex-based, email, number, phone, URL) and handling text replacements and URL encode/decode.
components/pipedream_utils/actions/retrieve-all-rss-stories/retrieve-all-rss-stories.mjs, retrieve-new-rss-stories/retrieve-new-rss-stories.mjs New actions to retrieve stories from RSS feeds, including checkpointing for new items.
components/pipedream_utils/actions/random-integer/random-integer.mjs, random-item-from-list/random-item-from-list.mjs New modules for generating random integers and selecting random list items.
components/pipedream_utils/actions/trim-whitespace/trim-whitespace.mjs, set-default-value/set-default-value.mjs, split-text/split-text.mjs, transform-case/transform-case.mjs New modules for text manipulation including trimming, setting default values, splitting, and case transformation.
components/pipedream_utils/actions/trigger-workflow/trigger-workflow.mjs, schedule-task-in-future/schedule-task-in-future.mjs New modules for triggering workflows and scheduling tasks in the future via an HTTP request.
components/pipedream_utils/common/date-time/commonDateTime.mjs, dateFormats.mjs, dateTimeUnits.mjs New common utilities for date parsing, formatting, and unit definitions.
components/pipedream_utils/common/numbers/currencies.mjs, currencyFormats.mjs, formatNumber.mjs, numberFormattingOptions.mjs New common utilities for currency data, number formatting functions, and formatting options.
components/pipedream_utils/common/text/buildRegExp.mjs, caseOptions.mjs, commonExtractText.mjs, splitTextOptions.mjs New common utilities for building regular expressions, text extraction, case transformation, and splitting options.
components/pipedream_utils/package.json Updated version from 0.0.1 to 0.0.2 and added multiple new dependencies.
components/pipedream_utils/pipedream_utils.app.mjs Expanded property definitions with new date format options (inputDate, inputFormat, outputFormat).
components/pipedream_utils/pipedream_utils.mjs Removed obsolete Pipedream app file.

Suggested labels

ai-assisted

Suggested reviewers

  • jcortes
  • GTFalcao

Poem

I'm a little rabbit, hopping along,
Celebrating new modules in a joyful song.
From dates to texts, each action is clear,
Coding with precision, bringing function near.
With carrots of code and a whimsical beat,
I toast these changes — oh, so sweet!
🥕🐇 Happy coding in every byte!

Tip

⚡🧪 Multi-step agentic review comment chat (experimental)
  • We're introducing multi-step agentic chat in review comments. This experimental feature enhances review discussions with the CodeRabbit agentic chat by enabling advanced interactions, including the ability to create pull requests directly from comments.
    - To enable this feature, set early_access to true under in the settings.

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@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: 49

🧹 Nitpick comments (78)
components/pipedream_utils/common/date-time/dateTimeUnits.mjs (1)

7-7: Consider accounting for leap years in the year calculation

The current implementation uses a simplified 365-day year. For more precise calculations, consider using 365.25 days to account for leap years, depending on your use case requirements.

-const year = 365 * day;
+const year = 365.25 * day;
components/pipedream_utils/actions/url-encode/url-encode.mjs (1)

4-4: Clarify description for URL encoding purpose

There's a slight inconsistency between the component description and the input description. The component description says "Encode a string as a URL" while the input description mentions "A valid URL as a string to be encoded".

-  description: "Encode a string as a URL",
+  description: "URL encode a string for use in a URL",
-      description: "A valid URL as a string to be encoded.",
+      description: "String to be URL encoded (e.g., for query parameters, path segments).",

Also applies to: 12-13

components/pipedream_utils/actions/get-current-time-in-specific-timezone/get-current-time-in-specific-timezone.mjs (1)

18-20: Consider adding a summary export for consistency

For consistency with other actions like url-encode.mjs, consider adding a summary export to indicate successful execution.

  async run() {
-    return moment.tz(new Date(), this.timezone).format();
+    const result = moment.tz(new Date(), this.timezone).format();
+    $.export("$summary", `Current time in ${this.timezone}: ${result}`);
+    return result;
  },
components/pipedream_utils/actions/convert-html-to-slack-mrkdwn/convert-html-to-slack-mrkdwn.mjs (2)

8-8: Incomplete description sentence

The description ends abruptly with "using" but doesn't specify what library/tool it's using.

- description: "Converts an HTML string to the Slack mrkdwn format using",
+ description: "Converts an HTML string to the Slack mrkdwn format using slackify-html",

18-20: Add summary export for consistent user experience

Most Pipedream actions export a summary to provide feedback to users. Consider adding one to maintain consistency with other components.

async run() {
+ const $ = this;
+ const result = slackifyHtml(this.html);
+ $.export("$summary", "Successfully converted HTML to Slack mrkdwn format");
- return slackifyHtml(this.html);
+ return result;
},
components/pipedream_utils/actions/date-time-format/date-time-format.mjs (2)

27-31: Add a comment explaining the dual formatting approach

The code uses both a custom mapping and the Sugar library for formatting. A comment would help maintainers understand why both approaches are used.

try {
  const response = DATE_FORMAT_PARSE_MAP.get(outputFormat);
+ // First check if we have a predefined format mapping, otherwise use Sugar's formatting
  const output = response
    ? response.outputFn(dateObj)
    : sugar.Date.format(dateObj, outputFormat);

35-37: Improve error message specificity

The current error message is generic. Consider including the original error message to help users troubleshoot more effectively.

catch (err) {
- throw new ConfigurationError("**Parse error** - check your input and if the selected format is correct.");
+ throw new ConfigurationError(`**Parse error** - check your input and if the selected format is correct. Details: ${err.message}`);
}
components/pipedream_utils/actions/format-iso8601-datetime/format-iso8601-datetime.mjs (1)

2-2: Consider using a more modern date library

Moment.js is now considered legacy by its maintainers. Consider using a more modern alternative like Day.js, date-fns, or Luxon for new code.

-import moment from "moment";
+import dayjs from "dayjs";
+import customParseFormat from "dayjs/plugin/customParseFormat.js";
+import utc from "dayjs/plugin/utc.js";
+
+// Extend Day.js with plugins
+dayjs.extend(customParseFormat);
+dayjs.extend(utc);

Then update the run method accordingly:

async run() {
+  // Validate that the provided string is a valid date
+  if (!dayjs(this.iso8601).isValid()) {
+    throw new Error(`Invalid ISO8601 date/time string: "${this.iso8601}"`);
+  }
+
  return {
-    date_time: moment(this.iso8601).format("MM/DD/YYYY h:mm:ss a"),
-    date: moment(this.iso8601).format("MM/DD/YYYY"),
-    time: moment(this.iso8601).format("h:mm:ss a"),
+    date_time: dayjs(this.iso8601).format("MM/DD/YYYY h:mm:ss a"),
+    date: dayjs(this.iso8601).format("MM/DD/YYYY"),
+    time: dayjs(this.iso8601).format("h:mm:ss a"),
  };
},
components/pipedream_utils/actions/random-integer/random-integer.mjs (2)

24-26: Make run method async for consistency

All other action components use async run methods. For consistency, make this one async as well.

-run() {
+async run() {
  return Math.floor(Math.random() * (this.max - this.min + 1) + this.min);
},

24-26: Improve random integer generation

The current implementation works correctly but could be enhanced with additional error handling and a clearer return format.

-run() {
+async run() {
+  // Handle the case where min equals max
+  if (this.min === this.max) {
+    return {
+      value: this.min,
+      min: this.min,
+      max: this.max
+    };
+  }
+  
+  const randomValue = Math.floor(Math.random() * (this.max - this.min + 1) + this.min);
+  
+  return {
+    value: randomValue,
+    min: this.min,
+    max: this.max
+  };
-  return Math.floor(Math.random() * (this.max - this.min + 1) + this.min);
},
components/pipedream_utils/actions/get-iso-string-n-days-ago/get-iso-string-n-days-ago.mjs (2)

18-21: Consider input validation and return value

The component should validate that this.days is a valid number before using it with moment.js, and also consider returning the date value for better component usability.

async run({ $ }) {
+  const days = parseInt(this.days);
+  if (isNaN(days)) {
+    throw new Error("Days must be a valid number");
+  }
-  $.export("date", moment().subtract(this.days, "days")
+  const date = moment().subtract(days, "days")
    .toISOString());
+  $.export("date", date);
+  $.export("$summary", `Generated ISO string for ${days} days ago`);
+  return { date };
},

2-2: Consider using dayjs instead of moment

Moment.js is now considered legacy and its creators recommend using alternatives like dayjs for new projects.

-import moment from "moment";
+import dayjs from "dayjs";

And update the implementation accordingly:

-$.export("date", moment().subtract(this.days, "days")
-  .toISOString());
+$.export("date", dayjs().subtract(this.days, "days")
+  .toISOString());
components/pipedream_utils/common/text/splitTextOptions.mjs (2)

1-1: Document the significance of the INDEX_ALL_SEGMENTS value

The value 999 seems arbitrary. Adding a comment would help explain why this specific value was chosen and how it's used.

+// A large number to represent "all segments" in text splitting operations
export const INDEX_ALL_SEGMENTS = 999;

4-5: Track workaround for issue #5429

The comment indicates a workaround for issue #5429. Consider adding a TODO to revisit this once the issue is fixed.

  {
    label: "First",
-    value: INDEX_ALL_SEGMENTS * -1, // value should be 0, but that is not accepted - see issue #5429
+    value: INDEX_ALL_SEGMENTS * -1, // TODO: Change to 0 when issue #5429 is fixed
  },
components/pipedream_utils/common/numbers/formatNumber.mjs (3)

1-1: Add function documentation and type validation

This utility function would benefit from clear documentation about its parameters and expected behavior, as well as validation for input types.

+/**
+ * Formats a number with customizable grouping and decimal separators
+ * @param {string} integer - The integer part of the number
+ * @param {string} [decimal] - The decimal part of the number (optional)
+ * @param {string} [groupChar=","] - Character used to separate groups of thousands
+ * @param {string} [decimalChar="."] - Character used as decimal separator
+ * @returns {string} The formatted number as a string
+ */
export default function (integer, decimal, groupChar = ",", decimalChar = ".") {
+  // Validate inputs
+  if (typeof integer !== 'string') {
+    throw new TypeError('Integer part must be a string');
+  }
+  if (decimal !== undefined && typeof decimal !== 'string') {
+    throw new TypeError('Decimal part must be a string or undefined');
+  }

  const result = [];

8-10: Simplify conditional logic and clarify reverse().pop() operation

The else clause is unnecessary since there's no early return in the if block. Also, the reverse().pop() operation could use a clarifying comment.

  if (groupChar) {
    for (let i = integer.length; i > 0; i -= 3) {
      result.push(groupChar, integer.slice(Math.max(0, i - 3), i));
    }
+    // Remove the leading separator after reversing the array
    result.reverse().pop();
  }
-  else
+  else {
    result.push(integer);
+  }

3-7: Consider a more readable approach to grouping digits

The current implementation works but is somewhat complex. A more readable approach using regular expressions could be considered.

-  if (groupChar) {
-    for (let i = integer.length; i > 0; i -= 3) {
-      result.push(groupChar, integer.slice(Math.max(0, i - 3), i));
-    }
-    result.reverse().pop();
+  if (groupChar && integer.length > 3) {
+    // Add grouping separators every 3 digits from the right
+    result.push(integer.replace(/\B(?=(\d{3})+(?!\d))/g, groupChar));
   }
components/pipedream_utils/actions/convert-html-to-text/convert-html-to-text.mjs (3)

6-6: Align component key with naming pattern

The component key doesn't follow the same pattern as other components in this PR. Consider using "pipedream_utils-convert-html-to-text" for consistency.

-  key: "formatting-convert-html-to-text",
+  key: "pipedream_utils-convert-html-to-text",

17-21: Add error handling for the HTML conversion

The component should handle potential errors from the html-to-text library to provide a better user experience.

  async run({ $ }) {
    const { input } = this;
-    const result = convert(input);
-    $.export("$summary", "Successfully converted to text");
-    return result;
+    try {
+      const result = convert(input);
+      $.export("$summary", "Successfully converted to text");
+      return result;
+    } catch (error) {
+      $.export("$summary", `Error converting HTML to text: ${error.message}`);
+      throw error;
+    }
  },

1-2: Consider adding options for HTML to text conversion

The html-to-text library supports various options to customize the conversion process, which could be exposed as props.

You could extend this component by adding optional configuration props like:

options: {
  label: "Conversion Options",
  description: "Advanced options for HTML to text conversion",
  type: "object",
  optional: true,
}

And then pass them to the convert function:

const result = convert(input, this.options || {});

This would allow users to customize settings like wordwrap, link formatting, etc.

components/pipedream_utils/actions/format-number/format-number.mjs (1)

1-56: Comprehensive number formatting component with good error handling

The component is well-structured with clear descriptions and proper error handling for multiple decimal marks. The formatting logic separates the integer and decimal parts, supporting various formatting styles.

A few suggestions to enhance robustness:

  1. Consider adding validation to ensure the input is actually a number before processing
  2. Add a try/catch block around the formatNumber call to handle any unexpected errors
async run({ $ }) {
  const {
    inputDecimalMark, toFormat,
  } = this;
  const input = this.input.toString();
+ // Validate that input is a number
+ if (isNaN(Number(input))) {
+   throw new ConfigurationError("Input must be a valid number");
+ }
  const decimalMark = inputDecimalMark ?? ".";
  const splitInput = input.split(decimalMark);
  if (splitInput.length > 2) {
    throw new ConfigurationError(`Input has more than one decimal mark (\`${decimalMark}\`). Check if the \`Input Decimal Mark\` prop is set correctly.`);
  }
  const [
    integer,
    decimal,
  ] = splitInput;
  const [
    groupChar,
    decimalChar,
  ] = toFormat.split("");
+ try {
    const result = formatNumber(integer, decimal, groupChar, decimalChar);
    $.export("$summary", "Successfully formatted number");
    return result;
+ } catch (error) {
+   throw new Error(`Error formatting number: ${error.message}`);
+ }
},
components/pipedream_utils/actions/get-time-in-specific-timezone/get-time-in-specific-timezone.mjs (1)

8-11: Consider adding a formatting option for more flexibility

Currently, the component only returns the time in ISO format. Consider adding an optional format property to allow users to specify the desired output format.

export default {
  key: "pipedream_utils-get-time-in-specific-timezone",
  name: "Helper Functions - Get Time in Timezone",
  description: "Given an ISO 8601 timestamp, and a timezone, convert the time to the target timezone.",
- version: "0.0.1",
+ version: "0.0.2",
  type: "action",
  props: {
    pipedream_utils,
    time: {
      type: "string",
      label: "ISO 8601 Time",
      description: "An [ISO 8601 string](https://en.wikipedia.org/wiki/ISO_8601) representing the time you'd like to convert to your target timezone. If this timestamp doesn't have a timezone component, it's assumed to be in UTC.",
    },
    timezone: {
      type: "string",
      label: "Timezone",
      description: "The IANA timezone name, e.g. `America/Los_Angeles`. [See the full list here](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones).",
    },
+   format: {
+     type: "string",
+     label: "Output Format",
+     description: "Optional. The format for the output time string. See [moment.js documentation](https://momentjs.com/docs/#/displaying/format/) for format options. Defaults to ISO 8601 format.",
+     optional: true,
+   },
  },
  async run({ $ }) {
    try {
      // Validate the timezone
      if (!moment.tz.zone(this.timezone)) {
        throw new Error(`Invalid timezone: ${this.timezone}. Please provide a valid IANA timezone name.`);
      }
      
      // Convert the time
-     const convertedTime = moment.tz(this.time, this.timezone).format();
+     const time = moment.tz(this.time, this.timezone);
+     
+     // Check if the result is valid
+     if (!time.isValid()) {
+       throw new Error("Invalid date or time format. Please provide a valid ISO 8601 time string.");
+     }
+     
+     // Format the time
+     const convertedTime = this.format 
+       ? time.format(this.format) 
+       : time.format();
      
      $.export("$summary", `Successfully converted time to ${this.timezone}`);
      return convertedTime;
    } catch (error) {
      throw new Error(`Error converting time: ${error.message}`);
    }
  },
};
components/pipedream_utils/actions/schedule-task-in-future/schedule-task-in-future.mjs (2)

14-17: Consider using number type for numSeconds

The numSeconds property is defined as a string type but is used in a mathematical calculation on line 34. While JavaScript will implicitly convert strings to numbers in mathematical operations, it would be clearer to define this as a number type or explicitly convert it when used.

 numSeconds: {
-  type: "string",
+  type: "integer",
   label: "Num Seconds",
   description: "How many seconds in the future would you like to schedule the task?",
 },

33-35: Consider refactoring timestamp calculation for better readability

The timestamp calculation is concise but a bit complex to read. Breaking it into steps would improve readability and make debugging easier.

-  const ts = (new Date(+new Date() + (this.numSeconds * 1000))).toISOString();
+  const currentTimeMs = Date.now();
+  const futureTimeMs = currentTimeMs + (Number(this.numSeconds) * 1000);
+  const ts = new Date(futureTimeMs).toISOString();
   $.export("ts", ts);
components/pipedream_utils/actions/extract-email-address/extract-email-address.mjs (1)

19-21: Consider a more robust email regex pattern

The current regex pattern for extracting email addresses is relatively simple and might not catch all valid email patterns or could match some invalid ones. Email validation is notoriously complex.

  getRegExp() {
-    return /[\w.!#$%&'*+-/=?^_`{|}~]+@([\w-]+\.)+[\w-]{2,}/;
+    return /[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+/;
  },
components/pipedream_utils/actions/format-currency/format-currency.mjs (2)

43-52: Simplify decimal extraction logic

The current implementation for splitting the number into integer and decimal parts is unnecessarily complex. Consider using a more direct approach and add validation for the decimal part.

-  const [
-    integer,
-    decimal,
-  ] = input.split(input.includes(".")
-    ? "."
-    : ",");
-  if (isNaN(Number(integer))) {
+  // Parse the input using a regex that handles both dot and comma as decimal separators
+  const match = input.match(/^([^.,]+)(?:[.,]([^.,]*))?$/);
+  if (!match || isNaN(Number(match[1]))) {
     throw new ConfigurationError("**Invalid number** - please check your input.");
   }
+  const integer = match[1];
+  const decimal = match[2] || "0";

52-56: Simplify conditional logic in formatNumber call

The nested ternary operations make the code harder to read. Consider restructuring this logic for better readability.

-  const numberString = formatNumber(integer, (decimal?.length > 1
-    ? decimal
-    : (decimal ?? "0") + "0"), currencyFormat.includes(",")
-    ? ","
-    : "");
+  // Prepare the decimal part
+  let decimalPart = decimal || "0";
+  if (decimalPart.length <= 1) {
+    decimalPart += "0";
+  }
+  
+  // Determine separator
+  const separator = currencyFormat.includes(",") ? "," : "";
+  
+  const numberString = formatNumber(integer, decimalPart, separator);
components/pipedream_utils/actions/split-text/split-text.mjs (2)

42-43: Input validation missing

There's no validation to ensure input is a string before calling split(). If input is undefined or null, this will cause an error.

Add validation:

let summary = "Separator not found - returned unmodified input";
let result = input;
+ if (typeof input !== 'string') {
+   $.export("$summary", "Error: Input must be a string");
+   return input;
+ }
const arrResults = input.split(separator);

56-60: Inconsistent indentation and potential out-of-bounds access

The indentation here is inconsistent, and there's no check for out-of-bounds indices when accessing arrResults.

Fix indentation and add a boundary check:

default:
-  result =
-                  arrResults[segmentIndex < 0
-                    ? length + segmentIndex
-                    : segmentIndex];
+  const index = segmentIndex < 0 ? length + segmentIndex : segmentIndex;
+  result = (index >= 0 && index < length) ? arrResults[index] : null;
+  if (result === null) {
+    summary += `, but index ${segmentIndex} is out of bounds`;
+  }
  break;
components/pipedream_utils/common/text/commonExtractText.mjs (3)

10-12: Add error handling and input validation

The getResult method doesn't handle potential errors when getRegExp() returns an invalid regular expression or when input isn't a string.

getResult(input) {
+  if (typeof input !== 'string') {
+    return null;
+  }
+  try {
    return input.match(this.getRegExp())?.[0];
+  } catch (error) {
+    console.error("Error in regex matching:", error);
+    return null;
+  }
},

17-24: Add error handling in run method

The run method should handle potential errors from getResult and getType.

async run({ $ }) {
  const input = this.input;
+  try {
    const result = this.getResult(input);
+    const type = this.getType();
    $.export("$summary", result
-      ? `Successfully found ${this.getType()} "${result}"`
-      : `No ${this.getType()} found`);
+      ? `Successfully found ${type} "${result}"`
+      : `No ${type} found`);
    return result;
+  } catch (error) {
+    $.export("$summary", `Error: ${error.message}`);
+    return null;
+  }
},

6-16: Consider caching RegExp for performance

Creating regular expressions repeatedly can impact performance. Consider caching the RegExp object.

methods: {
+  _cachedRegExp: null,
  getRegExp() {
    throw new Error("RegExp not implemented for this action!");
  },
+  getCachedRegExp() {
+    if (!this._cachedRegExp) {
+      this._cachedRegExp = this.getRegExp();
+    }
+    return this._cachedRegExp;
+  },
  getResult(input) {
-    return input.match(this.getRegExp())?.[0];
+    return input.match(this.getCachedRegExp())?.[0];
  },
  getType() {
    throw new Error("Type not implemented for this action!");
  },
},
components/pipedream_utils/README.md (1)

1-15: Expand README with more comprehensive content

The README provides a good overview but could be enhanced with more detailed information about the available components, installation instructions, and usage examples.

Consider adding:

  1. A table of contents
  2. List of available components grouped by category (text, date, etc.)
  3. Basic usage instructions with code examples
  4. Links to more detailed documentation

Also, as noted by the static analysis tool, you could strengthen the wording in line 3 by replacing "speeding up" with "accelerating" or "enhancing".

🧰 Tools
🪛 LanguageTool

[style] ~3-~3: Consider using a different verb to strengthen your wording.
Context: ...ate code needed for routine operations, speeding up the development of intricate automation...

(SPEED_UP_ACCELERATE)

components/pipedream_utils/actions/extract-number/extract-number.mjs (2)

5-8: Add more detail to the component description

The current description mentions that it finds the first number only, but doesn't specify what qualifies as a number (e.g., whether it handles negative numbers, scientific notation, etc.).

name: "Formatting - [Text] Extract Number",
-description: "Find a number out of a text field. Finds the first number only.",
+description: "Find a number out of a text field. Finds the first number only. Supports positive and negative numbers, decimals, and scientific notation.",
key: "formatting-extract-number",

2-26: Consider returning actual number values

The component currently returns the matched number as a string. Users might expect an actual numeric value.

Add an option to convert the result to a number:

props: {
  ...commonExtractText.props,
  input: {
    label: "Input",
    description: "String from which you'd like to extract a number",
    type: "string",
  },
+ convertToNumber: {
+   label: "Convert to Number",
+   description: "Convert the result to a numeric value",
+   type: "boolean",
+   default: false,
+ },
},
methods: {
  ...commonExtractText.methods,
  getRegExp() {
    return /[0-9][0-9.,]*/;
  },
  getType() {
    return "number";
  },
+ getResult(input) {
+   const result = commonExtractText.methods.getResult.call(this, input);
+   if (result && this.convertToNumber) {
+     return Number(result);
+   }
+   return result;
+ },
},
components/pipedream_utils/common/numbers/currencies.mjs (1)

1-773: Consider optimizing currency data access

For better performance when working with currencies, consider adding utility functions to find currencies by ISO code, symbol, or name.

const CURRENCIES = [
  // ... existing currency data
];

export const CURRENCY_OPTIONS = CURRENCIES.map(({
  name, symbol, iso,
}) => ({
  label: name,
  value: `${iso} - ${symbol} - ${name}`,
}));

+ // Utility functions for finding currencies
+ export const findCurrencyByISO = (isoCode) => 
+   CURRENCIES.find(currency => currency.iso === isoCode);
+ 
+ export const getCurrencySymbol = (isoCode) => {
+   const currency = findCurrencyByISO(isoCode);
+   return currency ? currency.symbol : null;
+ };
components/pipedream_utils/actions/convert-markdown-to-html/convert-markdown-to-html.mjs (2)

18-25: Add error handling to the run method

The current implementation lacks error handling. If showdown encounters problems with malformed Markdown or if other issues arise during conversion, the component will throw an unhandled exception.

  async run({ $ }) {
    const { input } = this;
-    const converter = new showdown.Converter();
-    const dom = parseHTML("");
-    const result = converter.makeHtml(input, dom.window.document);
-    $.export("$summary", "Successfully converted to HTML");
-    return result;
+    try {
+      if (typeof input !== 'string') {
+        throw new Error('Input must be a string');
+      }
+      const converter = new showdown.Converter();
+      const dom = parseHTML("");
+      const result = converter.makeHtml(input, dom.window.document);
+      $.export("$summary", "Successfully converted to HTML");
+      return result;
+    } catch (error) {
+      $.export("$summary", `Failed to convert Markdown to HTML: ${error.message}`);
+      throw error;
+    }
  },

12-16: Consider adding input validation or examples

The input property could benefit from default examples or additional validation to ensure users provide proper Markdown. This would improve the usability of the component.

    input: {
      label: "Input",
      description: "Markdown string to be converted to HTML",
      type: "string",
+     default: "# Hello World\n\nThis is **Markdown**.",
    },
components/pipedream_utils/actions/retrieve-all-rss-stories/retrieve-all-rss-stories.mjs (1)

9-10: Consider bumping the version number

The component uses version "0.0.1" which suggests it's in a very early stage. If the component is ready for production use, consider bumping the version number to at least "0.1.0".

  description: "Retrieve all stories from one or more RSS feeds.",
-  version: "0.0.1",
+  version: "0.1.0",
  type: "action",
components/pipedream_utils/actions/send-to-s3/send-to-s3.mjs (2)

22-25: Consider adding additional S3 configuration options

The component allows only basic configuration for S3 uploads. Consider adding options for content-type, ACL, and other S3-specific settings.

    payload: {
      type: "object",
      description: "An object, either a reference to a variable from a previous step (for example, event.body), or a set of hardcoded key-value pairs.",
    },
+    contentType: {
+      type: "string",
+      label: "Content Type",
+      description: "The MIME content type of the object being uploaded",
+      optional: true,
+    },
+    acl: {
+      type: "string",
+      label: "ACL",
+      description: "The access control list for the object being uploaded",
+      optional: true,
+      options: [
+        { label: "Private", value: "private" },
+        { label: "Public Read", value: "public-read" },
+        { label: "Public Read-Write", value: "public-read-write" },
+      ],
+    },

8-9: Consider bumping the version number

The component uses version "0.0.1" which suggests it's in a very early stage. If the component is ready for production use, consider bumping the version number to at least "0.1.0".

  description: "Send data to Amazon S3 using Pipedream's destination integration. See https://docs.pipedream.com/destinations/s3/",
-  version: "0.0.1",
+  version: "0.1.0",
  type: "action",
components/pipedream_utils/common/text/caseOptions.mjs (3)

7-12: Improve the Capitalize function to handle edge cases

The current capitalization function doesn't handle empty words or strings with multiple spaces correctly. It will also throw an error if a word is an empty string (due to accessing index 0).

    outputFn(str) {
      return str
        .split(" ")
-        .map((word) => word[0].toUpperCase() + word.slice(1))
+        .map((word) => word.length ? word[0].toUpperCase() + word.slice(1) : "")
        .join(" ");
    },

4-41: Add input validation to prevent errors with non-string inputs

None of the transformation functions validate their input. This could lead to errors if the input is not a string or is null/undefined.

Consider adding a helper function for input validation, for example:

const validateInput = (str) => {
  if (typeof str !== 'string') {
    throw new Error('Input must be a string');
  }
  return str;
};

Then update each outputFn to use this validation:

    outputFn(str) {
+      str = validateInput(str);
      return str
        .split(" ")
        .map((word) => word.length ? word[0].toUpperCase() + word.slice(1) : "")
        .join(" ");
    },

Apply similar changes to all other transformation functions.


43-48: Add a comment explaining the purpose of CASE_OPTIONS_PROP

The purpose of CASE_OPTIONS_PROP is not immediately clear. It would be helpful to add a comment explaining its usage.

+// Extract only the label and value properties from CASE_OPTIONS
+// This is used for generating UI option lists without the implementation functions
export const CASE_OPTIONS_PROP = CASE_OPTIONS.map(({
  label, value,
}) => ({
  label,
  value,
}));
components/pipedream_utils/actions/compare-arrays/compare-arrays.mjs (3)

6-6: Fix typo in description

There is a typo in the description: "symetric" should be "symmetric" to match the option in line 31.

- description: "Get the difference, intersection, union, or symetric difference of two arrays/sets.",
+ description: "Get the difference, intersection, union, or symmetric difference of two arrays/sets.",

36-40: Consider preserving the original Set

The getDifference method creates a new array from set1 and then filters it, which modifies neither original Set. However, the implementation is inconsistent with other methods like getUnion and getSymmetricDifference which do modify the original Set.

  getDifference(set1, set2) {
-   return new Set([
-     ...set1,
-   ].filter((x) => !set2.has(x)));
+   const result = new Set();
+   for (const elem of set1) {
+     if (!set2.has(elem)) {
+       result.add(elem);
+     }
+   }
+   return result;
  },

41-45: Consider preserving the original Set

Similar to the previous comment, the getIntersection method creates a new array, filters it, and then returns a new Set, which doesn't modify either original Set.

  getIntersection(set1, set2) {
-   return new Set([
-     ...set1,
-   ].filter((x) => set2.has(x)));
+   const result = new Set();
+   for (const elem of set1) {
+     if (set2.has(elem)) {
+       result.add(elem);
+     }
+   }
+   return result;
  },
components/pipedream_utils/actions/parse-json/parse-json.mjs (2)

2-7: Inconsistent key naming pattern

The key formatting-parse-json doesn't follow the pattern in other components which use the app name as prefix (e.g., pipedream_utils-compare-arrays).

- key: "formatting-parse-json",
+ key: "pipedream_utils-parse-json",

16-25: Consider adding input validation and improved error handling

The error handling is good, but you might want to add validation for empty input and provide more specific error messages.

  async run({ $ }) {
+   if (!this.input || !this.input.trim()) {
+     throw new Error("Input cannot be empty or whitespace");
+   }
    try {
      const result = JSON.parse(this.input);
      $.export("$summary", "Successfully parsed JSON string");
      return result;
    }
    catch (err) {
-     throw new Error("Error parsing input as JSON: " + err.toString());
+     throw new Error(`Error parsing input as JSON: ${err.message || err.toString()}`);
    }
  },
components/pipedream_utils/actions/compare-dates/compare-dates.mjs (3)

4-11: Inconsistent key naming pattern

Similar to the previous file, the key formatting-compare-dates doesn't follow the naming pattern used in other components like pipedream_utils-compare-arrays.

- key: "formatting-compare-dates",
+ key: "pipedream_utils-compare-dates",

36-56: Improve readability of date duration formatting

The date difference calculation is complex and could benefit from better variable naming and comments to improve readability.

Consider extracting the duration formatting logic into a separate method for better readability and maintainability. This would also make the run method cleaner.

formatDuration(milliseconds) {
  if (!milliseconds) return "equal";
  
  const arrResults = [];
  const arrUnits = Object.entries(DATE_TIME_UNITS).sort((a, b) => b[1] - a[1]);
  let remaining = milliseconds;
  
  for (const [unitName, unitValue] of arrUnits) {
    const amount = Math.floor(remaining / unitValue);
    if (amount) {
      arrResults.push(`${amount} ${amount === 1 ? unitName : `${unitName}s`}`);
      remaining %= unitValue;
      if (!remaining) break;
    }
  }
  
  return arrResults.join(", ");
}

41-54: Improve variable naming

The variable names could be more descriptive to better reflect their purpose.

- for (const [
-   word,
-   unit,
- ] of arrUnits) {
-   const amount = Math.floor(remainingValue / unit);
+ for (const [
+   unitName,
+   unitValue,
+ ] of arrUnits) {
+   const amount = Math.floor(remainingValue / unitValue);
  if (amount) {
    arrResults.push(`${amount} ${amount === 1
-     ? word
-     : `${word}s`}`);
-   remainingValue %= unit;
+     ? unitName
+     : `${unitName}s`}`);
+   remainingValue %= unitValue;
components/pipedream_utils/actions/set-default-value/set-default-value.mjs (2)

2-7: Inconsistent key naming pattern

Similar to the other files, the key formatting-set-default-value doesn't follow the pattern used in components like pipedream_utils-compare-arrays.

- key: "formatting-set-default-value",
+ key: "pipedream_utils-set-default-value",

21-30: Handle broader range of empty values

The current implementation only checks for falsy values, which may not catch all cases of "empty" input, such as strings with only whitespace.

  async run({ $ }) {
    const {
      input, defaultValue,
    } = this;
-   const result = input || defaultValue;
+   const result = (input === undefined || input === null || input === '') 
+     ? defaultValue 
+     : input;
    $.export("$summary", input
-     ? "Checked text - not empty"
-     : "Replaced empty text with default value");
+     ? "Input is not empty - using original value"
+     : "Input is empty - using default value");
    return result;
  },
components/pipedream_utils/actions/extract-phone-number/extract-phone-number.mjs (1)

9-31: Consider expanding phone number format options

The current regex patterns may not catch all common phone number formats. The NANP regex won't match plain 10 digits (e.g., "1234567890") or formats with dots (e.g., "123.456.7890"). The international format is quite restrictive and won't match many valid international phone numbers.

Consider adding more formats or a more comprehensive regex pattern. For example:

  options: [
    {
      label: "North American Number Plan (NANP) e.g. `(123) 456-7890` or `123-456-7890`",
      value: "((\\([0-9]{3}\\) ?)|[0-9]{3}-)[0-9]{3}-[0-9]{4}",
    },
    {
      label: "International e.g. `(12) 34-56-78-90`",
      value: "\\([0-9]{2}\\) ?([0-9]{2}-){3}[0-9]{2}",
    },
+   {
+     label: "Generic (matches most formats)",
+     value: "\\+?[0-9]{1,4}?[-. ]?\\(?[0-9]{1,3}?\\)?[-. ]?[0-9]{1,4}[-. ]?[0-9]{1,4}[-. ]?[0-9]{1,9}",
+   },
  ],
components/pipedream_utils/actions/convert-json-to-string/convert-json-to-string.mjs (1)

16-25: Consider adding formatting options and handling circular references

The current implementation is straightforward but could be enhanced with options for pretty-printing JSON and handling circular references.

Consider adding these improvements:

  props: {
    pipedream_utils,
    input: {
      label: "Input",
      description: "An object to be serialized to a JSON format string.",
      type: "object",
    },
+   indentation: {
+     label: "Indentation",
+     description: "Number of spaces to use for indentation (0 for no formatting)",
+     type: "integer",
+     default: 0,
+     optional: true,
+   },
  },
  async run({ $ }) {
    try {
-     const result = JSON.stringify(this.input);
+     const result = JSON.stringify(this.input, function(key, value) {
+       // Handle circular references
+       if (typeof value === 'object' && value !== null) {
+         if (seen.has(value)) {
+           return '[Circular Reference]';
+         }
+         seen.add(value);
+       }
+       return value;
+     }, this.indentation || null);
+     seen = null; // Clean up
      $.export("$summary", "Successfully convert object to JSON string");
      return result;
    }

Also, consider adding a seen = new WeakSet() variable before the try block to support the circular reference detection.

components/pipedream_utils/actions/download-file-to-tmp/download-file-to-tmp.mjs (1)

42-47: Consider returning a more structured and informative response.

The current return value is an array with filename and path. A structured object would be more self-documenting and could include additional useful metadata.

-    const filedata = [
-      filename,
-      downloadedFilepath,
-    ];
-
-    return filedata;
+    return {
+      filename,
+      path: downloadedFilepath,
+      size: fs.statSync(downloadedFilepath).size,
+      url: url,
+    };
components/pipedream_utils/actions/send-email-with-nodemailer/send-email-with-nodemailer.mjs (2)

89-96: Consider adding support for more email features.

The current implementation is missing support for common email features like BCC, attachments, and reply-to headers.

    var mailOptions = {
      from: this.from,
      to: this.to,
      cc: this.cc,
+     bcc: this.bcc,
+     replyTo: this.replyTo,
      subject: this.subject,
      text: (this.text || ""),
      html: (this.html || ""),
+     attachments: this.attachments,
    };

Don't forget to add the corresponding props in the props section:

bcc: {
  type: "string",
  description: "Comma separated list or an array of recipients email addresses that will appear on the Bcc: field",
  optional: true,
},
replyTo: {
  type: "string",
  description: "Email address that will appear on the Reply-To: field",
  optional: true,
},
attachments: {
  type: "object",
  description: "An array of attachment objects (see Nodemailer docs)",
  optional: true,
},

1-1: Remove legacy_hash_id comment if not needed.

The comment with legacy_hash_id appears to be an artifact from a previous system or migration.

-// legacy_hash_id: a_EViLg3
components/pipedream_utils/actions/convert-object-to-json-string/convert-object-to-json-string.mjs (1)

1-1: Remove legacy_hash_id comment if not needed.

The comment with legacy_hash_id appears to be an artifact from a previous system or migration.

-// legacy_hash_id: a_a4i80O
components/pipedream_utils/package.json (1)

1-27: Consider adding additional package.json metadata.

For better package documentation and maintenance, consider adding:

  1. A repository field pointing to the GitHub repository
  2. A bugs field for issue reporting
  3. A license field (if applicable)
  4. An engines field to specify Node.js version compatibility

This is particularly important for utility packages that may be used across different projects.

components/pipedream_utils/actions/get-coutry-name-by-code-iso/get-coutry-name-by-code-iso.mjs (2)

18-265: Move country data outside the run method

The country code mapping is defined inside the run method, which means it's recreated every time the action runs. For better performance, move this large object outside the method as a constant.

import pipedream_utils from "../../pipedream_utils.app.mjs";

+const ISO_COUNTRIES = {
+  "AF": "Afghanistan",
+  "AX": "Aland Islands",
+  // ... rest of the country definitions
+  "ZW": "Zimbabwe",
+};
+
export default {
  key: "pipedream_utils-get-country-name-by-code-iso",
  // ... rest of component definition
  
  async run() {
    const { countryCode } = this;
-    const isoCountries = {
-      "AF": "Afghanistan",
-      "AX": "Aland Islands",
-      // ... rest of the country definitions
-      "ZW": "Zimbabwe",
-    };

-    if (isoCountries.get(countryCode)) {
-      return isoCountries[countryCode];
+    if (ISO_COUNTRIES[countryCode]) {
+      return ISO_COUNTRIES[countryCode];
    } else {
      return countryCode;
    }
  },
};

17-271: Add input normalization for country codes

The component doesn't normalize the input country code, which could lead to failed lookups if users provide lowercase codes.

async run() {
  const { countryCode } = this;
+  // Normalize the country code to uppercase
+  const normalizedCode = countryCode ? countryCode.toUpperCase() : countryCode;
  
  // ... rest of the function
-  if (isoCountries[countryCode]) {
-    return isoCountries[countryCode];
+  if (ISO_COUNTRIES[normalizedCode]) {
+    return ISO_COUNTRIES[normalizedCode];
  } else {
-    return countryCode;
+    return normalizedCode;
  }
},
components/pipedream_utils/actions/transform-case/transform-case.mjs (2)

26-39: Enhance error message with specific failure details

The current error handling throws a generic configuration error. Consider enhancing it with more specific information about what went wrong.

try {
  const { outputFn } = CASE_OPTIONS.find(({ value }) => value === operation);
  const result = outputFn(input);
  $.export("$summary", "Successfully transformed text case");
  return result;
}
catch (err) {
-  throw new ConfigurationError("**Parse error** - check your input and if the selected operation is correct.");
+  throw new ConfigurationError(`**Parse error** - ${err.message}. Please check your input and ensure the selected operation (${operation}) is correct.`);
}

30-33: Add validation for invalid operation selection

The code doesn't explicitly check if a valid operation was found in CASE_OPTIONS. If operation doesn't match any value, outputFn will be undefined, causing an error.

try {
-  const { outputFn } = CASE_OPTIONS.find(({ value }) => value === operation);
+  const option = CASE_OPTIONS.find(({ value }) => value === operation);
+  if (!option) {
+    throw new Error(`Invalid operation: ${operation}`);
+  }
+  const { outputFn } = option;
  const result = outputFn(input);
  $.export("$summary", "Successfully transformed text case");
  return result;
}
components/pipedream_utils/common/date-time/commonDateTime.mjs (1)

24-39: Consider adding timezone handling capabilities

The date parsing functionality works well, but doesn't explicitly handle timezone information. For a more robust solution, consider adding timezone support to avoid unexpected date/time conversions.

The current implementation will use the local timezone when parsing dates without explicit timezone information. For international applications, you might want to:

  1. Allow users to specify a timezone
  2. Clarify in documentation how timezones are handled
  3. Consider using a library like date-fns-tz or moment-timezone for more robust timezone support
components/pipedream_utils/actions/retrieve-new-rss-stories/retrieve-new-rss-stories.mjs (1)

42-42: Add export summary for better workflow visibility

Adding a summary export will make it clearer to users what the action accomplished.

this.db.set("$checkpoint", previouslyPostedStories.concat(newStories.map((s) => s.link)));
+ $.export("$summary", `Found ${newStories.length} new stories from RSS feeds`);
return newStories;
components/pipedream_utils/actions/add-subtract-time/add-subtract-time.mjs (3)

42-65: Improve the duration parsing logic to handle more formats

The current implementation only supports basic formats like "1s", "1m", etc., but doesn't handle more complex formats like "1.5h" or combinations like "1h30m".

getOperationMilliseconds(str) {
-  let result = 0;
-  const {
-    second, minute, hour, day, week, year,
-  } = DATE_TIME_UNITS;
-  Object.entries({
-    s: second,
-    m: minute,
-    h: hour,
-    d: day,
-    w: week,
-    y: year,
-  }).forEach(([
-    identifier,
-    multiplier,
-  ]) => {
-    const substr = str.match(new RegExp(`[0-9]+\\s*${identifier}`))?.[0];
-    if (substr) {
-      const value = Number(substr.match(/[0-9]+/));
-      result += value * multiplier;
-    }
-  });
-  return result;
+  const normalizedStr = str.toLowerCase().trim();
+  let result = 0;
+  const {
+    second, minute, hour, day, week, year,
+  } = DATE_TIME_UNITS;
+  
+  // Handle combined formats (e.g., "1h30m")
+  const patterns = [
+    { regex: /([0-9]+\.?[0-9]*)[ ]*s(ec(ond)?s?)?/g, multiplier: second },
+    { regex: /([0-9]+\.?[0-9]*)[ ]*m(in(ute)?s?)?/g, multiplier: minute },
+    { regex: /([0-9]+\.?[0-9]*)[ ]*h(r|our)?s?/g, multiplier: hour },
+    { regex: /([0-9]+\.?[0-9]*)[ ]*d(ay)?s?/g, multiplier: day },
+    { regex: /([0-9]+\.?[0-9]*)[ ]*w(eek)?s?/g, multiplier: week },
+    { regex: /([0-9]+\.?[0-9]*)[ ]*y(ear)?s?/g, multiplier: year },
+  ];
+  
+  patterns.forEach(({ regex, multiplier }) => {
+    let match;
+    while ((match = regex.exec(normalizedStr)) !== null) {
+      const value = parseFloat(match[1]);
+      if (!isNaN(value)) {
+        result += value * multiplier;
+      }
+    }
+  });
+  
+  return result;
},

78-89: Improve error message with more specific information

The current error message doesn't provide enough information to help users debug their issue.

try {
  const { outputFn } = DATE_FORMAT_PARSE_MAP.get(format);
  const output = outputFn(sugar.Date.create(result));
  $.export("$summary", `Successfully ${operation === OPERATION_OPTIONS.SUBTRACT
    ? "subtracted"
    : "added"} time`);
  return output;
}
catch (err) {
  console.log("Error parsing date", err);
-  throw new ConfigurationError("**Parse error** - check your input and if the selected format is correct.");
+  throw new ConfigurationError(`**Parse error** - Unable to format the date using the '${format}' format. Please check your input date and selected format. Original error: ${err.message}`);
}

81-83: Simplify the ternary operator for better readability

The current ternary operator spans multiple lines and could be simplified.

-  $.export("$summary", `Successfully ${operation === OPERATION_OPTIONS.SUBTRACT
-    ? "subtracted"
-    : "added"} time`);
+  const actionWord = operation === OPERATION_OPTIONS.SUBTRACT ? "subtracted" : "added";
+  $.export("$summary", `Successfully ${actionWord} time`);
components/pipedream_utils/actions/export-variables/export-variables.mjs (2)

25-35: Enhance the parse method to handle more edge cases

The current implementation doesn't handle all potential edge cases, which could lead to unexpected errors.

parse(value) {
  const valueToParse = this.emptyStrToUndefined(value);
-  if (typeof(valueToParse) === "object" || valueToParse === undefined) {
+  // Return null, undefined, or already object values as is
+  if (valueToParse === null || valueToParse === undefined || typeof(valueToParse) === "object") {
    return valueToParse;
  }
+  // Handle non-string primitives
+  if (typeof(valueToParse) !== "string") {
+    return valueToParse;
+  }
  try {
    return JSON.parse(valueToParse);
  } catch (e) {
-    throw new ConfigurationError("Make sure the custom expression contains a valid object");
+    throw new ConfigurationError(`Failed to parse value as JSON: ${e.message}. Make sure the custom expression contains a valid object.`);
  }
},

37-39: Add success feedback and error handling to the run method

The current implementation doesn't provide any feedback or error handling.

run({ $ }) {
-  $.export("config", this.parse(this.config));
+  try {
+    const parsedConfig = this.parse(this.config);
+    $.export("config", parsedConfig);
+    $.export("$summary", `Successfully exported ${Object.keys(parsedConfig || {}).length} configuration variable(s)`);
+  } catch (error) {
+    console.error("Error parsing configuration:", error);
+    throw error;
+  }
},
components/pipedream_utils/actions/csv-file-to-objects/csv-file-to-objects.mjs (2)

57-63: Consider using asynchronous file operations.

The current implementation uses readFileSync which blocks the event loop. For better performance and scalability, especially with large files, consider using the asynchronous fs.promises.readFile instead.

-import { readFileSync } from "fs";
+import { promises as fsPromises } from "fs";

// ...

-      fileContent = readFileSync(path.resolve(filePath), "utf8");
+      fileContent = await fsPromises.readFile(path.resolve(filePath), "utf8");

73-74: Add more detailed summary information.

The current summary only indicates the number of records converted. Consider providing more details such as the CSV file name to make the summary more informative.

-      $.export("$summary", `Converted ${records.length} records from CSV to objects.`);
+      const fileName = path.basename(filePath);
+      $.export("$summary", `Converted ${records.length} records from CSV file "${fileName}" to objects.`);
components/pipedream_utils/actions/replace-text/replace-text.mjs (2)

40-56: Simplify text replacement logic.

The current implementation uses a potentially inefficient loop for non-regex replacements. Modern JavaScript provides the replaceAll method which would be more efficient. While the comment mentions Node 14.x compatibility, consider detecting if replaceAll is available:

-    // replaceAll would be optimal, but it is not supported in Node 14.x
-    let result = input.replace(expression, replaceValue);
-    if (!isRegExp) {
-      let counter = 0;
-      while (result.match(expression)) {
-        result = result.replace(expression, replaceValue);
-        if (++counter > MAX_REPLACES) {
-          throw new Error(`Over ${MAX_REPLACES} replace operations reached - please check your inputs and, if this is expected, search with a regular expression instead of text`);
-        }
-      }
-    }
+    let result;
+    if (isRegExp) {
+      result = input.replace(expression, replaceValue);
+    } else if (typeof input.replaceAll === 'function') {
+      // Use replaceAll if available (Node.js 15.0.0+)
+      result = input.replaceAll(expression, replaceValue);
+    } else {
+      // Fallback for older Node.js versions
+      result = input.replace(expression, replaceValue);
+      let counter = 0;
+      while (result.match(expression)) {
+        result = result.replace(expression, replaceValue);
+        if (++counter > MAX_REPLACES) {
+          throw new Error(`Over ${MAX_REPLACES} replace operations reached - please check your inputs and, if this is expected, search with a regular expression instead of text`);
+        }
+      }
+    }

51-56: Improve match count calculation.

The current implementation for counting matches has a potential issue. For non-regex replacements, it's only checking if the input includes the findText, and then setting matchText to "text" rather than the actual count. Consider improving this to provide an accurate count:

-    const matchText = isRegExp
-      ? [
-        ...input.matchAll(expression),
-      ].length
-      : input.includes(findText) && "text";
-    $.export("$summary", matchText
-      ? `Successfully replaced ${matchText} matches`
-      : "No matches found. Input was not modified");
+    let matchCount = 0;
+    if (isRegExp) {
+      matchCount = [...input.matchAll(expression)].length;
+    } else {
+      // Count occurrences for non-regex
+      let tempInput = input;
+      let index = tempInput.indexOf(findText);
+      while (index !== -1) {
+        matchCount++;
+        tempInput = tempInput.substring(index + findText.length);
+        index = tempInput.indexOf(findText);
+      }
+    }
+    
+    $.export("$summary", matchCount > 0
+      ? `Successfully replaced ${matchCount} ${matchCount === 1 ? 'match' : 'matches'}`
+      : "No matches found. Input was not modified");
components/pipedream_utils/common/date-time/dateFormats.mjs (1)

167-168: Add comprehensive documentation for exported constants.

Consider adding JSDoc comments to the exported constants to better document their purpose, expected usage, and any constraints:

+/**
+ * Maps date format strings to their corresponding input and output functions.
+ * Input functions convert strings to date objects, while output functions format date objects as strings.
+ * @type {Map<string, {inputFn?: Function, outputFn: Function}>}
+ */
export const DATE_FORMAT_PARSE_MAP = new Map(mapData);

+/**
+ * Array of all supported date format strings.
+ * These can be used as options for date format selection in the UI.
+ * @type {string[]}
+ */
export const DATE_FORMAT_OPTIONS = DATE_FORMATS.map(({ value }) => value);
components/pipedream_utils/actions/convert-currency/convert-currency.mjs (1)

34-48: Consider caching currency options.

The getCurrencyOptions method fetches the currency list from an external API each time it's called. Since currency lists don't change frequently, consider implementing caching to reduce API calls:

+// Cache to store currency options
+let currencyOptionsCache = null;
+let cacheTime = null;
+const CACHE_DURATION = 60 * 60 * 1000; // 1 hour in milliseconds

methods: {
  async getCurrencyOptions() {
+    // Use cached options if they exist and are not expired
+    const now = Date.now();
+    if (currencyOptionsCache && cacheTime && now - cacheTime < CACHE_DURATION) {
+      return currencyOptionsCache;
+    }
    
    const currencies = await this.getCurrencies();
    const options = [];
    for (const [
      key,
      value,
    ] of Object.entries(currencies)) {
      options.push({
        value: key,
        label: value,
      });
    }
+    
+    // Update cache
+    currencyOptionsCache = options;
+    cacheTime = now;
    
    return options;
  },
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fae2ff9 and 11c813c.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (61)
  • components/pipedream_utils/README.md (1 hunks)
  • components/pipedream_utils/actions/add-subtract-time/add-subtract-time.mjs (1 hunks)
  • components/pipedream_utils/actions/base64-decode-string/base64-decode-string.mjs (1 hunks)
  • components/pipedream_utils/actions/compare-arrays/compare-arrays.mjs (1 hunks)
  • components/pipedream_utils/actions/compare-dates/compare-dates.mjs (1 hunks)
  • components/pipedream_utils/actions/convert-currency/convert-currency.mjs (1 hunks)
  • components/pipedream_utils/actions/convert-html-to-markdown/convert-html-to-markdown.mjs (1 hunks)
  • components/pipedream_utils/actions/convert-html-to-slack-mrkdwn/convert-html-to-slack-mrkdwn.mjs (1 hunks)
  • components/pipedream_utils/actions/convert-html-to-text/convert-html-to-text.mjs (1 hunks)
  • components/pipedream_utils/actions/convert-json-to-string/convert-json-to-string.mjs (1 hunks)
  • components/pipedream_utils/actions/convert-markdown-to-html/convert-markdown-to-html.mjs (1 hunks)
  • components/pipedream_utils/actions/convert-object-to-json-string/convert-object-to-json-string.mjs (1 hunks)
  • components/pipedream_utils/actions/csv-file-to-objects/csv-file-to-objects.mjs (1 hunks)
  • components/pipedream_utils/actions/date-time-format/date-time-format.mjs (1 hunks)
  • components/pipedream_utils/actions/download-file-to-tmp/download-file-to-tmp.mjs (1 hunks)
  • components/pipedream_utils/actions/export-variables/export-variables.mjs (1 hunks)
  • components/pipedream_utils/actions/extract-by-regular-expression/extract-by-regular-expression.mjs (1 hunks)
  • components/pipedream_utils/actions/extract-email-address/extract-email-address.mjs (1 hunks)
  • components/pipedream_utils/actions/extract-number/extract-number.mjs (1 hunks)
  • components/pipedream_utils/actions/extract-phone-number/extract-phone-number.mjs (1 hunks)
  • components/pipedream_utils/actions/extract-url/extract-url.mjs (1 hunks)
  • components/pipedream_utils/actions/format-currency/format-currency.mjs (1 hunks)
  • components/pipedream_utils/actions/format-iso8601-datetime/format-iso8601-datetime.mjs (1 hunks)
  • components/pipedream_utils/actions/format-number/format-number.mjs (1 hunks)
  • components/pipedream_utils/actions/get-coutry-name-by-code-iso/get-coutry-name-by-code-iso.mjs (1 hunks)
  • components/pipedream_utils/actions/get-current-time-in-specific-timezone/get-current-time-in-specific-timezone.mjs (1 hunks)
  • components/pipedream_utils/actions/get-iso-string-n-days-ago/get-iso-string-n-days-ago.mjs (1 hunks)
  • components/pipedream_utils/actions/get-time-in-specific-timezone/get-time-in-specific-timezone.mjs (1 hunks)
  • components/pipedream_utils/actions/html-to-markdown/html-to-markdown.mjs (1 hunks)
  • components/pipedream_utils/actions/parse-json/parse-json.mjs (1 hunks)
  • components/pipedream_utils/actions/prettify-json/prettify-json.mjs (1 hunks)
  • components/pipedream_utils/actions/random-integer/random-integer.mjs (1 hunks)
  • components/pipedream_utils/actions/random-item-from-list/random-item-from-list.mjs (1 hunks)
  • components/pipedream_utils/actions/replace-text/replace-text.mjs (1 hunks)
  • components/pipedream_utils/actions/retrieve-all-rss-stories/retrieve-all-rss-stories.mjs (1 hunks)
  • components/pipedream_utils/actions/retrieve-new-rss-stories/retrieve-new-rss-stories.mjs (1 hunks)
  • components/pipedream_utils/actions/schedule-task-in-future/schedule-task-in-future.mjs (1 hunks)
  • components/pipedream_utils/actions/send-email-with-nodemailer/send-email-with-nodemailer.mjs (1 hunks)
  • components/pipedream_utils/actions/send-to-s3/send-to-s3.mjs (1 hunks)
  • components/pipedream_utils/actions/set-default-value/set-default-value.mjs (1 hunks)
  • components/pipedream_utils/actions/split-text/split-text.mjs (1 hunks)
  • components/pipedream_utils/actions/transform-case/transform-case.mjs (1 hunks)
  • components/pipedream_utils/actions/trigger-workflow/trigger-workflow.mjs (1 hunks)
  • components/pipedream_utils/actions/trim-whitespace/trim-whitespace.mjs (1 hunks)
  • components/pipedream_utils/actions/url-decode/url-decode.mjs (1 hunks)
  • components/pipedream_utils/actions/url-encode/url-encode.mjs (1 hunks)
  • components/pipedream_utils/actions/xml-to-json/xml-to-json.mjs (1 hunks)
  • components/pipedream_utils/common/date-time/commonDateTime.mjs (1 hunks)
  • components/pipedream_utils/common/date-time/dateFormats.mjs (1 hunks)
  • components/pipedream_utils/common/date-time/dateTimeUnits.mjs (1 hunks)
  • components/pipedream_utils/common/numbers/currencies.mjs (1 hunks)
  • components/pipedream_utils/common/numbers/currencyFormats.mjs (1 hunks)
  • components/pipedream_utils/common/numbers/formatNumber.mjs (1 hunks)
  • components/pipedream_utils/common/numbers/numberFormattingOptions.mjs (1 hunks)
  • components/pipedream_utils/common/text/buildRegExp.mjs (1 hunks)
  • components/pipedream_utils/common/text/caseOptions.mjs (1 hunks)
  • components/pipedream_utils/common/text/commonExtractText.mjs (1 hunks)
  • components/pipedream_utils/common/text/splitTextOptions.mjs (1 hunks)
  • components/pipedream_utils/package.json (2 hunks)
  • components/pipedream_utils/pipedream_utils.app.mjs (1 hunks)
  • components/pipedream_utils/pipedream_utils.mjs (0 hunks)
💤 Files with no reviewable changes (1)
  • components/pipedream_utils/pipedream_utils.mjs
🧰 Additional context used
🪛 GitHub Check: Lint Code Base
components/pipedream_utils/actions/convert-html-to-slack-mrkdwn/convert-html-to-slack-mrkdwn.mjs

[warning] 13-13:
Component prop html must have a description. See https://pipedream.com/docs/components/guidelines/#props

components/pipedream_utils/actions/format-iso8601-datetime/format-iso8601-datetime.mjs

[warning] 13-13:
Component prop iso8601 must have a description. See https://pipedream.com/docs/components/guidelines/#props

components/pipedream_utils/actions/convert-object-to-json-string/convert-object-to-json-string.mjs

[warning] 12-12:
Component prop object must have a label. See https://pipedream.com/docs/components/guidelines/#props

components/pipedream_utils/actions/get-time-in-specific-timezone/get-time-in-specific-timezone.mjs

[warning] 18-18:
Component prop timezone must have a label. See https://pipedream.com/docs/components/guidelines/#props

components/pipedream_utils/actions/html-to-markdown/html-to-markdown.mjs

[warning] 13-13:
Component prop input must have a description. See https://pipedream.com/docs/components/guidelines/#props

components/pipedream_utils/actions/get-current-time-in-specific-timezone/get-current-time-in-specific-timezone.mjs

[warning] 13-13:
Component prop timezone must have a label. See https://pipedream.com/docs/components/guidelines/#props

components/pipedream_utils/actions/get-iso-string-n-days-ago/get-iso-string-n-days-ago.mjs

[warning] 13-13:
Component prop days must have a description. See https://pipedream.com/docs/components/guidelines/#props

components/pipedream_utils/actions/get-coutry-name-by-code-iso/get-coutry-name-by-code-iso.mjs

[warning] 12-12:
Component prop countryCode must have a label. See https://pipedream.com/docs/components/guidelines/#props

components/pipedream_utils/actions/base64-decode-string/base64-decode-string.mjs

[warning] 12-12:
Component prop data must have a label. See https://pipedream.com/docs/components/guidelines/#props


[warning] 12-12:
Component prop data must have a description. See https://pipedream.com/docs/components/guidelines/#props

🪛 LanguageTool
components/pipedream_utils/README.md

[style] ~3-~3: Consider using a different verb to strengthen your wording.
Context: ...ate code needed for routine operations, speeding up the development of intricate automation...

(SPEED_UP_ACCELERATE)

🪛 Biome (1.9.4)
components/pipedream_utils/actions/format-currency/format-currency.mjs

[error] 59-61: The default clause should be the last switch clause.

The following case clause is here:

Regardless its position, the default clause is always executed when there is no match. To avoid confusion, the default clause should be the last switch clause.

(lint/suspicious/useDefaultSwitchClauseLast)

⏰ Context from checks skipped due to timeout of 90000ms (3)
  • GitHub Check: pnpm publish
  • GitHub Check: Verify TypeScript components
  • GitHub Check: Publish TypeScript components
🔇 Additional comments (32)
components/pipedream_utils/common/date-time/dateTimeUnits.mjs (1)

1-16: Simple, effective time unit definitions

The constants are well-organized and each unit is defined in terms of the preceding unit, which is a clean approach. This makes the code maintainable and easy to understand.

components/pipedream_utils/actions/url-encode/url-encode.mjs (1)

1-21: Well-structured URL encoding component

The component is properly structured with clear naming, good descriptions, and appropriate implementation using the standard encodeURIComponent function.

components/pipedream_utils/actions/trim-whitespace/trim-whitespace.mjs (1)

1-22: LGTM: Straightforward and well-implemented trim whitespace component

This component correctly implements a text whitespace trimming utility with appropriate props and descriptions. The implementation is clean and performs the exact functionality described.

components/pipedream_utils/common/numbers/currencyFormats.mjs (1)

1-26: LGTM: Well-structured currency format options

The currency format options are clearly defined with helpful example labels and appropriate ICU format patterns for different display preferences.

components/pipedream_utils/common/numbers/numberFormattingOptions.mjs (1)

1-28: Well-structured formatting options constants

The constants provide clear, descriptive options for decimal marks and number formatting patterns. The consistent structure with label/value pairs makes them easy to use in dropdown menus.

components/pipedream_utils/actions/schedule-task-in-future/schedule-task-in-future.mjs (2)

40-42: LGTM: Secure handling of optional secret

Good implementation of conditional header inclusion. This ensures the secret is only included when provided, maintaining security best practices.


44-51: LGTM: Well-structured HTTP request

The axios request is well structured with proper URL composition, headers, and payload data. It correctly sends the timestamp and message to the task scheduler endpoint.

components/pipedream_utils/actions/random-item-from-list/random-item-from-list.mjs (1)

27-29: LGTM: Good validation for quantity parameter

The validation ensures that the quantity requested doesn't exceed the list size, throwing an appropriate configuration error with a descriptive message.

components/pipedream_utils/actions/extract-email-address/extract-email-address.mjs (1)

6-8:

✅ Verification successful

Check key and naming consistency

The key is "formatting-extract-email-address" but it's part of the pipedream_utils components. Ensure this naming convention is consistent with other components in the library.


🏁 Script executed:

#!/bin/bash
# Check the naming patterns for other components to ensure consistency
echo "Checking key naming patterns in other components..."
grep -r "key:" --include="*.mjs" components/pipedream_utils/actions/ | head -n 10

Length of output: 1438


Naming Consistency Verified for extract-email-address

The key "formatting-extract-email-address" is consistent with the naming pattern used by other formatting-related actions in the pipedream_utils library (e.g., "formatting-add-subtract-time", "formatting-compare-dates", "formatting-convert-html-to-markdown", etc.). No changes are necessary.

components/pipedream_utils/actions/split-text/split-text.mjs (1)

50-52:

✅ Verification successful

Implementation comment that should be addressed

There's a comment about an issue (#5429) that suggests this case wouldn't be needed if 0 was accepted as an option.


🏁 Script executed:

#!/bin/bash
# Check the current status of issue #5429
gh issue view 5429 || echo "Issue not found or not accessible"

Length of output: 1284


Action Required: Clarify Temporary Workaround for Issue #5429

After verifying that issue #5429 is still open and the bug (where integer props with options do not accept 0) persists, the current implementation in components/pipedream_utils/actions/split-text/split-text.mjs (lines 50-52) remains necessary. Please consider updating the comment to clearly mark this case as a temporary workaround and add a TODO note, such as:

  • "TODO: Remove this case once integer props with options accept 0 (see #5429)."

This will help future developers understand the intent and know to revisit this section once the bug is fixed.

components/pipedream_utils/common/text/commonExtractText.mjs (1)

7-9: Good use of abstract method pattern

The method throws an appropriate error to indicate it should be implemented by extending modules.

components/pipedream_utils/actions/html-to-markdown/html-to-markdown.mjs (2)

5-10: Action metadata looks good

The component is well-structured with appropriate key, name, description, version, and type properties.


102-117: Run method implementation is solid

The implementation correctly initializes the TurndownService with all the user-configurable options and returns the converted markdown content.

components/pipedream_utils/actions/xml-to-json/xml-to-json.mjs (3)

4-9: Well-structured component metadata

The component has clear and descriptive metadata with appropriate key, name, description, version, and type.


11-30: Props are well-documented

All props have descriptive labels and helpful descriptions, including links to documentation for additional information. Default values are appropriately set for optional properties.


32-39: Run method properly implements XML to JSON conversion

The implementation correctly uses the xml2js function with the user-provided options and returns the converted JSON. The summary export provides good feedback to the user.

components/pipedream_utils/common/numbers/currencies.mjs (2)

1-767: Comprehensive currency data implementation

The CURRENCIES array provides a thorough collection of world currencies with consistent structure (name, symbol, ISO code). This will be valuable for various currency-related operations.


768-773: Effective currency options mapping

The exported CURRENCY_OPTIONS transforms the raw currency data into a format suitable for dropdown menus or selectors, with a descriptive label and value format that includes all relevant information.

components/pipedream_utils/actions/compare-arrays/compare-arrays.mjs (1)

63-91: Improve run method with non-mutating operations

If you implement the suggested changes to create new Sets rather than modifying the originals, the run method would still work correctly, but would be more consistent and safer.

The switch statement is well-structured and handles errors appropriately. The final conversion from Set to Array is correct.

components/pipedream_utils/actions/extract-phone-number/extract-phone-number.mjs (3)

1-4: LGTM: Good use of extending the common module

The component properly extends the commonExtractText module, which promotes code reuse and consistency across text extraction components.


4-8: Clear naming and metadata

The name, description, key, version, and type are well-defined and follow a consistent naming pattern that clearly identifies this as a formatting utility for text.


32-40: LGTM: Methods implementation is clean and straightforward

The getRegExp and getType methods are implemented correctly and concisely. The getRegExp method properly creates a new RegExp object based on the selected format.

components/pipedream_utils/actions/extract-url/extract-url.mjs (2)

1-8: LGTM: Component structure and metadata

The component follows the same pattern as other extract components, with clear naming and metadata.


9-16: Props defined correctly

The input property is well-defined with appropriate label, description, and type.

components/pipedream_utils/actions/convert-json-to-string/convert-json-to-string.mjs (1)

1-15: LGTM: Component definition and props

The component properly defines its metadata and props with clear labels and descriptions.

components/pipedream_utils/actions/trigger-workflow/trigger-workflow.mjs (1)

1-22: LGTM: Well-defined component structure and props

The component has clear metadata and well-defined props with descriptive labels and helpful descriptions.

components/pipedream_utils/actions/download-file-to-tmp/download-file-to-tmp.mjs (1)

29-32: Good use of axios with arraybuffer response type.

The configuration correctly specifies responseType: "arraybuffer" which is appropriate for downloading binary files.

components/pipedream_utils/package.json (2)

3-3: Version bump is appropriate for new features.

The version increase from 0.0.1 to 0.0.2 is appropriate for adding new functionality.


15-25: Dependencies are well-structured with appropriate version constraints.

The added dependencies all use caret ranges (^) which is good practice as it allows for compatible minor and patch updates while maintaining the major version.

components/pipedream_utils/common/date-time/commonDateTime.mjs (2)

9-15: LGTM! Good use of description for inputDate

The description provides clear guidance on how the date string will be processed, including information about the fallback behavior when a format isn't specified.


30-31: Good validation for invalid dates

I appreciate the check for invalid dates using isNaN(dateObj.getFullYear()) - this is a reliable way to detect invalid Date objects.

components/pipedream_utils/pipedream_utils.app.mjs (1)

6-26: LGTM: Well-structured prop definitions for date handling

The prop definitions for inputDate, inputFormat, and outputFormat are well-structured with clear labels, descriptions, and appropriate options. This provides a good foundation for date manipulation utilities throughout the application.

Comment on lines +13 to +16
timezone: {
type: "string",
description: "The IANA timezone name, e.g. `America/Los_Angeles`. [See the full list here](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones).",
},
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add label to timezone property

The static analysis detected that the timezone prop is missing a required label property.

   timezone: {
     type: "string",
+    label: "Timezone",
     description: "The IANA timezone name, e.g. `America/Los_Angeles`. [See the full list here](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones).",
   },
📝 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
timezone: {
type: "string",
description: "The IANA timezone name, e.g. `America/Los_Angeles`. [See the full list here](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones).",
},
timezone: {
type: "string",
label: "Timezone",
description: "The IANA timezone name, e.g. `America/Los_Angeles`. [See the full list here](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones).",
},
🧰 Tools
🪛 GitHub Check: Lint Code Base

[warning] 13-13:
Component prop timezone must have a label. See https://pipedream.com/docs/components/guidelines/#props

},
},
async run() {
set(this, "prettifiedValue.val", JSON.stringify(this.val, null, 2));
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Parse JSON input before prettifying

The current implementation doesn't parse the input string as JSON before prettifying. If the input is already a JSON string, this will add unnecessary quotes.

-    set(this, "prettifiedValue.val", JSON.stringify(this.val, null, 2));
+    try {
+      const parsed = JSON.parse(this.val);
+      set(this, "prettifiedValue.val", JSON.stringify(parsed, null, 2));
+    } catch (error) {
+      throw new Error("Invalid JSON input: " + error.message);
+    }
📝 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
set(this, "prettifiedValue.val", JSON.stringify(this.val, null, 2));
try {
const parsed = JSON.parse(this.val);
set(this, "prettifiedValue.val", JSON.stringify(parsed, null, 2));
} catch (error) {
throw new Error("Invalid JSON input: " + error.message);
}

Comment on lines +19 to +21
async run() {
set(this, "prettifiedValue.val", JSON.stringify(this.val, null, 2));
},
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Function doesn't return the prettified JSON

The run method sets a property on this but doesn't return a value. This is inconsistent with typical action patterns where the result is returned directly.

  async run() {
-    set(this, "prettifiedValue.val", JSON.stringify(this.val, null, 2));
+    const prettified = JSON.stringify(this.val, null, 2);
+    $.export("$summary", "Successfully prettified JSON");
+    return prettified;
  },
📝 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
async run() {
set(this, "prettifiedValue.val", JSON.stringify(this.val, null, 2));
},
async run() {
const prettified = JSON.stringify(this.val, null, 2);
$.export("$summary", "Successfully prettified JSON");
return prettified;
},

Comment on lines +13 to +16
html: {
type: "string",
label: "HTML",
},
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Missing description for HTML prop

The static analysis correctly flagged that the HTML prop is missing a description, which is required by Pipedream's documentation guidelines.

html: {
  type: "string",
  label: "HTML",
+ description: "The HTML content to convert to Slack mrkdwn format",
},
📝 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
html: {
type: "string",
label: "HTML",
},
html: {
type: "string",
label: "HTML",
description: "The HTML content to convert to Slack mrkdwn format",
},
🧰 Tools
🪛 GitHub Check: Lint Code Base

[warning] 13-13:
Component prop html must have a description. See https://pipedream.com/docs/components/guidelines/#props

Comment on lines +2 to +14
export default function (str, defaultFlags) {
const end = str.match(/\/[a-z]*$/);
if (!end) {
throw new ConfigurationError("Parse error - invalid regular expression.");
}
str = str.slice(1, end[0].length * -1);
let flags = end[0].split("/")[1] ?? "";
defaultFlags?.forEach((flag) => {
if (!flags.includes(flag))
flags += flag;
});
return new RegExp(str, flags);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Improve regex extraction and error handling

The regex extraction logic has a few issues:

  1. The function assumes the regex always starts with '/' but doesn't check for it
  2. The pattern extraction logic on line 7 is problematic - it uses end[0].length * -1 which could incorrectly slice the string if there are escaped forward slashes in the pattern
-export default function (str, defaultFlags) {
+/**
+ * Builds a RegExp object from a string representation
+ * @param {string} str - The string representation of regex (e.g., "/pattern/flags")
+ * @param {string[]} [defaultFlags] - Optional default flags to add if not present
+ * @returns {RegExp} - The constructed RegExp object
+ * @throws {ConfigurationError} - If the string is not a valid regex
+ */
+export default function (str, defaultFlags) {
   const end = str.match(/\/[a-z]*$/);
   if (!end) {
-    throw new ConfigurationError("Parse error - invalid regular expression.");
+    throw new ConfigurationError("Parse error - invalid regular expression. Format should be '/pattern/flags'");
   }
-  str = str.slice(1, end[0].length * -1);
-  let flags = end[0].split("/")[1] ?? "";
+  
+  // Find the position of the last slash
+  const lastSlashPos = str.lastIndexOf('/');
+  if (lastSlashPos <= 0) {
+    throw new ConfigurationError("Parse error - invalid regular expression. Missing delimiters.");
+  }
+  
+  // Extract pattern (everything between the first and last slash)
+  const pattern = str.slice(1, lastSlashPos);
+  
+  // Extract flags (everything after the last slash)
+  let flags = str.slice(lastSlashPos + 1) ?? "";
   defaultFlags?.forEach((flag) => {
     if (!flags.includes(flag))
       flags += flag;
   });
-  return new RegExp(str, flags);
+  
+  try {
+    return new RegExp(pattern, flags);
+  } catch (error) {
+    throw new ConfigurationError(`Invalid regular expression: ${error.message}`);
+  }
📝 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
export default function (str, defaultFlags) {
const end = str.match(/\/[a-z]*$/);
if (!end) {
throw new ConfigurationError("Parse error - invalid regular expression.");
}
str = str.slice(1, end[0].length * -1);
let flags = end[0].split("/")[1] ?? "";
defaultFlags?.forEach((flag) => {
if (!flags.includes(flag))
flags += flag;
});
return new RegExp(str, flags);
}
/**
* Builds a RegExp object from a string representation
* @param {string} str - The string representation of regex (e.g., "/pattern/flags")
* @param {string[]} [defaultFlags] - Optional default flags to add if not present
* @returns {RegExp} - The constructed RegExp object
* @throws {ConfigurationError} - If the string is not a valid regex
*/
export default function (str, defaultFlags) {
const end = str.match(/\/[a-z]*$/);
if (!end) {
throw new ConfigurationError("Parse error - invalid regular expression. Format should be '/pattern/flags'");
}
// Find the position of the last slash
const lastSlashPos = str.lastIndexOf('/');
if (lastSlashPos <= 0) {
throw new ConfigurationError("Parse error - invalid regular expression. Missing delimiters.");
}
// Extract pattern (everything between the first and last slash)
const pattern = str.slice(1, lastSlashPos);
// Extract flags (everything after the last slash)
let flags = str.slice(lastSlashPos + 1) ?? "";
defaultFlags?.forEach((flag) => {
if (!flags.includes(flag))
flags += flag;
});
try {
return new RegExp(pattern, flags);
} catch (error) {
throw new ConfigurationError(`Invalid regular expression: ${error.message}`);
}
}

Comment on lines +3 to +6
export const DEFAULT_INPUT_FUNCTION = (str) => {
const num = Number(str);
return sugar.Date.create(num * 1000 || str);
};
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Improve error handling in the DEFAULT_INPUT_FUNCTION.

The current implementation doesn't handle potential errors when parsing dates. If the input is invalid, it might return an unexpected date object. Consider adding validation and explicit error handling:

export const DEFAULT_INPUT_FUNCTION = (str) => {
  const num = Number(str);
-  return sugar.Date.create(num * 1000 || str);
+  if (!str) {
+    throw new Error('Date string is required');
+  }
+  
+  const dateObj = sugar.Date.create(num * 1000 || str);
+  
+  // Validate that we got a valid date
+  if (isNaN(dateObj.getTime())) {
+    throw new Error(`Invalid date format: ${str}`);
+  }
+  
+  return dateObj;
};
📝 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
export const DEFAULT_INPUT_FUNCTION = (str) => {
const num = Number(str);
return sugar.Date.create(num * 1000 || str);
};
export const DEFAULT_INPUT_FUNCTION = (str) => {
const num = Number(str);
if (!str) {
throw new Error('Date string is required');
}
const dateObj = sugar.Date.create(num * 1000 || str);
// Validate that we got a valid date
if (isNaN(dateObj.getTime())) {
throw new Error(`Invalid date format: ${str}`);
}
return dateObj;
};

Comment on lines +89 to +104
{
value: "22-01-2006",
inputFn(str) {
const [
day,
month,
year,
] = str.split("-");
return sugar.Date.create(`${year}-${month}-${day}`, {
fromUTC: true,
});
},
outputFn(dateObj) {
return sugar.Date.format(dateObj, "%d-%m-%Y");
},
},
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add safeguards against invalid date components.

In the inputFn for the "22-01-2006" format, there's no validation that the split operation produced the expected components. This could lead to unexpected behavior or errors if the input doesn't match the expected format:

inputFn(str) {
  const [
    day,
    month,
    year,
  ] = str.split("-");
+  
+  // Validate that we have all expected components
+  if (!day || !month || !year) {
+    throw new Error(`Invalid date format: ${str}. Expected format: DD-MM-YYYY`);
+  }
+  
+  // Validate that components are numbers
+  if (isNaN(Number(day)) || isNaN(Number(month)) || isNaN(Number(year))) {
+    throw new Error(`Invalid date components in: ${str}. Day, month, and year must be numbers`);
+  }
  
  return sugar.Date.create(`${year}-${month}-${day}`, {
    fromUTC: true,
  });
}

Similar validation should be added to other inputFn implementations.

📝 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
{
value: "22-01-2006",
inputFn(str) {
const [
day,
month,
year,
] = str.split("-");
return sugar.Date.create(`${year}-${month}-${day}`, {
fromUTC: true,
});
},
outputFn(dateObj) {
return sugar.Date.format(dateObj, "%d-%m-%Y");
},
},
{
value: "22-01-2006",
inputFn(str) {
const [
day,
month,
year,
] = str.split("-");
// Validate that we have all expected components
if (!day || !month || !year) {
throw new Error(`Invalid date format: ${str}. Expected format: DD-MM-YYYY`);
}
// Validate that components are numbers
if (isNaN(Number(day)) || isNaN(Number(month)) || isNaN(Number(year))) {
throw new Error(`Invalid date components in: ${str}. Day, month, and year must be numbers`);
}
return sugar.Date.create(`${year}-${month}-${day}`, {
fromUTC: true,
});
},
outputFn(dateObj) {
return sugar.Date.format(dateObj, "%d-%m-%Y");
},
},

Comment on lines +66 to +68
const response = await this.convertCurrency($);
$.export("$summary", `${this.value} ${this.fromCurrency} = ${response.rates[this.toCurrency]} ${this.toCurrency}`);
return response;
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add validation for API response.

The current implementation assumes the API response will contain the expected data structure. Consider adding validation to ensure the response contains the required properties:

async run({ $ }) {
  const response = await this.convertCurrency($);
+  
+  // Validate response contains expected data
+  if (!response || !response.rates || !response.rates[this.toCurrency]) {
+    throw new Error(`Invalid API response: Missing conversion rate for ${this.toCurrency}`);
+  }
  
  $.export("$summary", `${this.value} ${this.fromCurrency} = ${response.rates[this.toCurrency]} ${this.toCurrency}`);
  return response;
}
📝 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
const response = await this.convertCurrency($);
$.export("$summary", `${this.value} ${this.fromCurrency} = ${response.rates[this.toCurrency]} ${this.toCurrency}`);
return response;
async run({ $ }) {
const response = await this.convertCurrency($);
// Validate response contains expected data
if (!response || !response.rates || !response.rates[this.toCurrency]) {
throw new Error(`Invalid API response: Missing conversion rate for ${this.toCurrency}`);
}
$.export("$summary", `${this.value} ${this.fromCurrency} = ${response.rates[this.toCurrency]} ${this.toCurrency}`);
return response;
}

Comment on lines +49 to +53
getCurrencies($ = this) {
return axios($, {
url: "https://api.frankfurter.app/currencies",
});
},
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add error handling for API requests.

The current implementation of getCurrencies doesn't handle potential API failures. Consider adding error handling:

getCurrencies($ = this) {
-  return axios($, {
-    url: "https://api.frankfurter.app/currencies",
-  });
+  return axios($, {
+    url: "https://api.frankfurter.app/currencies",
+  })
+    .catch(error => {
+      console.error("Error fetching currencies:", error);
+      throw new Error(`Failed to fetch currency list: ${error.message}`);
+    });
}

Similar error handling should be added to convertCurrency as well.

📝 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
getCurrencies($ = this) {
return axios($, {
url: "https://api.frankfurter.app/currencies",
});
},
getCurrencies($ = this) {
return axios($, {
url: "https://api.frankfurter.app/currencies",
})
.catch(error => {
console.error("Error fetching currencies:", error);
throw new Error(`Failed to fetch currency list: ${error.message}`);
});
},

Comment on lines +28 to +32
value: {
type: "string",
label: "Value",
description: "The value to convert",
},
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Validate currency value is a number.

The value property is defined as a string type, but should be a valid number for currency conversion. Consider adding validation or changing the type:

value: {
-  type: "string",
+  type: "integer",
  label: "Value",
  description: "The value to convert",
+  validate: (value) => {
+    const num = Number(value);
+    if (isNaN(num)) {
+      return "Value must be a valid number";
+    }
+    return true;
+  },
},
📝 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
value: {
type: "string",
label: "Value",
description: "The value to convert",
},
value: {
type: "integer",
label: "Value",
description: "The value to convert",
validate: (value) => {
const num = Number(value);
if (isNaN(num)) {
return "Value must be a valid number";
}
return true;
},
},

Copy link
Contributor

@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: 4

♻️ Duplicate comments (1)
components/pipedream_utils/actions/format-currency/format-currency.mjs (1)

58-69: ⚠️ Potential issue

Fix the default clause position in switch statement

The default clause should be the last switch clause according to best practices. This was also flagged by the static analysis tool.

  switch (currencyFormat.match(/¤+$/g)?.[0].length) {
-  default:
-    break;
    // ¤¤ - ISO currency symbol: USD, BRL, etc.
  case 2:
    result += ` ${isoCode}`;
    break;
    // ¤¤¤ - Currency display name: United States dollar, Brazilian real, etc.
  case 3:
    result += ` ${currencyName}`;
    break;
+  default:
+    break;
  }
🧰 Tools
🪛 Biome (1.9.4)

[error] 59-61: The default clause should be the last switch clause.

The following case clause is here:

Regardless its position, the default clause is always executed when there is no match. To avoid confusion, the default clause should be the last switch clause.

(lint/suspicious/useDefaultSwitchClauseLast)

🧹 Nitpick comments (9)
components/pipedream_utils/actions/replace-text/replace-text.mjs (5)

33-38: Consider using a more robust method for RegExp detection.

The current approach of checking if findText starts with a slash is a simplistic way to determine if the input is a regular expression. This may not handle all valid regex patterns correctly. Additionally, always adding the 'g' flag may not be appropriate for all use cases.

-    const isRegExp = findText.startsWith("/");
-    const expression = isRegExp
-      ? buildRegExp(findText, [
-        "g",
-      ])
-      : findText;
+    let isRegExp = false;
+    let expression = findText;
+    
+    // Check if input is intended to be a regular expression
+    if (findText.startsWith("/")) {
+      try {
+        expression = buildRegExp(findText, ["g"]);
+        isRegExp = true;
+      } catch (error) {
+        // Fallback to literal string if regex construction fails
+        $.export("$summary", `Warning: Invalid regular expression format. Treating as literal text.`);
+      }
+    }

40-50: Optimize the replacement logic for better performance.

The current implementation uses a loop with match and replace for non-regex replacements, which can be inefficient for large inputs. The comment indicates replaceAll isn't supported in Node 14.x, but if newer Node versions are supported, this could be simplified.

-    // replaceAll would be optimal, but it is not supported in Node 14.x
-    let result = input.replace(expression, replaceValue);
-    if (!isRegExp) {
-      let counter = 0;
-      while (result.match(expression)) {
-        result = result.replace(expression, replaceValue);
-        if (++counter > MAX_REPLACES) {
-          throw new Error(`Over ${MAX_REPLACES} replace operations reached - please check your inputs and, if this is expected, search with a regular expression instead of text`);
-        }
-      }
-    }
+    // Use a more efficient approach for text replacement
+    let result;
+    if (isRegExp) {
+      result = input.replace(expression, replaceValue);
+    } else {
+      // For newer Node versions, use replaceAll if available
+      if (typeof input.replaceAll === 'function') {
+        result = input.replaceAll(findText, replaceValue);
+      } else {
+        // Fallback for Node 14.x
+        result = input.replace(new RegExp(findText.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g'), replaceValue);
+      }
+    }

51-58: Improve the match counting logic.

The current match counting logic mixes boolean and text values in a potentially confusing way. It would be clearer to count actual replacements uniformly for both regex and non-regex scenarios.

-    const matchText = isRegExp
-      ? [
-        ...input.matchAll(expression),
-      ].length
-      : input.includes(findText) && "text";
-    $.export("$summary", matchText
-      ? `Successfully replaced ${matchText} matches`
-      : "No matches found. Input was not modified");
+    let matchCount = 0;
+    if (isRegExp) {
+      matchCount = [...input.matchAll(expression)].length;
+    } else {
+      // Count occurrences of findText in input
+      let pos = input.indexOf(findText);
+      while (pos !== -1) {
+        matchCount++;
+        pos = input.indexOf(findText, pos + findText.length);
+      }
+    }
+    
+    $.export("$summary", matchCount > 0
+      ? `Successfully replaced ${matchCount} matches`
+      : "No matches found. Input was not modified");

47-47: Enhance the error message to provide better guidance.

When suggesting users switch to regular expressions, the error message could be more helpful by providing an example of how to format their input as a regex.

-          throw new Error(`Over ${MAX_REPLACES} replace operations reached - please check your inputs and, if this is expected, search with a regular expression instead of text`);
+          throw new Error(`Over ${MAX_REPLACES} replace operations reached - please check your inputs. If this is expected, use a regular expression instead (e.g., "/your-text-here/g").`);

19-19: Enhance the regex pattern description.

The description provides a link to learn about regular expressions but could be more helpful by including basic examples of common regex patterns users might need.

-      description: "The text you would like to search for and replace. This can also be a string representing a [Regular Expression](https://www.w3schools.com/js/js_regexp.asp)",
+      description: "The text you would like to search for and replace. This can also be a string representing a [Regular Expression](https://www.w3schools.com/js/js_regexp.asp). Examples: '/pattern/i' (case-insensitive), '/\\d+/g' (all numbers).",
components/pipedream_utils/actions/format-number/format-number.mjs (1)

38-38: Consider trimming the input to handle whitespace

The input is converted to a string but not trimmed, which might cause issues if it contains leading or trailing whitespace.

- const input = this.input.toString();
+ const input = this.input.toString().trim();
components/pipedream_utils/actions/format-currency/format-currency.mjs (3)

52-56: Simplify decimal part handling logic

The current implementation for handling the decimal part is complex and can be simplified for better readability.

- const numberString = formatNumber(integer, (decimal?.length > 1
-   ? decimal
-   : (decimal ?? "0") + "0"), currencyFormat.includes(",")
-   ? ","
-   : "");
+ let decimalPart = decimal ?? "0";
+ // Ensure at least 2 decimal places for currency
+ if (decimalPart.length < 2) {
+   decimalPart += "0";
+ }
+ const groupingSeparator = currencyFormat.includes(",") ? "," : "";
+ const numberString = formatNumber(integer, decimalPart, groupingSeparator, ".");

36-36: Consider trimming the input to handle whitespace

The input is converted to a string but not trimmed, which might cause issues if it contains leading or trailing whitespace.

- const input = this.input.toString();
+ const input = this.input.toString().trim();

42-42: Be explicit about the initial result value

The current code has a complex conditional expression to set the initial result value. This could be made more readable with an explicit if statement.

- let result = (currencyFormat.startsWith("¤") && currencySymbol) || "";
+ let result = "";
+ if (currencyFormat.startsWith("¤") && currencySymbol) {
+   result = currencySymbol;
+ }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 11c813c and 6856cc4.

📒 Files selected for processing (22)
  • components/pipedream_utils/actions/add-subtract-time/add-subtract-time.mjs (1 hunks)
  • components/pipedream_utils/actions/compare-dates/compare-dates.mjs (1 hunks)
  • components/pipedream_utils/actions/convert-html-to-markdown/convert-html-to-markdown.mjs (1 hunks)
  • components/pipedream_utils/actions/convert-html-to-text/convert-html-to-text.mjs (1 hunks)
  • components/pipedream_utils/actions/convert-json-to-string/convert-json-to-string.mjs (1 hunks)
  • components/pipedream_utils/actions/convert-markdown-to-html/convert-markdown-to-html.mjs (1 hunks)
  • components/pipedream_utils/actions/date-time-format/date-time-format.mjs (1 hunks)
  • components/pipedream_utils/actions/extract-by-regular-expression/extract-by-regular-expression.mjs (1 hunks)
  • components/pipedream_utils/actions/extract-email-address/extract-email-address.mjs (1 hunks)
  • components/pipedream_utils/actions/extract-number/extract-number.mjs (1 hunks)
  • components/pipedream_utils/actions/extract-phone-number/extract-phone-number.mjs (1 hunks)
  • components/pipedream_utils/actions/extract-url/extract-url.mjs (1 hunks)
  • components/pipedream_utils/actions/format-currency/format-currency.mjs (1 hunks)
  • components/pipedream_utils/actions/format-number/format-number.mjs (1 hunks)
  • components/pipedream_utils/actions/parse-json/parse-json.mjs (1 hunks)
  • components/pipedream_utils/actions/replace-text/replace-text.mjs (1 hunks)
  • components/pipedream_utils/actions/set-default-value/set-default-value.mjs (1 hunks)
  • components/pipedream_utils/actions/split-text/split-text.mjs (1 hunks)
  • components/pipedream_utils/actions/transform-case/transform-case.mjs (1 hunks)
  • components/pipedream_utils/actions/trim-whitespace/trim-whitespace.mjs (1 hunks)
  • components/pipedream_utils/actions/url-decode/url-decode.mjs (1 hunks)
  • components/pipedream_utils/actions/url-encode/url-encode.mjs (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (19)
  • components/pipedream_utils/actions/date-time-format/date-time-format.mjs
  • components/pipedream_utils/actions/convert-html-to-text/convert-html-to-text.mjs
  • components/pipedream_utils/actions/convert-markdown-to-html/convert-markdown-to-html.mjs
  • components/pipedream_utils/actions/trim-whitespace/trim-whitespace.mjs
  • components/pipedream_utils/actions/url-encode/url-encode.mjs
  • components/pipedream_utils/actions/extract-url/extract-url.mjs
  • components/pipedream_utils/actions/extract-number/extract-number.mjs
  • components/pipedream_utils/actions/extract-email-address/extract-email-address.mjs
  • components/pipedream_utils/actions/extract-phone-number/extract-phone-number.mjs
  • components/pipedream_utils/actions/url-decode/url-decode.mjs
  • components/pipedream_utils/actions/set-default-value/set-default-value.mjs
  • components/pipedream_utils/actions/split-text/split-text.mjs
  • components/pipedream_utils/actions/convert-html-to-markdown/convert-html-to-markdown.mjs
  • components/pipedream_utils/actions/add-subtract-time/add-subtract-time.mjs
  • components/pipedream_utils/actions/transform-case/transform-case.mjs
  • components/pipedream_utils/actions/compare-dates/compare-dates.mjs
  • components/pipedream_utils/actions/convert-json-to-string/convert-json-to-string.mjs
  • components/pipedream_utils/actions/parse-json/parse-json.mjs
  • components/pipedream_utils/actions/extract-by-regular-expression/extract-by-regular-expression.mjs
🧰 Additional context used
🪛 Biome (1.9.4)
components/pipedream_utils/actions/format-currency/format-currency.mjs

[error] 59-61: The default clause should be the last switch clause.

The following case clause is here:

Regardless its position, the default clause is always executed when there is no match. To avoid confusion, the default clause should be the last switch clause.

(lint/suspicious/useDefaultSwitchClauseLast)

⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: Verify TypeScript components
  • GitHub Check: Publish TypeScript components
🔇 Additional comments (2)
components/pipedream_utils/actions/replace-text/replace-text.mjs (2)

1-3: The MAX_REPLACES constant is well-defined.

Setting a maximum number of replacements is a good safeguard against potential infinite loops or performance issues with large inputs. This is especially important in a cloud environment where resources may be constrained.


10-28: Props are well-structured with clear descriptions.

The component properties are well-defined with appropriate labels, descriptions, and types. Making the replacement text optional with a clear indication that it defaults to deleting the found text provides good flexibility for users.

name: "Formatting - [Text] Replace Text",
description: "Replace all instances of any character, word or phrase in the text with another character, word or phrase.",
key: "pipedream_utils-replace-text",
version: "0.0.5",
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Version number indicates early development.

The component version is set to "0.0.5", which suggests it might be in early development. Consider clarifying the versioning strategy or incrementing to a more appropriate version if this component is ready for production use.


🌐 Web query:

What are best practices for semantic versioning in npm packages?

💡 Result:

Semantic versioning (SemVer) in npm packages ensures predictable dependency management by communicating the impact of changes through version numbers (MAJOR.MINOR.PATCH). Here are key best practices:


Core Principles

  • MAJOR for breaking API changes (e.g., 1.0.02.0.0) [2][4].
  • MINOR for backward-compatible features (e.g., 1.0.01.1.0) [2][5].
  • PATCH for backward-compatible bug fixes (e.g., 1.0.01.0.1) [2][4].
  • Start production-ready packages at 1.0.0 [5][4].

Version Ranges & Symbols

Use version specifiers to control updates:

Symbol Rule Example (Range ~1.2.3)
^ Allow minor/patch updates ^1.2.31.3.0, 1.2.4 [2][8].
~ Allow only patch updates ~1.2.31.2.4 (not 1.3.0) [2][8].
= Pin to exact version =1.2.3 → No updates [2].

Example Dependency Entry:

"dependencies": {
  "lodash": "^4.17.21",  // Allows minor/patch updates
  "react": "~18.2.0"     // Allows only patch updates
}

Pre-Release Handling

  • Use pre-release tags (e.g., 1.0.0-alpha.1) instead of custom distribution tags (beta, latest) to avoid accidental breaking changes [1][3].
  • Automate tagging in CI/CD:
    npm version prerelease --preid=beta  # Bumps to 1.0.0-beta.0
    npm publish --tag beta               # Publishes under beta tag

Automation & Workflow

  • Script Integration: Automate version bumps and publishing:
    # Bump patch version, publish, and push Git tags
    npm version patch && npm publish && git push --follow-tags
  • CI/CD: Use tools to auto-increment versions and run tests before release [1][3].

Avoid Common Pitfalls

  • Never mutate published versions: Use immutable tags like -rc.1 instead of reassigning beta [1].
  • Lock dependencies: Use package-lock.json to ensure reproducible builds [6][8].
  • Update frequently: Regularly run npm update to stay current with non-breaking changes [6].

Organizational Standards

  • Define team-wide scopes (e.g., @myorg/utils) to group related packages [1].
  • Enforce consistent metadata in package.json (author, license, repository) [1][5].

By following these practices, teams reduce dependency conflicts, streamline releases, and maintain trust in package updates.

Citations:


Action Required: Update Version Number for Production Readiness

  • File: components/pipedream_utils/actions/replace-text/replace-text.mjs (Line 8)
  • Issue: The current version "0.0.5" suggests that the component is still in early development.
  • Recommendation: According to semantic versioning best practices, production-ready packages should start at 1.0.0. If this component is intended for production, please update the version number accordingly. Alternatively, if it remains under active early development, consider adding documentation that clearly outlines your versioning strategy to avoid confusion.

Comment on lines +34 to +43
async run({ $ }) {
const {
inputDecimalMark, toFormat,
} = this;
const input = this.input.toString();
const decimalMark = inputDecimalMark ?? ".";
const splitInput = input.split(decimalMark);
if (splitInput.length > 2) {
throw new ConfigurationError(`Input has more than one decimal mark (\`${decimalMark}\`). Check if the \`Input Decimal Mark\` prop is set correctly.`);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add proper number validation to prevent invalid inputs

The current code only validates if the input has more than one decimal mark, but it doesn't check if the input is actually a valid number. You should add validation similar to what's implemented in the format-currency.mjs file.

  const input = this.input.toString();
  const decimalMark = inputDecimalMark ?? ".";
  const splitInput = input.split(decimalMark);
  if (splitInput.length > 2) {
    throw new ConfigurationError(`Input has more than one decimal mark (\`${decimalMark}\`). Check if the \`Input Decimal Mark\` prop is set correctly.`);
  }
+ const [integer, decimal] = splitInput;
+ if (isNaN(Number(integer))) {
+   throw new ConfigurationError("**Invalid number** - please check your input.");
+ }
-  const [
-    integer,
-    decimal,
-  ] = splitInput;

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +48 to +52
const [
groupChar,
decimalChar,
] = toFormat.split("");
const result = formatNumber(integer, decimal, groupChar, decimalChar);
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Add validation for the toFormat property

The code assumes that toFormat will always have at least two characters, but there's no validation to handle cases where toFormat might be too short.

  const [
    groupChar,
    decimalChar,
+  ] = toFormat.split("").length >= 2 ? toFormat.split("") : ["", ""];
-  ] = toFormat.split("");
  
  const result = formatNumber(integer, decimal, groupChar, decimalChar);

Run this script to verify how toFormat values are defined and used across the codebase:


🏁 Script executed:

#!/bin/bash
# Check the possible values of toFormat and their structure
grep -r "FINAL_FORMAT_OPTIONS" --include="*.mjs" .
cat $(find . -name "numberFormattingOptions.mjs")

Length of output: 995


Action Required: Add Validation for toFormat

The code currently assumes that toFormat always contains at least two characters. Even though our defined options in FINAL_FORMAT_OPTIONS (in components/pipedream_utils/common/numbers/numberFormattingOptions.mjs) always provide two-character strings, we should not rely solely on that assumption. To safeguard against any unexpected or dynamic inputs, please add proper validation as suggested below.

  • Location: components/pipedream_utils/actions/format-number/format-number.mjs (Lines 48-52)
  • Proposed Change: Validate that splitting toFormat yields at least two characters before destructuring; otherwise, fall back to a safe default.
  • Diff:
      const splitFormat = toFormat.split("");
  • const [groupChar, decimalChar] = splitFormat;
  • const [groupChar, decimalChar] = splitFormat.length >= 2 ? splitFormat : ["", ""];

    const result = formatNumber(integer, decimal, groupChar, decimalChar);




<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **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.

`````suggestion
  const splitFormat = toFormat.split("");
  const [groupChar, decimalChar] = splitFormat.length >= 2 ? splitFormat : ["", ""];
  const result = formatNumber(integer, decimal, groupChar, decimalChar);

isoCode,
currencySymbol,
currencyName,
] = currency.split(" - ");
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add validation for currency format

The code assumes the currency string will always split into three parts with " - " as the separator, but there's no validation to handle cases where the format might be different.

  const [
    isoCode,
    currencySymbol,
    currencyName,
  ] = currency.split(" - ");
+ 
+ if (!isoCode || !currencySymbol || !currencyName) {
+   throw new ConfigurationError("Invalid currency format. Expected format: 'ISO - Symbol - Name'");
+ }
📝 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
] = currency.split(" - ");
const [
isoCode,
currencySymbol,
currencyName,
] = currency.split(" - ");
if (!isoCode || !currencySymbol || !currencyName) {
throw new ConfigurationError("Invalid currency format. Expected format: 'ISO - Symbol - Name'");
}

Copy link
Contributor

@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: 1

🧹 Nitpick comments (6)
components/pipedream_utils/common/date-time/commonDateTime.mjs (6)

24-39: Improve error handling and input validation in getDateFromInput method

The current error handling captures all exceptions and treats them as date parsing errors. Consider validating inputs before processing and providing more specific error handling.

 getDateFromInput(date = this.inputDate, format = this.inputFormat) {
   let dateObj;
   try {
+    if (!date) {
+      throw new ConfigurationError("Input date is empty or undefined");
+    }
     const inputFn = DATE_FORMAT_PARSE_MAP.get(format)?.inputFn ??
                 DEFAULT_INPUT_FUNCTION;
     dateObj = inputFn(date);
     if (isNaN(dateObj.getFullYear()))
       throw new Error("Invalid date");
   }
   catch (err) {
+    if (err instanceof ConfigurationError) {
+      throw err;
+    }
     throw new ConfigurationError(`**Error** parsing input \`${date}\` ${format
       ? `expecting specified format \`${format}\``
       : "- try selecting a format in the **Input Format** prop."}`);
   }
   return dateObj;
 },

27-28: Simplify optional chaining and nullish coalescing expressions

The current syntax spans two lines and may reduce readability. Consider refactoring to a more concise format.

-        const inputFn = DATE_FORMAT_PARSE_MAP.get(format)?.inputFn ??
-                    DEFAULT_INPUT_FUNCTION;
+        const inputFn = DATE_FORMAT_PARSE_MAP.get(format)?.inputFn ?? DEFAULT_INPUT_FUNCTION;

4-5: Consider adding type or interface documentation for imported utilities

Adding a brief comment about the structure and purpose of DATE_FORMAT_PARSE_MAP and DEFAULT_INPUT_FUNCTION would improve code readability and maintenance.

 import {
-  DATE_FORMAT_PARSE_MAP, DEFAULT_INPUT_FUNCTION,
+  // Map of format strings to their corresponding parsing functions
+  DATE_FORMAT_PARSE_MAP, 
+  // Default function for parsing dates when no specific format is provided
+  DEFAULT_INPUT_FUNCTION,
 } from "./dateFormats.mjs";

30-31: Provide more specific error message for invalid dates

The current error message "Invalid date" is generic. A more specific message would help users understand what went wrong.

-        if (isNaN(dateObj.getFullYear()))
-          throw new Error("Invalid date");
+        if (isNaN(dateObj.getFullYear())) {
+          throw new Error(`Invalid date: '${date}' could not be parsed as a valid date`);
+        }

34-36: Improve error message formatting for better readability

The current template string spans multiple lines with concatenation. Consider using a more readable format.

-        throw new ConfigurationError(`**Error** parsing input \`${date}\` ${format
-          ? `expecting specified format \`${format}\``
-          : "- try selecting a format in the **Input Format** prop."}`);
+        throw new ConfigurationError(
+          `**Error** parsing input \`${date}\` ${
+            format 
+              ? `expecting specified format \`${format}\`` 
+              : "- try selecting a format in the **Input Format** prop."
+          }`
+        );

6-41: Add JSDoc comments to improve documentation

Adding JSDoc comments to the exported object and its methods would improve code documentation and provide better editor support.

+/**
+ * Common date-time utilities for Pipedream
+ * @module commonDateTime
+ */
 export default {
   props: {
     pipedream_utils,
     inputDate: {
       description: "A valid date string, in the format selected in **Input Format**. If the format is not set, Pipedream will attempt to infer it using the parser from [Sugar Date library](https://sugarjs.com/dates/#/Parsing).",
       propDefinition: [
         pipedream_utils,
         "inputDate",
       ],
     },
     inputFormat: {
       propDefinition: [
         pipedream_utils,
         "inputFormat",
       ],
     },
   },
   methods: {
+    /**
+     * Parses a date string according to the specified format
+     * @param {string} [date=this.inputDate] - The date string to parse
+     * @param {string} [format=this.inputFormat] - The format to use for parsing
+     * @returns {Date} A JavaScript Date object
+     * @throws {ConfigurationError} If the date cannot be parsed
+     */
     getDateFromInput(date = this.inputDate, format = this.inputFormat) {
       // Method implementation...
     },
   },
 };
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6856cc4 and c766b04.

📒 Files selected for processing (1)
  • components/pipedream_utils/common/date-time/commonDateTime.mjs (1 hunks)
🧰 Additional context used
🪛 GitHub Actions: Pull Request Checks
components/pipedream_utils/common/date-time/commonDateTime.mjs

[error] 1-1: Error [ERR_MODULE_NOT_FOUND]: Cannot find module '/home/runner/work/pipedream/pipedream/components/pipedream_utils/app/pipedream_utils.app.mjs' imported from /home/runner/work/pipedream/pipedream/components/pipedream_utils/common/date-time/commonDateTime.mjs

⏰ Context from checks skipped due to timeout of 90000ms (3)
  • GitHub Check: pnpm publish
  • GitHub Check: Verify TypeScript components
  • GitHub Check: Publish TypeScript components

@@ -0,0 +1,41 @@
import pipedream_utils from "../../app/pipedream_utils.app.mjs";
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix the module import path for pipedream_utils

The import path "../../app/pipedream_utils.app.mjs" is causing a build failure as shown in the pipeline logs. The module cannot be found at this location.

-import pipedream_utils from "../../app/pipedream_utils.app.mjs";
+import pipedream_utils from "../../../pipedream_utils/app/pipedream_utils.app.mjs";

The path should be adjusted to match the actual location of the module in the repository structure.

📝 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
import pipedream_utils from "../../app/pipedream_utils.app.mjs";
import pipedream_utils from "../../../pipedream_utils/app/pipedream_utils.app.mjs";
🧰 Tools
🪛 GitHub Actions: Pull Request Checks

[error] 1-1: Error [ERR_MODULE_NOT_FOUND]: Cannot find module '/home/runner/work/pipedream/pipedream/components/pipedream_utils/app/pipedream_utils.app.mjs' imported from /home/runner/work/pipedream/pipedream/components/pipedream_utils/common/date-time/commonDateTime.mjs

Copy link
Contributor

@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: 0

🧹 Nitpick comments (1)
components/pipedream_utils/package.json (1)

15-33: New Dependency Additions

A new "dependencies" section has been introduced with a comprehensive list of packages (including @pipedream/platform, csv-parse, html-to-text, linkedom, and others). Verify that:

  • Each dependency is necessary for the expanded functionality of the Pipedream Utils components.
  • The version constraints using caret (^) align with your stability and upgrade policies.
  • Any related documentation (like updates in README.md) reflects these new dependencies.
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ce66b9b and 127466d.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (1)
  • components/pipedream_utils/package.json (2 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: Verify TypeScript components
  • GitHub Check: Publish TypeScript components
🔇 Additional comments (2)
components/pipedream_utils/package.json (2)

3-3: Version Bump Update

The version has been updated to "0.0.2". Ensure that this version increment aligns with your release notes and versioning strategy.


12-14: PublishConfig Formatting Verification

The "publishConfig" block now explicitly sets "access": "public". Confirm that this configuration meets your publishing requirements and that no unintended formatting changes have been applied.

@andrewjschuang andrewjschuang merged commit cad7b84 into master Mar 14, 2025
11 checks passed
@andrewjschuang andrewjschuang deleted the pipedream-utils-components branch March 14, 2025 17:56
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