Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: allow date updating in generic strategy #2440

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
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
2 changes: 2 additions & 0 deletions __snapshots__/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,8 @@ Options:
--latest-tag-sha Override the detected latest tag SHA[string]
--latest-tag-name Override the detected latest tag name
[string]
--date-format format in strftime format for updating dates
[default: "string"]
--label comma-separated list of labels to add to
from release PR
[default: "autorelease: pending"]
Expand Down
12 changes: 12 additions & 0 deletions __snapshots__/generic.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,15 @@ public final class Version {
public static String VERSION = "2.3.4";
// {x-release-please-end}

// {x-release-please-start-date}
public static String DATE = "01-12-2023";
// {x-release-please-end}

// {x-release-please-start-version-date}
public static String NEW_DATE = "01-12-2023";
public static String NEW_VERSION = "2.3.4";
// {x-release-please-end}

// {x-release-please-start-major}
public static String MAJOR = "2";
// {x-release-please-end}
Expand All @@ -37,6 +46,9 @@ public final class Version {
public static String INLINE_MAJOR = "2"; // {x-release-please-major}
public static String INLINE_MINOR = "3"; // {x-release-please-minor}
public static String INLINE_PATCH = "4"; // {x-release-please-patch}

public static String RELEASE_DATE = "01-12-2023"; // {x-release-please-date}
public static String RELEASE_INFO = "v2.3.4 01-12-2023"; // {x-release-please-version-date}
}

`
5 changes: 5 additions & 0 deletions schemas/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,10 @@
"description": "Customize the separator between the component and version in the GitHub tag.",
"type": "string"
},
"date-format": {
"description": "Date format given as a strftime expression for the generic strategy.",
"type": "string"
},
"extra-files": {
"description": "Specify extra generic files to replace versions.",
"type": "array",
Expand Down Expand Up @@ -476,6 +480,7 @@
"separate-pull-requests": true,
"always-update": true,
"tag-separator": true,
"date-format": true,
"extra-files": true,
"version-file": true,
"snapshot-label": true,
Expand Down
4 changes: 4 additions & 0 deletions src/bin/release-please.ts
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,10 @@ function pullRequestStrategyOptions(yargs: yargs.Argv): yargs.Argv {
describe: 'Override the detected latest tag name',
type: 'string',
})
.option('date-format', {
describe: 'format in strftime format for updating dates',
default: 'string',
})
.middleware(_argv => {
const argv = _argv as CreatePullRequestArgs;

Expand Down
5 changes: 5 additions & 0 deletions src/manifest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ export interface ReleaserConfig {
releaseLabels?: string[];
extraLabels?: string[];
initialVersion?: string;
dateFormat?: string;

// Changelog options
changelogSections?: ChangelogSection[];
Expand Down Expand Up @@ -183,6 +184,7 @@ interface ReleaserConfigJson {
'skip-snapshot'?: boolean; // Java-only
'initial-version'?: string;
'exclude-paths'?: string[]; // manifest-only
'date-format'?: string;
}

export interface ManifestOptions {
Expand All @@ -207,6 +209,7 @@ export interface ManifestOptions {
releaseSearchDepth?: number;
commitSearchDepth?: number;
logger?: Logger;
dateFormat?: string;
}

export interface ReleaserPackageConfig extends ReleaserConfigJson {
Expand Down Expand Up @@ -1397,6 +1400,7 @@ function extractReleaserConfig(
skipSnapshot: config['skip-snapshot'],
initialVersion: config['initial-version'],
excludePaths: config['exclude-paths'],
dateFormat: config['date-format'],
};
}

Expand Down Expand Up @@ -1755,6 +1759,7 @@ function mergeReleaserConfig(
initialVersion: pathConfig.initialVersion ?? defaultConfig.initialVersion,
extraLabels: pathConfig.extraLabels ?? defaultConfig.extraLabels,
excludePaths: pathConfig.excludePaths ?? defaultConfig.excludePaths,
dateFormat: pathConfig.dateFormat ?? defaultConfig.dateFormat,
};
}

Expand Down
30 changes: 22 additions & 8 deletions src/strategies/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ export interface BaseStrategyOptions {
logger?: Logger;
initialVersion?: string;
extraLabels?: string[];
dateFormat?: string;
}

/**
Expand Down Expand Up @@ -113,6 +114,7 @@ export abstract class BaseStrategy implements Strategy {
readonly componentNoSpace?: boolean;
readonly extraFiles: ExtraFile[];
readonly extraLabels: string[];
protected dateFormat: string;

readonly changelogNotes: ChangelogNotes;

Expand Down Expand Up @@ -148,6 +150,7 @@ export abstract class BaseStrategy implements Strategy {
this.extraFiles = options.extraFiles || [];
this.initialVersion = options.initialVersion;
this.extraLabels = options.extraLabels || [];
this.dateFormat = options.dateFormat || '%Y-%m-%d';
}

/**
Expand Down Expand Up @@ -330,7 +333,13 @@ export abstract class BaseStrategy implements Strategy {
commits: conventionalCommits,
});
const updatesWithExtras = mergeUpdates(
updates.concat(...(await this.extraFileUpdates(newVersion, versionsMap)))
updates.concat(
...(await this.extraFileUpdates(
newVersion,
versionsMap,
this.dateFormat
))
)
);
const pullRequestBody = await this.buildPullRequestBody(
component,
Expand Down Expand Up @@ -390,7 +399,8 @@ export abstract class BaseStrategy implements Strategy {

protected async extraFileUpdates(
version: Version,
versionsMap: VersionsMap
versionsMap: VersionsMap,
dateFormat: string
): Promise<Update[]> {
const extraFileUpdates: Update[] = [];
for (const extraFile of this.extraFiles) {
Expand All @@ -402,7 +412,11 @@ export abstract class BaseStrategy implements Strategy {
extraFileUpdates.push({
path: this.addPath(path),
createIfMissing: false,
updater: new Generic({version, versionsMap}),
updater: new Generic({
version,
versionsMap,
dateFormat: dateFormat,
}),
});
break;
case 'json':
Expand Down Expand Up @@ -454,7 +468,7 @@ export abstract class BaseStrategy implements Strategy {
createIfMissing: false,
updater: new CompositeUpdater(
new GenericJson('$.version', version),
new Generic({version, versionsMap})
new Generic({version, versionsMap, dateFormat: dateFormat})
),
});
} else if (extraFile.endsWith('.yaml') || extraFile.endsWith('.yml')) {
Expand All @@ -463,7 +477,7 @@ export abstract class BaseStrategy implements Strategy {
createIfMissing: false,
updater: new CompositeUpdater(
new GenericYaml('$.version', version),
new Generic({version, versionsMap})
new Generic({version, versionsMap, dateFormat: dateFormat})
),
});
} else if (extraFile.endsWith('.toml')) {
Expand All @@ -472,7 +486,7 @@ export abstract class BaseStrategy implements Strategy {
createIfMissing: false,
updater: new CompositeUpdater(
new GenericToml('$.version', version),
new Generic({version, versionsMap})
new Generic({version, versionsMap, dateFormat: dateFormat})
),
});
} else if (extraFile.endsWith('.xml')) {
Expand All @@ -482,14 +496,14 @@ export abstract class BaseStrategy implements Strategy {
updater: new CompositeUpdater(
// Updates "version" element that is a child of the root element.
new GenericXml('/*/version', version),
new Generic({version, versionsMap})
new Generic({version, versionsMap, dateFormat: dateFormat})
),
});
} else {
extraFileUpdates.push({
path: this.addPath(extraFile),
createIfMissing: false,
updater: new Generic({version, versionsMap}),
updater: new Generic({version, versionsMap, dateFormat: dateFormat}),
});
}
}
Expand Down
8 changes: 7 additions & 1 deletion src/strategies/java.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,13 @@ export class Java extends BaseStrategy {
commits: [],
});
const updatesWithExtras = mergeUpdates(
updates.concat(...(await this.extraFileUpdates(newVersion, versionsMap)))
updates.concat(
...(await this.extraFileUpdates(
newVersion,
versionsMap,
this.dateFormat
))
)
);
return {
title: pullRequestTitle,
Expand Down
111 changes: 103 additions & 8 deletions src/updaters/generic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,19 @@ const VERSION_REGEX =
/(?<major>\d+)\.(?<minor>\d+)\.(?<patch>\d+)(-(?<preRelease>[\w.]+))?(\+(?<build>[-\w.]+))?/;
const SINGLE_VERSION_REGEX = /\b\d+\b/;
const INLINE_UPDATE_REGEX =
/x-release-please-(?<scope>major|minor|patch|version)/;
/x-release-please-(?<scope>major|minor|patch|version-date|version|date)/;
const BLOCK_START_REGEX =
/x-release-please-start-(?<scope>major|minor|patch|version)/;
/x-release-please-start-(?<scope>major|minor|patch|version-date|version|date)/;
const BLOCK_END_REGEX = /x-release-please-end/;
const DATE_FORMAT_REGEX = /%[Ymd]/g;

type BlockScope = 'major' | 'minor' | 'patch' | 'version';
type BlockScope =
| 'major'
| 'minor'
| 'patch'
| 'version'
| 'date'
| 'version-date';

/**
* Options for the Generic updater.
Expand All @@ -34,6 +41,8 @@ export interface GenericUpdateOptions extends UpdateOptions {
inlineUpdateRegex?: RegExp;
blockStartRegex?: RegExp;
blockEndRegex?: RegExp;
date?: Date;
dateFormat?: string;
}

/**
Expand All @@ -52,24 +61,32 @@ export interface GenericUpdateOptions extends UpdateOptions {
* 4. `x-release-please-patch` if this string is found on the line,
* then replace an integer looking value with the next version's
* patch
* 5. `x-release-please-date` if this string is found on the line,
* then replace the date with the date of the last commit
* 6. `x-release-please-version-date` if this string is found on the line,
* then replace the both date and version
*
* You can also use a block-based replacement. Content between the
* opening `x-release-please-start-version` and `x-release-please-end` will
* be considered for version replacement. You can also open these blocks
* with `x-release-please-start-<major|minor|patch>` to replace single
* numbers
* with `x-release-please-start-<major|minor|patch|version-date>` to replace
* single numbers
*/
export class Generic extends DefaultUpdater {
private readonly inlineUpdateRegex: RegExp;
private readonly blockStartRegex: RegExp;
private readonly blockEndRegex: RegExp;
private readonly date: Date;
private readonly dateFormat: string;

constructor(options: GenericUpdateOptions) {
super(options);

this.inlineUpdateRegex = options.inlineUpdateRegex ?? INLINE_UPDATE_REGEX;
this.blockStartRegex = options.blockStartRegex ?? BLOCK_START_REGEX;
this.blockEndRegex = options.blockEndRegex ?? BLOCK_END_REGEX;
this.date = options.date ?? new Date();
this.dateFormat = options.dateFormat ?? '%Y-%m-%d';
}

/**
Expand All @@ -88,8 +105,33 @@ export class Generic extends DefaultUpdater {
const newLines: string[] = [];
let blockScope: BlockScope | undefined;

function replaceVersion(line: string, scope: BlockScope, version: Version) {
function replaceVersion(
line: string,
scope: BlockScope,
version: Version,
date: Date,
dateFormat: string
) {
const dateRegex = createDateRegex(dateFormat);
const formattedDate = formatDate(dateFormat, date);

switch (scope) {
case 'date':
if (isValidDate(formattedDate, dateFormat)) {
newLines.push(line.replace(dateRegex, formattedDate));
} else {
logger.warn(`Invalid date format: ${formattedDate}`);
newLines.push(line);
}
return;
case 'version-date':
if (isValidDate(formattedDate, dateFormat)) {
line = line.replace(dateRegex, formattedDate);
} else {
logger.warn(`Invalid date format: ${formattedDate}`);
}
newLines.push(line.replace(VERSION_REGEX, version.toString()));
return;
case 'major':
newLines.push(line.replace(SINGLE_VERSION_REGEX, `${version.major}`));
return;
Expand All @@ -115,11 +157,19 @@ export class Generic extends DefaultUpdater {
replaceVersion(
line,
(match.groups?.scope || 'version') as BlockScope,
this.version
this.version,
this.date,
this.dateFormat
);
} else if (blockScope) {
// in a block, so try to replace versions
replaceVersion(line, blockScope, this.version);
replaceVersion(
line,
blockScope,
this.version,
this.date,
this.dateFormat
);
if (line.match(this.blockEndRegex)) {
blockScope = undefined;
}
Expand All @@ -140,3 +190,48 @@ export class Generic extends DefaultUpdater {
return newLines.join('\n');
}
}

function createDateRegex(format: string): RegExp {
const regexString = format.replace(DATE_FORMAT_REGEX, match => {
switch (match) {
case '%Y':
return '(\\d{4})';
case '%m':
return '(\\d{2})';
case '%d':
return '(\\d{2})';
default:
return match;
}
});
return new RegExp(regexString);
}

function formatDate(format: string, date: Date): string {
return format.replace(DATE_FORMAT_REGEX, match => {
switch (match) {
case '%Y':
return date.getFullYear().toString();
case '%m':
return ('0' + (date.getMonth() + 1)).slice(-2);
case '%d':
return ('0' + date.getDate()).slice(-2);
default:
return match;
}
});
}

function isValidDate(dateString: string, format: string): boolean {
const dateParts = dateString.match(/\d+/g);
if (!dateParts) return false;

const year = parseInt(dateParts[format.indexOf('%Y') / 3], 10);
const month = parseInt(dateParts[format.indexOf('%m') / 3], 10);
const day = parseInt(dateParts[format.indexOf('%d') / 3], 10);

if (year < 1 || month < 1 || month > 12 || day < 1 || day > 31) return false;

const daysInMonth = new Date(year, month, 0).getDate();
return day <= daysInMonth;
}
Loading