feat(scaffolder): added parseRepoUrl helper to parse to json object
Signed-off-by: blam <ben@blam.sh>
This commit is contained in:
@@ -30,8 +30,14 @@ export const getRepoSourceDirectory = (
|
||||
}
|
||||
return workspacePath;
|
||||
};
|
||||
export type RepoSpec = {
|
||||
repo: string;
|
||||
host: string;
|
||||
owner: string;
|
||||
organization?: string;
|
||||
};
|
||||
|
||||
export const parseRepoUrl = (repoUrl: string) => {
|
||||
export const parseRepoUrl = (repoUrl: string): RepoSpec => {
|
||||
let parsed;
|
||||
try {
|
||||
parsed = new URL(`https://${repoUrl}`);
|
||||
@@ -55,7 +61,7 @@ export const parseRepoUrl = (repoUrl: string) => {
|
||||
);
|
||||
}
|
||||
|
||||
const organization = parsed.searchParams.get('organization');
|
||||
const organization = parsed.searchParams.get('organization') ?? undefined;
|
||||
|
||||
return { host, owner, repo, organization };
|
||||
};
|
||||
|
||||
@@ -24,6 +24,7 @@ import { ConfigReader, JsonObject } from '@backstage/config';
|
||||
import { StorageTaskBroker } from './StorageTaskBroker';
|
||||
import { DatabaseTaskStore } from './DatabaseTaskStore';
|
||||
import { createTemplateAction, TemplateActionRegistry } from '../actions';
|
||||
import { RepoSpec } from '../actions/builtin/publish/util';
|
||||
|
||||
async function createStore(): Promise<DatabaseTaskStore> {
|
||||
const manager = SingleConnectionDatabaseManager.fromConfig(
|
||||
@@ -174,6 +175,7 @@ describe('TaskWorker', () => {
|
||||
it('should parse strings as objects if possible', async () => {
|
||||
const inputAction = createTemplateAction<{
|
||||
address: { line1: string };
|
||||
list: string[];
|
||||
address2: string;
|
||||
}>({
|
||||
id: 'test-input',
|
||||
@@ -195,10 +197,21 @@ describe('TaskWorker', () => {
|
||||
address2: {
|
||||
type: 'string',
|
||||
},
|
||||
list: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
async handler(ctx) {
|
||||
if (ctx.input.list.length !== 1) {
|
||||
throw new Error(
|
||||
`expected list to have length "1" got ${ctx.input.list.length}`,
|
||||
);
|
||||
}
|
||||
if (ctx.input.address.line1 !== 'line 1') {
|
||||
throw new Error(
|
||||
`expected address.line1 to be "line 1" got ${ctx.input.address.line1}`,
|
||||
@@ -231,6 +244,7 @@ describe('TaskWorker', () => {
|
||||
action: 'test-input',
|
||||
input: {
|
||||
address: JSON.stringify({ line1: 'line 1' }),
|
||||
list: JSON.stringify(['hey!']),
|
||||
address2: '{"not valid"}',
|
||||
},
|
||||
},
|
||||
@@ -249,4 +263,102 @@ describe('TaskWorker', () => {
|
||||
|
||||
expect((event?.body?.output as JsonObject).result).toBe('line 1');
|
||||
});
|
||||
|
||||
// TODO(blam): Can delete this test when we make the helpers a public API
|
||||
it('should provide a repoUrlParse helper for the templates', async () => {
|
||||
const inputAction = createTemplateAction<{
|
||||
destination: RepoSpec;
|
||||
}>({
|
||||
id: 'test-input',
|
||||
schema: {
|
||||
input: {
|
||||
type: 'object',
|
||||
required: ['destination'],
|
||||
properties: {
|
||||
destination: {
|
||||
title: 'destination',
|
||||
type: 'object',
|
||||
properties: {
|
||||
repo: {
|
||||
type: 'string',
|
||||
},
|
||||
host: {
|
||||
type: 'string',
|
||||
},
|
||||
owner: {
|
||||
type: 'string',
|
||||
},
|
||||
organization: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
async handler(ctx) {
|
||||
ctx.output('host', ctx.input.destination.host);
|
||||
ctx.output('repo', ctx.input.destination.repo);
|
||||
ctx.output('owner', ctx.input.destination.owner);
|
||||
|
||||
if (ctx.input.destination.host !== 'github.com') {
|
||||
throw new Error(
|
||||
`expected host to be "github.com" got ${ctx.input.destination.host}`,
|
||||
);
|
||||
}
|
||||
|
||||
if (ctx.input.destination.repo !== 'repo') {
|
||||
throw new Error(
|
||||
`expected repo to be "repo" got ${ctx.input.destination.repo}`,
|
||||
);
|
||||
}
|
||||
|
||||
if (ctx.input.destination.owner !== 'owner') {
|
||||
throw new Error(
|
||||
`expected repo to be "owner" got ${ctx.input.destination.owner}`,
|
||||
);
|
||||
}
|
||||
},
|
||||
});
|
||||
actionRegistry.register(inputAction);
|
||||
|
||||
const broker = new StorageTaskBroker(storage, logger);
|
||||
const taskWorker = new TaskWorker({
|
||||
logger,
|
||||
workingDirectory: os.tmpdir(),
|
||||
actionRegistry,
|
||||
taskBroker: broker,
|
||||
});
|
||||
|
||||
const { taskId } = await broker.dispatch({
|
||||
steps: [
|
||||
{
|
||||
id: 'test-input',
|
||||
name: 'test-input',
|
||||
action: 'test-input',
|
||||
input: {
|
||||
destination: '{{ parseRepoUrl parameters.repoUrl }}',
|
||||
},
|
||||
},
|
||||
],
|
||||
output: {
|
||||
host: '{{ steps.test-input.output.host }}',
|
||||
repo: '{{ steps.test-input.output.repo }}',
|
||||
owner: '{{ steps.test-input.output.owner }}',
|
||||
},
|
||||
values: {
|
||||
repoUrl: 'github.com?repo=repo&owner=owner',
|
||||
},
|
||||
});
|
||||
|
||||
const task = await broker.claim();
|
||||
await taskWorker.runOneTask(task);
|
||||
|
||||
const { events } = await storage.listEvents({ taskId });
|
||||
const event = events.find(e => e.type === 'completion');
|
||||
|
||||
expect((event?.body?.output as JsonObject).host).toBe('github.com');
|
||||
expect((event?.body?.output as JsonObject).repo).toBe('repo');
|
||||
expect((event?.body?.output as JsonObject).owner).toBe('owner');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -23,8 +23,9 @@ import { TaskBroker, Task } from './types';
|
||||
import fs from 'fs-extra';
|
||||
import path from 'path';
|
||||
import { TemplateActionRegistry } from '../actions/TemplateActionRegistry';
|
||||
import * as handlebars from 'handlebars';
|
||||
import * as Handlebars from 'handlebars';
|
||||
import { InputError } from '@backstage/errors';
|
||||
import { parseRepoUrl } from '../actions/builtin/publish/util';
|
||||
|
||||
type Options = {
|
||||
logger: Logger;
|
||||
@@ -34,7 +35,20 @@ type Options = {
|
||||
};
|
||||
|
||||
export class TaskWorker {
|
||||
constructor(private readonly options: Options) {}
|
||||
private readonly handlebars: typeof Handlebars;
|
||||
|
||||
constructor(private readonly options: Options) {
|
||||
this.handlebars = Handlebars.create();
|
||||
|
||||
// TODO(blam): this should be a public facing API but it's a little
|
||||
// scary right now, so we're going to lock it off like the component API is
|
||||
// in the frontend until we can work out a nice way to do it.
|
||||
this.handlebars.registerHelper('parseRepoUrl', repoUrl => {
|
||||
return JSON.stringify(parseRepoUrl(repoUrl));
|
||||
});
|
||||
|
||||
this.handlebars.registerHelper('json', obj => JSON.stringify(obj));
|
||||
}
|
||||
|
||||
start() {
|
||||
(async () => {
|
||||
@@ -102,7 +116,7 @@ export class TaskWorker {
|
||||
step.input &&
|
||||
JSON.parse(JSON.stringify(step.input), (_key, value) => {
|
||||
if (typeof value === 'string') {
|
||||
const templated = handlebars.compile(value, {
|
||||
const templated = this.handlebars.compile(value, {
|
||||
noEscape: true,
|
||||
strict: true,
|
||||
data: false,
|
||||
@@ -110,9 +124,13 @@ export class TaskWorker {
|
||||
})(templateCtx);
|
||||
|
||||
// If it smells like a JSON object then give it a parse as an object and if it fails return the string
|
||||
if (templated.startsWith('{') && templated.endsWith('}')) {
|
||||
if (
|
||||
(templated.startsWith('{') && templated.endsWith('}')) ||
|
||||
(templated.startsWith('[') && templated.endsWith(']'))
|
||||
) {
|
||||
try {
|
||||
// Don't recursively JSON parse the values of this string. Shouldn't need to, don't want to encourage the use of returning handlebars from somewhere else
|
||||
// Don't recursively JSON parse the values of this string.
|
||||
// Shouldn't need to, don't want to encourage the use of returning handlebars from somewhere else
|
||||
return JSON.parse(templated);
|
||||
} catch {
|
||||
return templated;
|
||||
@@ -183,7 +201,7 @@ export class TaskWorker {
|
||||
JSON.stringify(task.spec.output),
|
||||
(_key, value) => {
|
||||
if (typeof value === 'string') {
|
||||
return handlebars.compile(value, {
|
||||
return this.handlebars.compile(value, {
|
||||
noEscape: true,
|
||||
strict: true,
|
||||
data: false,
|
||||
|
||||
Reference in New Issue
Block a user