Merge branch 'master' into canon-responsivness

This commit is contained in:
Charles de Dreuille
2024-12-16 16:17:25 +00:00
37 changed files with 388 additions and 130 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/backend-defaults': patch
---
Immediately close all connections when shutting down in local development.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-scaffolder-backend-module-github': patch
---
adding requiredLinearHistory property for branch protection settings
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/backend-app-api': patch
---
Add a `toString` on the default `BackendFeatureMeta` implementations
+1 -1
View File
@@ -1,5 +1,5 @@
{
"mode": "pre",
"mode": "exit",
"tag": "next",
"initialVersions": {
"example-app": "0.2.103",
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/cli': patch
---
added experimental RSPack support for build command in the repo scope
+1
View File
@@ -537,6 +537,7 @@ module.exports = {
'architecture-decisions/adrs-adr011',
'architecture-decisions/adrs-adr012',
'architecture-decisions/adrs-adr013',
'architecture-decisions/adrs-adr014',
],
},
'api/deprecations',
@@ -14,13 +14,9 @@
* limitations under the License.
*/
import { rootLifecycleServiceFactory } from '@backstage/backend-defaults/rootLifecycle';
import { lifecycleServiceFactory } from '@backstage/backend-defaults/lifecycle';
import { loggerServiceFactory } from '@backstage/backend-defaults/logger';
import {
createServiceRef,
createServiceFactory,
coreServices,
createBackendPlugin,
createBackendModule,
createExtensionPoint,
@@ -29,26 +25,13 @@ import {
} from '@backstage/backend-plugin-api';
import { BackendInitializer } from './BackendInitializer';
import { instanceMetadataServiceRef } from '@backstage/backend-plugin-api/alpha';
class MockLogger {
debug() {}
info() {}
warn() {}
error() {}
child() {
return this;
}
}
import { mockServices } from '@backstage/backend-test-utils';
const baseFactories = [
lifecycleServiceFactory,
rootLifecycleServiceFactory,
createServiceFactory({
service: coreServices.rootLogger,
deps: {},
factory: () => new MockLogger(),
}),
loggerServiceFactory,
mockServices.rootLifecycle.factory(),
mockServices.lifecycle.factory(),
mockServices.rootLogger.factory(),
mockServices.logger.factory(),
];
function mkNoopFactory(ref: ServiceRef<{}, 'plugin'>) {
@@ -707,12 +690,8 @@ describe('BackendInitializer', () => {
const extA = createExtensionPoint<string>({ id: 'a' });
const extB = createExtensionPoint<string>({ id: 'b' });
const init = new BackendInitializer([
rootLifecycleServiceFactory,
createServiceFactory({
service: coreServices.rootLogger,
deps: {},
factory: () => new MockLogger(),
}),
mockServices.rootLifecycle.factory(),
mockServices.rootLogger.factory(),
]);
init.add(testPlugin);
init.add(
@@ -885,7 +864,7 @@ describe('BackendInitializer', () => {
});
it('should properly add plugins + modules to the instance metadata service', async () => {
expect.assertions(1);
expect.assertions(2);
const backend = new BackendInitializer(baseFactories);
const plugin = createBackendPlugin({
pluginId: 'test',
@@ -919,6 +898,13 @@ describe('BackendInitializer', () => {
type: 'plugin',
},
]);
expect(instanceMetadata.getInstalledFeatures().map(String)).toEqual(
[
'plugin{pluginId=test}',
'module{moduleId=test,pluginId=test}',
'plugin{pluginId=instance-metadata}',
],
);
},
});
},
@@ -111,13 +111,33 @@ function createInstanceMetadataServiceFactory(
.getRegistrations()
.map(feature => {
if (feature.type === 'plugin') {
return { type: 'plugin', pluginId: feature.pluginId };
return Object.defineProperty(
{
type: 'plugin',
pluginId: feature.pluginId,
},
'toString',
{
enumerable: false,
configurable: true,
value: () => `plugin{pluginId=${feature.pluginId}}`,
},
);
} else if (feature.type === 'module') {
return {
type: 'module',
pluginId: feature.pluginId,
moduleId: feature.moduleId,
};
return Object.defineProperty(
{
type: 'module',
pluginId: feature.pluginId,
moduleId: feature.moduleId,
},
'toString',
{
enumerable: false,
configurable: true,
value: () =>
`module{moduleId=${feature.moduleId},pluginId=${feature.pluginId}}`,
},
);
}
// Ignore unknown feature types.
return undefined;
@@ -53,6 +53,12 @@ export async function createHttpServer(
stop() {
return new Promise<void>((resolve, reject) => {
if (process.env.NODE_ENV === 'development') {
// Ensure that various polling connections are shut down fast in development
server.closeAllConnections();
} else {
server.closeIdleConnections();
}
server.close(error => {
if (error) {
reject(error);
@@ -44,7 +44,7 @@ export function parseUrl(url: string): { path: string; container: string } {
const parsedUrl = new URL(url);
const pathSegments = parsedUrl.pathname.split('/').filter(Boolean);
if (pathSegments.length < 2) {
if (pathSegments.length < 1) {
throw new Error(`Invalid Azure Blob Storage URL format: ${url}`);
}
@@ -171,7 +171,7 @@ export class AzureBlobStorageUrlReader implements UrlReaderService {
},
);
} catch (e) {
if (e.$metadata && e.$metadata.httpStatusCode === 304) {
if (e.statusCode === 304) {
throw new NotModifiedError();
}
+3 -1
View File
@@ -30,7 +30,9 @@ export default createBackendPlugin({
},
async init({ instanceMetadata, logger }) {
logger.info(
`Installed features on this instance: ${instanceMetadata.getInstalledFeatures()}`,
`Installed features on this instance: ${instanceMetadata
.getInstalledFeatures()
.join(', ')}`,
);
},
});
@@ -7,6 +7,7 @@
font-size: var(--canon-font-size-xs);
font-family: var(--canon-font-regular);
color: var(--canon-text-primary);
user-select: none;
& .checkbox {
all: unset;
@@ -18,6 +19,7 @@
box-shadow: inset 0 0 0 1px var(--canon-outline);
cursor: pointer;
border-radius: 2px;
transition: all 0.2s ease-in-out;
&[data-state='unchecked'] {
& .checkbox-indicator {
@@ -36,4 +38,10 @@
justify-content: center;
color: var(--canon-text-primary-on-accent);
}
&:hover {
& .checkbox {
box-shadow: inset 0 0 0 1px var(--canon-outline-hover);
}
}
}
@@ -64,3 +64,11 @@ export const Responsive: Story = {
},
},
};
export const CustomTag: Story = {
args: {
...Default.args,
variant: 'title5',
as: 'h2',
},
};
@@ -19,17 +19,21 @@ import { HeadingProps } from './types';
import { useTheme } from '../../theme/context';
import { getResponsiveValue } from '../../utils/getResponsiveValue';
export const Heading = forwardRef<HTMLParagraphElement, HeadingProps>(
export const Heading = forwardRef<HTMLHeadingElement, HeadingProps>(
(props, ref) => {
const { children, variant = 'title1', ...restProps } = props;
const { children, variant = 'title1', as = 'h1', ...restProps } = props;
const { breakpoint } = useTheme();
const responsiveVariant = getResponsiveValue(variant, breakpoint);
console.log(breakpoint);
console.log(responsiveVariant);
let Component = as;
if (variant === 'title2') Component = 'h2';
if (variant === 'title3') Component = 'h3';
if (variant === 'title4') Component = 'h4';
if (variant === 'title5') Component = 'h5';
if (as) Component = as;
return (
<p
<Component
ref={ref}
{...restProps}
className={`text ${
@@ -37,7 +41,7 @@ export const Heading = forwardRef<HTMLParagraphElement, HeadingProps>(
}`}
>
{children}
</p>
</Component>
);
},
);
@@ -32,4 +32,5 @@ export interface HeadingProps {
'display' | 'title1' | 'title2' | 'title3' | 'title4' | 'title5'
>
>;
as?: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6';
}
+2 -2
View File
@@ -108,8 +108,8 @@ body {
--canon-surface-2: #1a1a1a;
/* Outlines */
--canon-outline: rgba(255, 255, 255, 0.1);
--canon-outline-hover: rgba(255, 255, 255, 0.2);
--canon-outline: rgba(255, 255, 255, 0.2);
--canon-outline-hover: rgba(255, 255, 255, 0.4);
--canon-outline-focus: #fff;
/* States - Add more states */
+6
View File
@@ -31,6 +31,11 @@ import { createScriptOptionsParser } from './optionsParser';
export async function command(opts: OptionValues, cmd: Command): Promise<void> {
let packages = await PackageGraph.listTargetPackages();
const shouldUseRspack = Boolean(process.env.EXPERIMENTAL_RSPACK);
const rspack = shouldUseRspack
? (require('@rspack/core') as typeof import('@rspack/core').rspack)
: undefined;
if (opts.since) {
const graph = PackageGraph.fromPackages(packages);
@@ -111,6 +116,7 @@ export async function command(opts: OptionValues, cmd: Command): Promise<void> {
targetDir: pkg.dir,
configPaths: (buildOptions.config as string[]) ?? [],
writeStats: Boolean(buildOptions.stats),
rspack,
});
},
});
+6
View File
@@ -119,6 +119,12 @@ export async function buildBundle(options: BuildOptions) {
);
}
if (rspack) {
console.log(
chalk.yellow(`⚠️ WARNING: Using experimental RSPack bundler.`),
);
}
const { stats } = await build(configs, isCi, rspack);
if (!stats) {
+6
View File
@@ -233,6 +233,12 @@ DEPRECATION WARNING: React Router Beta is deprecated and support for it will be
? require('@rspack/dev-server').RspackDevServer
: WebpackDevServer;
if (rspack) {
console.log(
chalk.yellow(`⚠️ WARNING: Using experimental RSPack dev server.`),
);
}
const publicPaths = await resolveOptionalBundlingPaths({
entry: 'src/index-public-experimental',
dist: 'dist/public',
@@ -53,7 +53,7 @@ describe('createRouter readonly disabled', () => {
let locationAnalyzer: jest.Mocked<LocationAnalyzer>;
let permissionsService: jest.Mocked<PermissionsService>;
beforeAll(async () => {
beforeEach(async () => {
entitiesCatalog = {
entities: jest.fn(),
entitiesBatch: jest.fn(),
@@ -135,6 +135,38 @@ describe('createRouter readonly disabled', () => {
{ apiVersion: 'a', kind: 'b', metadata: { name: 'n' } },
];
entitiesCatalog.entities.mockResolvedValueOnce({
entities: { type: 'object', entities: [entities[0]] },
pageInfo: { hasNextPage: false },
});
const response = await request(app).get('/entities');
expect(response.status).toEqual(200);
expect(response.body).toEqual(entities);
});
it('happy path: lists entities when by-entities emulation is enabled', async () => {
const router = await createRouter({
entitiesCatalog,
locationService,
orchestrator,
logger: mockServices.logger.mock(),
refreshService,
config: new ConfigReader(undefined),
permissionIntegrationRouter: express.Router(),
auth: mockServices.auth(),
httpAuth: mockServices.httpAuth(),
locationAnalyzer,
permissionsService: permissionsService,
disableRelationsCompatibility: true, // added
});
app = await wrapServer(express().use(router));
const entities: Entity[] = [
{ apiVersion: 'a', kind: 'b', metadata: { name: 'n' } },
];
entitiesCatalog.queryEntities.mockResolvedValueOnce({
items: { type: 'object', entities: [entities[0]] },
pageInfo: {},
@@ -148,6 +180,49 @@ describe('createRouter readonly disabled', () => {
});
it('parses single and multiple request parameters and passes them down', async () => {
entitiesCatalog.entities.mockResolvedValueOnce({
entities: { type: 'object', entities: [] },
pageInfo: { hasNextPage: false },
});
const response = await request(app).get(
'/entities?filter=a=1,a=2,b=3&filter=c=4',
);
expect(response.status).toEqual(200);
expect(entitiesCatalog.entities).toHaveBeenCalledTimes(1);
expect(entitiesCatalog.entities).toHaveBeenCalledWith({
filter: {
anyOf: [
{
allOf: [
{ key: 'a', values: ['1', '2'] },
{ key: 'b', values: ['3'] },
],
},
{ key: 'c', values: ['4'] },
],
},
credentials: mockCredentials.user(),
});
});
it('parses single and multiple request parameters and passes them down when by-entities emulation is enabled', async () => {
const router = await createRouter({
entitiesCatalog,
locationService,
orchestrator,
logger: mockServices.logger.mock(),
refreshService,
config: new ConfigReader(undefined),
permissionIntegrationRouter: express.Router(),
auth: mockServices.auth(),
httpAuth: mockServices.httpAuth(),
locationAnalyzer,
permissionsService: permissionsService,
disableRelationsCompatibility: true, // added
});
app = await wrapServer(express().use(router));
entitiesCatalog.queryEntities.mockResolvedValueOnce({
items: { type: 'object', entities: [] },
pageInfo: {},
@@ -43,7 +43,6 @@ import { LocationService, RefreshService } from './types';
import {
disallowReadonlyMode,
encodeCursor,
expandLegacyCompoundRelationsInEntity,
locationInput,
validateRequestBody,
} from './util';
@@ -58,10 +57,8 @@ import {
} from '@backstage/backend-plugin-api';
import { LocationAnalyzer } from '@backstage/plugin-catalog-node';
import { AuthorizedValidationService } from './AuthorizedValidationService';
import { DeferredPromise, createDeferred } from '@backstage/types';
import {
createEntityArrayJsonStream,
processEntitiesResponseItems,
writeEntitiesResponse,
writeSingleEntityResponse,
} from './response';
@@ -154,7 +151,7 @@ export async function createRouter(
// When pagination parameters are passed in, use the legacy slow path
// that loads all entities into memory
if (pagination) {
if (pagination || disableRelationsCompatibility !== true) {
const { entities, pageInfo } = await entitiesCatalog.entities({
filter,
fields,
@@ -175,22 +172,6 @@ export async function createRouter(
return;
}
// For other read-the-entire-world cases, use queryEntities and stream
// out results.
// The write lock is used for back pressure, preventing slow readers
// from forcing our read loop to pile up response data in userspace
// buffers faster than the kernel buffer is emptied.
// https://nodejs.org/api/http.html#http_response_write_chunk_encoding_callback
const locks: { writeLock?: DeferredPromise } = {};
const controller = new AbortController();
const signal = controller.signal;
req.on('end', () => {
controller.abort(new Error('Client closed connection'));
locks.writeLock?.resolve();
delete locks.writeLock;
});
const responseStream = createEntityArrayJsonStream(res);
const limit = 10000;
let cursor: Cursor | undefined;
@@ -211,31 +192,11 @@ export async function createRouter(
);
if (result.items.entities.length) {
await locks?.writeLock;
signal.throwIfAborted();
if (!disableRelationsCompatibility) {
result.items = processEntitiesResponseItems(
result.items,
expandLegacyCompoundRelationsInEntity,
);
}
if (!responseStream.send(result.items)) {
// The kernel buffer is full. Create the lock but do not await it
// yet - we can better spend our time going to the next round of
// the loop and read from the database while we wait for it to
// drain.
locks.writeLock = createDeferred();
res.once('drain', () => {
locks.writeLock?.resolve();
delete locks.writeLock;
});
if (await responseStream.send(result.items)) {
return; // Client closed connection
}
}
signal.throwIfAborted();
cursor = result.pageInfo?.nextCursor;
} while (cursor);
@@ -16,9 +16,10 @@
import { EntitiesResponseItems } from '../../catalog/types';
import { Response } from 'express';
import { writeResponseData } from './write';
export interface EntityArrayJsonStream {
send(entities: EntitiesResponseItems): boolean;
send(entities: EntitiesResponseItems): Promise<boolean>;
complete(): void;
close(): void;
}
@@ -33,7 +34,7 @@ export function createEntityArrayJsonStream(
let completed = false;
return {
send(response) {
async send(response) {
if (firstSend) {
res.setHeader('Content-Type', 'application/json; charset=utf-8');
res.status(200);
@@ -41,13 +42,15 @@ export function createEntityArrayJsonStream(
}
if (response.type === 'raw') {
let needsDrain = false;
for (const item of response.entities) {
const prefix = firstSend ? '[' : ',';
firstSend = false;
needsDrain ||= !res.write(prefix + item, 'utf8');
if (await writeResponseData(res, prefix + item)) {
return true;
}
}
return !needsDrain;
return false;
}
let data: string;
@@ -60,7 +63,7 @@ export function createEntityArrayJsonStream(
}
firstSend = false;
return res.write(data, 'utf8');
return writeResponseData(res, data);
},
complete() {
if (firstSend) {
@@ -78,26 +78,40 @@ export async function writeEntitiesResponse(
const prefix = first ? '[' : ',';
first = false;
const needsDrain = !res.write(prefix + entity, 'utf8');
if (needsDrain) {
const closed = await new Promise<boolean>(resolve => {
function onContinue() {
res.off('drain', onContinue);
res.off('close', onClose);
resolve(false);
}
function onClose() {
res.off('drain', onContinue);
res.off('close', onClose);
resolve(true);
}
res.on('drain', onContinue);
res.on('close', onClose);
});
if (closed) {
return;
}
if (await writeResponseData(res, prefix + entity)) {
return;
}
}
res.end(`${first ? '[' : ''}]${trailing}`);
}
/**
* Writes a data to the response and waits if the response buffer needs draining.
*
* @internal
* @returns true if the response was closed while waiting for the buffer to drain
*/
export async function writeResponseData(res: Response, data: string | Buffer) {
const ok = res.write(data, 'utf8');
if (!ok) {
if (res.closed) {
return true;
}
const closed = await new Promise<boolean>(resolve => {
function onContinue() {
res.off('drain', onContinue);
res.off('close', onClose);
resolve(false);
}
function onClose() {
res.off('drain', onContinue);
res.off('close', onClose);
resolve(true);
}
res.on('drain', onContinue);
res.on('close', onClose);
});
return closed;
}
return false;
}
@@ -80,6 +80,7 @@ export function createGithubBranchProtectionAction(options: {
requiredConversationResolution?: boolean | undefined;
requireLastPushApproval?: boolean | undefined;
requiredCommitSigning?: boolean | undefined;
requiredLinearHistory?: boolean | undefined;
token?: string | undefined;
},
JsonObject
@@ -272,6 +273,7 @@ export function createGithubRepoCreateAction(options: {
}
| undefined;
requireCommitSigning?: boolean | undefined;
requiredLinearHistory?: boolean | undefined;
customProperties?:
| {
[key: string]: string;
@@ -319,6 +321,7 @@ export function createGithubRepoPushAction(options: {
sourcePath?: string | undefined;
token?: string | undefined;
requiredCommitSigning?: boolean | undefined;
requiredLinearHistory?: boolean | undefined;
requireLastPushApproval?: boolean | undefined;
},
JsonObject
@@ -432,6 +435,7 @@ export function createPublishGithubAction(options: {
}
| undefined;
requiredCommitSigning?: boolean | undefined;
requiredLinearHistory?: boolean | undefined;
customProperties?:
| {
[key: string]: string;
@@ -43,6 +43,7 @@ type BranchProtectionOptions = {
enforceAdmins?: boolean;
dismissStaleReviews?: boolean;
requiredCommitSigning?: boolean;
requiredLinearHistory?: boolean;
};
export const enableBranchProtectionOnDefaultRepoBranch = async ({
@@ -62,6 +63,7 @@ export const enableBranchProtectionOnDefaultRepoBranch = async ({
enforceAdmins = true,
dismissStaleReviews = false,
requiredCommitSigning = false,
requiredLinearHistory = false,
}: BranchProtectionOptions): Promise<void> => {
const tryOnce = async () => {
try {
@@ -93,6 +95,7 @@ export const enableBranchProtectionOnDefaultRepoBranch = async ({
require_last_push_approval: requireLastPushApproval,
},
required_conversation_resolution: requiredConversationResolution,
required_linear_history: requiredLinearHistory,
});
if (requiredCommitSigning) {
@@ -1097,6 +1097,7 @@ describe('publish:github', () => {
enforceAdmins: true,
dismissStaleReviews: false,
requiredCommitSigning: false,
requiredLinearHistory: false,
});
await action.handler({
@@ -1126,6 +1127,7 @@ describe('publish:github', () => {
enforceAdmins: true,
dismissStaleReviews: false,
requiredCommitSigning: false,
requiredLinearHistory: false,
});
await action.handler({
@@ -1154,6 +1156,7 @@ describe('publish:github', () => {
enforceAdmins: true,
dismissStaleReviews: false,
requiredCommitSigning: false,
requiredLinearHistory: false,
});
await action.handler({
@@ -1181,6 +1184,7 @@ describe('publish:github', () => {
enforceAdmins: true,
dismissStaleReviews: false,
requiredCommitSigning: false,
requiredLinearHistory: false,
});
});
@@ -1269,6 +1273,7 @@ describe('publish:github', () => {
enforceAdmins: true,
dismissStaleReviews: false,
requiredCommitSigning: false,
requiredLinearHistory: false,
});
await action.handler({
@@ -1302,6 +1307,7 @@ describe('publish:github', () => {
enforceAdmins: true,
dismissStaleReviews: false,
requiredCommitSigning: false,
requiredLinearHistory: false,
});
await action.handler({
@@ -1335,6 +1341,7 @@ describe('publish:github', () => {
enforceAdmins: true,
dismissStaleReviews: false,
requiredCommitSigning: false,
requiredLinearHistory: false,
});
await action.handler({
@@ -1370,6 +1377,7 @@ describe('publish:github', () => {
enforceAdmins: true,
dismissStaleReviews: false,
requiredCommitSigning: false,
requiredLinearHistory: false,
});
await action.handler({
@@ -1405,6 +1413,7 @@ describe('publish:github', () => {
enforceAdmins: true,
dismissStaleReviews: false,
requiredCommitSigning: false,
requiredLinearHistory: false,
});
await action.handler({
@@ -1440,6 +1449,7 @@ describe('publish:github', () => {
enforceAdmins: true,
dismissStaleReviews: false,
requiredCommitSigning: false,
requiredLinearHistory: false,
});
});
it('should call enableBranchProtectionOnDefaultRepoBranch with the correct values of bypassPullRequestAllowances', async () => {
@@ -1472,6 +1482,7 @@ describe('publish:github', () => {
enforceAdmins: true,
dismissStaleReviews: false,
requiredCommitSigning: false,
requiredLinearHistory: false,
});
await action.handler({
@@ -1503,6 +1514,7 @@ describe('publish:github', () => {
enforceAdmins: true,
dismissStaleReviews: false,
requiredCommitSigning: false,
requiredLinearHistory: false,
});
await action.handler({
@@ -1534,6 +1546,7 @@ describe('publish:github', () => {
enforceAdmins: true,
dismissStaleReviews: false,
requiredCommitSigning: false,
requiredLinearHistory: false,
});
await action.handler({
@@ -1565,6 +1578,7 @@ describe('publish:github', () => {
enforceAdmins: true,
dismissStaleReviews: false,
requiredCommitSigning: false,
requiredLinearHistory: false,
});
await action.handler({
@@ -1600,6 +1614,7 @@ describe('publish:github', () => {
enforceAdmins: true,
dismissStaleReviews: false,
requiredCommitSigning: false,
requiredLinearHistory: false,
});
await action.handler({
@@ -1635,6 +1650,7 @@ describe('publish:github', () => {
enforceAdmins: true,
dismissStaleReviews: false,
requiredCommitSigning: false,
requiredLinearHistory: false,
});
});
@@ -1664,6 +1680,11 @@ describe('publish:github', () => {
defaultValue: false,
overrideValue: true,
},
{
inputProperty: 'requiredLinearHistory',
defaultValue: false,
overrideValue: true,
},
{
inputProperty: 'protectEnforceAdmins',
defaultValue: true,
@@ -1712,6 +1733,7 @@ describe('publish:github', () => {
enforceAdmins: true,
dismissStaleReviews: false,
requiredCommitSigning: false,
requiredLinearHistory: false,
[octokitParameter || inputProperty]: defaultValue,
});
@@ -1740,6 +1762,7 @@ describe('publish:github', () => {
enforceAdmins: true,
dismissStaleReviews: false,
requiredCommitSigning: false,
requiredLinearHistory: false,
[octokitParameter || inputProperty]: overrideValue,
});
@@ -1768,6 +1791,7 @@ describe('publish:github', () => {
enforceAdmins: true,
dismissStaleReviews: false,
requiredCommitSigning: false,
requiredLinearHistory: false,
[octokitParameter || inputProperty]: defaultValue,
});
},
@@ -115,6 +115,7 @@ export function createPublishGithubAction(options: {
includeClaimKeys?: string[];
};
requiredCommitSigning?: boolean;
requiredLinearHistory?: boolean;
customProperties?: { [key: string]: string };
}>({
id: 'publish:github',
@@ -165,6 +166,7 @@ export function createPublishGithubAction(options: {
secrets: inputProps.secrets,
oidcCustomization: inputProps.oidcCustomization,
requiredCommitSigning: inputProps.requiredCommitSigning,
requiredLinearHistory: inputProps.requiredLinearHistory,
customProperties: inputProps.customProperties,
},
},
@@ -217,6 +219,7 @@ export function createPublishGithubAction(options: {
token: providedToken,
customProperties,
requiredCommitSigning = false,
requiredLinearHistory = false,
} = ctx.input;
const octokitOptions = await getOctokitOptions({
@@ -289,6 +292,7 @@ export function createPublishGithubAction(options: {
gitAuthorEmail,
dismissStaleReviews,
requiredCommitSigning,
requiredLinearHistory,
);
ctx.output('commitHash', commitResult?.commitHash);
@@ -98,6 +98,7 @@ describe('github:branch-protection:create', () => {
require_last_push_approval: false,
},
required_conversation_resolution: false,
required_linear_history: false,
});
expect(
mockOctokit.rest.repos.createCommitSignatureProtection,
@@ -130,6 +131,7 @@ describe('github:branch-protection:create', () => {
require_last_push_approval: false,
},
required_conversation_resolution: false,
required_linear_history: false,
});
expect(
mockOctokit.rest.repos.createCommitSignatureProtection,
@@ -162,6 +164,44 @@ describe('github:branch-protection:create', () => {
require_last_push_approval: true,
},
required_conversation_resolution: true,
required_linear_history: false,
});
expect(
mockOctokit.rest.repos.createCommitSignatureProtection,
).toHaveBeenCalledWith({
owner: 'owner',
repo: 'repo',
branch: 'master',
});
});
it('should create branch protection with params and require linear history', async () => {
const input = yaml.parse(examples[3].example).steps[0].input;
const ctx = Object.assign({}, mockContext, { input });
await action.handler(ctx);
expect(mockOctokit.rest.repos.updateBranchProtection).toHaveBeenCalledWith({
mediaType: {
previews: ['luke-cage-preview'],
},
owner: 'owner',
repo: 'repo',
branch: 'master',
required_status_checks: {
strict: true,
contexts: ['test'],
},
restrictions: null,
enforce_admins: true,
required_pull_request_reviews: {
required_approving_review_count: 1,
require_code_owner_reviews: true,
bypass_pull_request_allowances: undefined,
dismiss_stale_reviews: true,
require_last_push_approval: true,
},
required_conversation_resolution: true,
required_linear_history: true,
});
expect(
mockOctokit.rest.repos.createCommitSignatureProtection,
@@ -67,4 +67,25 @@ export const examples: TemplateExample[] = [
],
}),
},
{
description: `GitHub Branch Protection and required linear history on default branch.`,
example: yaml.stringify({
steps: [
{
action: 'github:branch-protection:create',
name: 'Setup Branch Protection',
input: {
repoUrl: 'github.com?repo=repo&owner=owner',
requireCodeOwnerReviews: true,
requiredStatusCheckContexts: ['test'],
dismissStaleReviews: true,
requireLastPushApproval: true,
requiredConversationResolution: true,
requiredCommitSigning: true,
requiredLinearHistory: true,
},
},
],
}),
},
];
@@ -96,6 +96,7 @@ describe('github:branch-protection:create', () => {
require_last_push_approval: false,
},
required_conversation_resolution: false,
required_linear_history: false,
});
expect(
mockOctokit.rest.repos.createCommitSignatureProtection,
@@ -132,6 +133,7 @@ describe('github:branch-protection:create', () => {
require_last_push_approval: false,
},
required_conversation_resolution: false,
required_linear_history: false,
});
expect(
mockOctokit.rest.repos.createCommitSignatureProtection,
@@ -167,6 +169,7 @@ describe('github:branch-protection:create', () => {
requiredConversationResolution: true,
requireLastPushApproval: true,
requiredCommitSigning: true,
requiredLinearHistory: true,
},
});
@@ -199,6 +202,7 @@ describe('github:branch-protection:create', () => {
require_last_push_approval: true,
},
required_conversation_resolution: true,
required_linear_history: true,
});
expect(
mockOctokit.rest.repos.createCommitSignatureProtection,
@@ -62,6 +62,7 @@ export function createGithubBranchProtectionAction(options: {
requiredConversationResolution?: boolean;
requireLastPushApproval?: boolean;
requiredCommitSigning?: boolean;
requiredLinearHistory?: boolean;
token?: string;
}>({
id: 'github:branch-protection:create',
@@ -90,6 +91,7 @@ export function createGithubBranchProtectionAction(options: {
inputProps.requiredConversationResolution,
requireLastPushApproval: inputProps.requireLastPushApproval,
requiredCommitSigning: inputProps.requiredCommitSigning,
requiredLinearHistory: inputProps.requiredLinearHistory,
token: inputProps.token,
},
},
@@ -109,6 +111,7 @@ export function createGithubBranchProtectionAction(options: {
requiredConversationResolution = false,
requireLastPushApproval = false,
requiredCommitSigning = false,
requiredLinearHistory = false,
token: providedToken,
} = ctx.input;
@@ -147,6 +150,7 @@ export function createGithubBranchProtectionAction(options: {
enforceAdmins,
dismissStaleReviews,
requiredCommitSigning,
requiredLinearHistory,
});
},
});
@@ -100,6 +100,7 @@ export function createGithubRepoCreateAction(options: {
includeClaimKeys?: string[];
};
requireCommitSigning?: boolean;
requiredLinearHistory?: boolean;
customProperties?: { [key: string]: string };
}>({
id: 'github:repo:create',
@@ -140,6 +141,7 @@ export function createGithubRepoCreateAction(options: {
secrets: inputProps.secrets,
oidcCustomization: inputProps.oidcCustomization,
requiredCommitSigning: inputProps.requiredCommitSigning,
requiredLinearHistory: inputProps.requiredLinearHistory,
customProperties: inputProps.customProperties,
},
},
@@ -331,6 +331,7 @@ describe('github:repo:push', () => {
bypassPullRequestAllowances: undefined,
requiredApprovingReviewCount: 1,
requiredCommitSigning: false,
requiredLinearHistory: false,
restrictions: undefined,
});
@@ -359,6 +360,7 @@ describe('github:repo:push', () => {
bypassPullRequestAllowances: undefined,
requiredApprovingReviewCount: 1,
requiredCommitSigning: false,
requiredLinearHistory: false,
restrictions: undefined,
});
@@ -387,6 +389,7 @@ describe('github:repo:push', () => {
bypassPullRequestAllowances: undefined,
requiredApprovingReviewCount: 1,
requiredCommitSigning: false,
requiredLinearHistory: false,
restrictions: undefined,
});
@@ -415,6 +418,7 @@ describe('github:repo:push', () => {
bypassPullRequestAllowances: undefined,
requiredApprovingReviewCount: 1,
requiredCommitSigning: false,
requiredLinearHistory: false,
restrictions: undefined,
});
});
@@ -464,6 +468,11 @@ describe('github:repo:push', () => {
defaultValue: false,
overrideValue: true,
},
{
inputProperty: 'requiredLinearHistory',
defaultValue: false,
overrideValue: true,
},
{
inputProperty: 'protectEnforceAdmins',
defaultValue: true,
@@ -508,6 +517,7 @@ describe('github:repo:push', () => {
bypassPullRequestAllowances: undefined,
requiredApprovingReviewCount: 1,
requiredCommitSigning: false,
requiredLinearHistory: false,
restrictions: undefined,
[octokitParameter || inputProperty]: defaultValue,
});
@@ -536,6 +546,7 @@ describe('github:repo:push', () => {
bypassPullRequestAllowances: undefined,
requiredApprovingReviewCount: 1,
requiredCommitSigning: false,
requiredLinearHistory: false,
restrictions: undefined,
[octokitParameter || inputProperty]: overrideValue,
});
@@ -564,6 +575,7 @@ describe('github:repo:push', () => {
bypassPullRequestAllowances: undefined,
requiredApprovingReviewCount: 1,
requiredCommitSigning: false,
requiredLinearHistory: false,
restrictions: undefined,
[octokitParameter || inputProperty]: defaultValue,
});
@@ -75,6 +75,7 @@ export function createGithubRepoPushAction(options: {
sourcePath?: string;
token?: string;
requiredCommitSigning?: boolean;
requiredLinearHistory?: boolean;
requireLastPushApproval?: boolean;
}>({
id: 'github:repo:push',
@@ -106,6 +107,7 @@ export function createGithubRepoPushAction(options: {
sourcePath: inputProps.sourcePath,
token: inputProps.token,
requiredCommitSigning: inputProps.requiredCommitSigning,
requiredLinearHistory: inputProps.requiredLinearHistory,
},
},
output: {
@@ -137,6 +139,7 @@ export function createGithubRepoPushAction(options: {
requireLastPushApproval = false,
token: providedToken,
requiredCommitSigning = false,
requiredLinearHistory = false,
} = ctx.input;
const { owner, repo } = parseRepoUrl(repoUrl, integrations);
@@ -185,6 +188,7 @@ export function createGithubRepoPushAction(options: {
gitAuthorEmail,
dismissStaleReviews,
requiredCommitSigning,
requiredLinearHistory,
);
ctx.output('remoteUrl', remoteUrl);
@@ -371,6 +371,7 @@ export async function initRepoPushAndProtect(
gitAuthorEmail?: string,
dismissStaleReviews?: boolean,
requiredCommitSigning?: boolean,
requiredLinearHistory?: boolean,
): Promise<{ commitHash: string }> {
const gitAuthorInfo = {
name: gitAuthorName
@@ -416,6 +417,7 @@ export async function initRepoPushAndProtect(
enforceAdmins: protectEnforceAdmins,
dismissStaleReviews: dismissStaleReviews,
requiredCommitSigning: requiredCommitSigning,
requiredLinearHistory: requiredLinearHistory,
});
} catch (e) {
assertError(e);
@@ -270,6 +270,12 @@ const requiredCommitSigning = {
description: `Require commit signing so that you must sign commits on this branch.`,
};
const requiredLinearHistory = {
title: 'Require linear history',
type: 'boolean',
description: `Prevent merge commits from being pushed to matching branches.`,
};
const repoVariables = {
title: 'Repository Variables',
description: `Variables attached to the repository`,
@@ -346,6 +352,7 @@ export { sourcePath };
export { token };
export { topics };
export { requiredCommitSigning };
export { requiredLinearHistory };
export { repoVariables };
export { secrets };
export { oidcCustomization };
+15 -15
View File
@@ -20163,11 +20163,11 @@ __metadata:
linkType: hard
"@types/node@npm:*, @types/node@npm:>=13.7.0, @types/node@npm:^22.0.0":
version: 22.10.1
resolution: "@types/node@npm:22.10.1"
version: 22.10.2
resolution: "@types/node@npm:22.10.2"
dependencies:
undici-types: ~6.20.0
checksum: 5a9b81500f288a8fb757b61bd939f99f72b6cb59347a5bae52dd1c2c87100ebbaa9da4256ef3cb9add2090e8704cda1d9a1ffc14ccd5db47a6466c8bae10ebcb
checksum: b22401e6e7d1484e437d802c72f5560e18100b1257b9ad0574d6fe05bebe4dbcb620ea68627d1f1406775070d29ace8b6b51f57e7b1c7b8bafafe6da7f29c843
languageName: node
linkType: hard
@@ -20193,20 +20193,20 @@ __metadata:
linkType: hard
"@types/node@npm:^18.11.18, @types/node@npm:^18.11.9, @types/node@npm:^18.17.15":
version: 18.19.67
resolution: "@types/node@npm:18.19.67"
version: 18.19.68
resolution: "@types/node@npm:18.19.68"
dependencies:
undici-types: ~5.26.4
checksum: 700f92c6a0b63352ce6327286392adab30bb17623c2a788811e9cf092c4dc2fb5e36ca4727247a981b3f44185fdceef20950a3b7a8ab72721e514ac037022a08
checksum: 84e1cd61b719405aa3b9cc42fbdd8821696684150be04cbd35ebed3b92363a83e904cd89dec5b50dd6a8ff0ea9f26c60436109900b485dbd5b93a81fd6374ccb
languageName: node
linkType: hard
"@types/node@npm:^20.1.1, @types/node@npm:^20.16.0":
version: 20.17.9
resolution: "@types/node@npm:20.17.9"
version: 20.17.10
resolution: "@types/node@npm:20.17.10"
dependencies:
undici-types: ~6.19.2
checksum: 2fc67ba937d2c4e7a52f0ccf71b8b4c616dcfa1ad6cd5a726582fd3cbf4f409c2eb44595592580f782c2ade05f8130df072dd04ac064fe150cfcd7849e643500
checksum: 44cfa7cd9a4ebb8f74efa4b89cf963ca0e522121a7d24d8121d40872bbcfd607eaccdc203c4fe92c8b587125be9ca7b071fe4f9b356f263434b8a8512dbebef0
languageName: node
linkType: hard
@@ -20531,12 +20531,12 @@ __metadata:
linkType: hard
"@types/react@npm:^18":
version: 18.3.16
resolution: "@types/react@npm:18.3.16"
version: 18.3.17
resolution: "@types/react@npm:18.3.17"
dependencies:
"@types/prop-types": "*"
csstype: ^3.0.2
checksum: 467c2a325870580b88b4e3bf439749b51b27cb13f52408653cb8c3e7e1b7eff86ada87e384b1aa4d34aa6027c187ca27df00bea77140fda524d726992f5b93ef
checksum: 8107f6f5cc8706a3814e6c927e135ce0c7b40a6d9ae2b8dfb071fee03c6f714456041ecdf92dece599da0db8be7f56f6dc6353d4701f47a04772c7ec0cbb0b59
languageName: node
linkType: hard
@@ -37287,8 +37287,8 @@ __metadata:
linkType: hard
"msw@npm:^2.0.0, msw@npm:^2.0.8":
version: 2.6.8
resolution: "msw@npm:2.6.8"
version: 2.6.9
resolution: "msw@npm:2.6.9"
dependencies:
"@bundled-es-modules/cookie": ^2.0.1
"@bundled-es-modules/statuses": ^1.0.1
@@ -37315,7 +37315,7 @@ __metadata:
optional: true
bin:
msw: cli/index.js
checksum: 5847c78a19413647edfc215452479b389ecaca155581726ac63c82e61bc388f2dd69f4efce9f9b867fb18405a1d748ecaf6ceab00cca26e743673baa20015a79
checksum: 959d42bbc164fd6a2458398c0aa91d3ff605ec7492884ec6b39c2e72dd4064dc0d7d1083656cc676048d57d8d69348c8cb97952459b8157021269404ceee542b
languageName: node
linkType: hard