Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 10 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,23 +24,25 @@ plugins:

| Function | Description |
|--|--|
| [lower][lower] | Converts a string to its lowercase representation |
| [upper][upper] | Converts a string to its uppercase representation |
| [ternary][ternary] | Performs equality check and returns a defined result |
| [capitalize][capitalize] | Converts a string to its Titlecase representation |
| [join][join] | Joins a collection of values with a given delimiter |
| [lower][lower] | Converts a string to its lowercase representation |
| [replace][replace] | Replaces occurrences using plain strings or regex patterns |
| [split][split] | Splits a string value on a given delimiter |
| [switch][switch] | Performs switch-statement lookups |
| [capitalize][capitalize] | Converts a string to its Titlecase representation |
| [ternary][ternary] | Performs equality check and returns a defined result |
| [upper][upper] | Converts a string to its uppercase representation |


[link-download]: https://img.shields.io/npm/dt/serverless-plugin-utils.svg
[link-version]: https://img.shields.io/npm/v/serverless-plugin-utils.svg
[link-license]: https://img.shields.io/npm/l/serverless-plugin-utils.svg

[lower]: https://github.com/icarus-sullivan/serverless-plugin-utils/blob/master/documentation/lower.md
[upper]: https://github.com/icarus-sullivan/serverless-plugin-utils/blob/master/documentation/upper.md
[capitalize]: https://github.com/icarus-sullivan/serverless-plugin-utils/blob/master/documentation/capitalize.md
[join]: https://github.com/icarus-sullivan/serverless-plugin-utils/blob/master/documentation/join.md
[lower]: https://github.com/icarus-sullivan/serverless-plugin-utils/blob/master/documentation/lower.md
[replace]: https://github.com/icarus-sullivan/serverless-plugin-utils/blob/master/documentation/replace.md
[split]: https://github.com/icarus-sullivan/serverless-plugin-utils/blob/master/documentation/split.md
[ternary]: https://github.com/icarus-sullivan/serverless-plugin-utils/blob/master/documentation/ternary.md
[switch]: https://github.com/icarus-sullivan/serverless-plugin-utils/blob/master/documentation/switch.md
[capitalize]: https://github.com/icarus-sullivan/serverless-plugin-utils/blob/master/documentation/capitalize.md
[ternary]: https://github.com/icarus-sullivan/serverless-plugin-utils/blob/master/documentation/ternary.md
[upper]: https://github.com/icarus-sullivan/serverless-plugin-utils/blob/master/documentation/upper.md
52 changes: 52 additions & 0 deletions documentation/replace.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
[Home](https://github.com/icarus-sullivan/serverless-plugin-utils/blob/master/README.md)

# replace
Replaces occurrences of a search pattern with a replacement string in a given string. Supports both plain strings and regular expression patterns.

### Usage
```
varName: ${replace(string, searchPattern, replacementString)}
```

The `searchPattern` can be either:
- A plain string (replaces first occurrence only - native JavaScript behavior)
- A regex pattern using Node.js syntax like `/pattern/flags` (for global replacement, use `/pattern/g`)

### Examples

#### Plain String Replacement:
```
service: MyApp

provider:
stage: ${opt:stage, 'dev'}

custom:
serviceName: ${replace(${self:service}, 'App', 'Service')}
# Note: For multiple slashes, use regex: ${replace(${opt:branch, 'main'}, '/\//g', '-')}
cleanBranch: ${replace(${opt:branch, 'main'}, '/', '-')}
```

#### Regular Expression Patterns:
```
custom:
# Case-insensitive replacement
normalizedName: ${replace('Hello WORLD and world', '/world/gi', 'serverless')}

# Replace only first occurrence
firstOnly: ${replace('test-test-test', '/test/', 'demo')}

# Global replacement with regex
allMatches: ${replace('foo123bar456', '/[0-9]+/g', 'XXX')}
```

### Outputs:

| Example | Input | Pattern | Replacement | Output |
|--|--|--|--|--|
| Plain string | MyApp | 'App' | 'Service' | MyService |
| Plain string (first only) | foo-bar-foo | 'foo' | 'baz' | baz-bar-foo |
| Regex (case-insensitive) | Hello WORLD and world | '/world/gi' | 'serverless' | Hello serverless and serverless |
| Regex (first only) | test-test-test | '/test/' | 'demo' | demo-test-test |
| Regex (global) | test-test-test | '/test/g' | 'demo' | demo-demo-demo |
| Regex (global numbers) | foo123bar456 | '/[0-9]+/g' | 'XXX' | fooXXXbarXXX |
110 changes: 76 additions & 34 deletions spec/index.test.js
Original file line number Diff line number Diff line change
@@ -1,95 +1,137 @@
const join = require('../src/utils/join');
const split = require('../src/utils/split');
const ternary = require('../src/utils/ternary');
const lower = require('../src/utils/lower');
const upper = require('../src/utils/upper');
const capitalize = require('../src/utils/capitalize');
const switchFn = require('../src/utils/switch');
const join = require("../src/utils/join");
const split = require("../src/utils/split");
const ternary = require("../src/utils/ternary");
const lower = require("../src/utils/lower");
const upper = require("../src/utils/upper");
const capitalize = require("../src/utils/capitalize");
const switchFn = require("../src/utils/switch");
const replace = require("../src/utils/replace");

test('join', () => {
test("join", () => {
const result = join({
params: ['one', 'two', 'three', '-'],
params: ["one", "two", "three", "-"],
});
expect(result).toMatchObject({
value: 'one-two-three',
value: "one-two-three",
});
});

test('split', () => {
test("split", () => {
const result = split({
params: ['foo-bar-example', '-'],
params: ["foo-bar-example", "-"],
});
expect(result).toMatchObject({
value: ['foo', 'bar', 'example'],
value: ["foo", "bar", "example"],
});

const result2 = split({
params: ['foo-bar-example', '-', 0],
params: ["foo-bar-example", "-", 0],
});
expect(result2).toMatchObject({
value: 'foo',
value: "foo",
});
});

test('ternary', () => {
test("ternary", () => {
const result = ternary({
params: ['prod', 'prod', true, false],
params: ["prod", "prod", true, false],
});
expect(result).toMatchObject({
value: true,
});

const result2 = ternary({
params: ['prod', 'beta', true, false],
params: ["prod", "beta", true, false],
});
expect(result2).toMatchObject({
value: false,
});
});

test('lower', () => {
test("lower", () => {
const result = lower({
params: ['DTesjf3'],
params: ["DTesjf3"],
});
expect(result).toMatchObject({
value: 'dtesjf3',
value: "dtesjf3",
});
});

test('upper', () => {
test("upper", () => {
const result = upper({
params: ['l38gt1'],
params: ["l38gt1"],
});
expect(result).toMatchObject({
value: 'L38GT1',
value: "L38GT1",
});
});

test('capitalize', () => {
test("capitalize", () => {
const result = capitalize({
params: ['l38gt1'],
params: ["l38gt1"],
});
expect(result).toMatchObject({
value: 'L38gt1',
value: "L38gt1",
});
});

test('switch', () => {
test("switch", () => {
const cases = {
foo: 'awesome',
'*': 'nope',
foo: "awesome",
"*": "nope",
};
const result = switchFn({
params: ['foo', cases],
params: ["foo", cases],
});
expect(result).toMatchObject({
value: 'awesome',
value: "awesome",
});

const result2 = switchFn({
params: ['bar', cases],
params: ["bar", cases],
});
expect(result2).toMatchObject({
value: 'nope',
value: "nope",
});
});

test("replace", () => {
// Test with plain string (replaces first occurrence only - native JS behavior)
const result = replace({
params: ["Hello world!", "world", "serverless"],
});
expect(result).toMatchObject({
value: "Hello serverless!",
});

const result2 = replace({
params: ["foo-bar-foo", "foo", "baz"],
});
expect(result2).toMatchObject({
value: "baz-bar-foo", // Only first 'foo' is replaced
});

// Test with regex pattern - global flag
const result3 = replace({
params: ["Hello World and WORLD!", "/world/gi", "serverless"],
});
expect(result3).toMatchObject({
value: "Hello serverless and serverless!",
});

// Test with regex pattern - case sensitive, first occurrence only
const result4 = replace({
params: ["foo-bar-foo-BAR", "/foo/g", "fiz"],
});
expect(result4).toMatchObject({
value: "fiz-bar-fiz-BAR",
});

// Test with regex pattern - case insensitive
const result5 = replace({
params: ["foo-bar-foo-BAR", "/bar/gi", "biz"],
});
expect(result5).toMatchObject({
value: "foo-biz-foo-biz",
});
});
3 changes: 3 additions & 0 deletions src/utils/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,7 @@ module.exports = {
capitalize: {
resolve: require('./capitalize'),
},
replace: {
resolve: require('./replace'),
},
};
24 changes: 24 additions & 0 deletions src/utils/replace.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
module.exports = ({ params }) => {
if (params.length < 3) {
throw new Error('Missing params for function replace. Expected: string, searchPattern, replacementString');
}

const [string, searchPattern, replacementString] = params;

// Check if searchPattern is a regex (starts with / and ends with /flags)
const regexMatch = searchPattern.match(/^\/(.+?)\/([gimsy]*)$/);
if (regexMatch) {
// It's a regex pattern, create RegExp object
const pattern = regexMatch[1];
const flags = regexMatch[2];
const searchRegex = new RegExp(pattern, flags);
return {
value: `${string}`.replace(searchRegex, replacementString),
};
} else {
// Treat as plain string - replaces first occurrence only (native JS behavior)
return {
value: `${string}`.replace(searchPattern, replacementString),
};
}
};