Skip to content

Commit

Permalink
Update universal generators
Browse files Browse the repository at this point in the history
  • Loading branch information
ipcrm committed Aug 5, 2020
1 parent 95fa0ca commit a7b8fe4
Show file tree
Hide file tree
Showing 4 changed files with 171 additions and 98 deletions.
187 changes: 120 additions & 67 deletions lib/pack/universal-generator/generator.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright © 2019 Atomist, Inc.
* Copyright © 2020 Atomist, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -14,17 +14,21 @@
* limitations under the License.
*/

import { SeedDrivenGeneratorParameters } from "@atomist/automation-client";
import { SeedDrivenGeneratorParameters } from "@atomist/automation-client/lib/operations/generate/SeedDrivenGeneratorParameters";
import { Project } from "@atomist/automation-client/lib/project/Project";
import { NoParameters } from "@atomist/automation-client/lib/SmartParameters";
import {
CachingProjectLoader,
chainTransforms,
CommandListenerInvocation,
computeStartingPoint,
GeneratorRegistration,
ParametersObject,
projectLoaderRepoLoader,
SoftwareDeliveryMachine,
toMachineOptions,
CachingProjectLoader,
CodeTransform,
CommandListenerInvocation,
computeStartingPoint,
GeneratorRegistration,
ParametersObject,
projectLoaderRepoLoader,
SoftwareDeliveryMachine,
toMachineOptions,
TransformResult,
TransformReturnable,
} from "@atomist/sdm";
import * as _ from "lodash";
import { toArray } from "../../util/misc/array";
Expand All @@ -34,73 +38,122 @@ import { UniversalTransform } from "./generatorSupport";
* Wrap provided generator to execute additional transformsAndParameters
*/
export function universalGenerator<P extends SeedDrivenGeneratorParameters = any>(
sdm: SoftwareDeliveryMachine,
generator: GeneratorRegistration<any>,
transforms: Array<UniversalTransform<any>>): GeneratorRegistration<P> {
return {
...generator,
startingPoint: async pi => {
const repoLoader = (p: SeedDrivenGeneratorParameters) =>
projectLoaderRepoLoader(
sdm.configuration.sdm.projectLoader || new CachingProjectLoader(),
p.target.credentials,
true);
sdm: SoftwareDeliveryMachine,
generator: GeneratorRegistration<any>,
transforms: Array<UniversalTransform<any>>,
): GeneratorRegistration<P> {
return {
...generator,
startingPoint: async pi => {
const repoLoader = (p: SeedDrivenGeneratorParameters) =>
projectLoaderRepoLoader(
sdm.configuration.sdm.projectLoader || new CachingProjectLoader(),
p.target.credentials,
true,
);

const project = await computeStartingPoint(
pi.parameters as any,
pi.context,
repoLoader(pi.parameters as any),
{ ...generator as any, redirecter: () => undefined },
generator.startingPoint,
generator,
toMachineOptions(sdm));
const project = await computeStartingPoint(
pi.parameters as any,
pi.context,
repoLoader(pi.parameters as any),
{ ...(generator as any), redirecter: () => undefined },
generator.startingPoint,
generator,
toMachineOptions(sdm),
);

const transformsToApply = [];
for (const transform of transforms) {
if (!!transform.test) {
if (await transform.test(project)) {
transformsToApply.push(transform);
}
} else {
transformsToApply.push(transform);
}
}
const transformsToApply = [];
for (const transform of transforms) {
if (!!transform.test) {
if (await transform.test(project)) {
transformsToApply.push(transform);
}
} else {
transformsToApply.push(transform);
}
}

await enhanceWithSpecificParameters(transformsToApply, pi as any);
await enhanceWithSpecificParameters(transformsToApply, pi as any);

// Safe the transformsAndParameters to invoke on the invocation to re-use later
(pi as any).parameters.__transforms = transformsToApply;
return project;
},
transform: async (p, pi) => {
const universalTransforms = (pi.parameters as any).__transforms as Array<UniversalTransform<any>>;
const computedTransforms = _.flatten(universalTransforms.map(t => toArray(t.transforms)));
// Safe the transformsAndParameters to invoke on the invocation to re-use later
(pi as any).parameters.__transforms = transformsToApply;
return project;
},
transform: async (p, pi) => {
const universalTransforms = (pi.parameters as any).__transforms as Array<UniversalTransform<any>>;
const computedTransforms = _.flatten(universalTransforms.map(t => toArray(t.transforms)));

// tslint:disable-next-line:deprecation
const trans = chainTransforms(...toArray(generator.transform || []), ...computedTransforms);
return trans(p, pi, pi.parameters);
},
};
const trans = chainTransforms(...toArray(generator.transform || []), ...computedTransforms);
return trans(p, pi, pi.parameters);
},
};
}

/**
* Enrich parameters with the extras if needed
*/
async function enhanceWithSpecificParameters<P>(universalTransforms: Array<UniversalTransform<any>>,
ctx: CommandListenerInvocation<any>): Promise<void> {
async function enhanceWithSpecificParameters<P>(
universalTransforms: Array<UniversalTransform<any>>,
ctx: CommandListenerInvocation<any>,
): Promise<void> {
const unsatisfiedParameters: ParametersObject<any> = {};
for (const universalTransform of universalTransforms) {
let params: ParametersObject<any>;
if (typeof universalTransform.parameters === "function") {
const paramsValues: any = await ctx.promptFor<P>(
unsatisfiedParameters,
);
params = universalTransform.parameters(paramsValues);
} else {
params = universalTransform.parameters;
}
_.forEach(params, (v, k) => {
if (ctx.parameters[k] === undefined) {
unsatisfiedParameters[k] = v;
}
});
}

const newParams: any = await ctx.promptFor<P>(unsatisfiedParameters);

const unsatisfiedParameters: ParametersObject<any> = {};
for (const universalTransform of universalTransforms) {
_.forEach(universalTransform.parameters, (v, k) => {
if (ctx.parameters[k] === undefined) {
unsatisfiedParameters[k] = v;
}
});
}
for (const name of Object.getOwnPropertyNames(newParams)) {
ctx.parameters[name] = newParams[name];
}
}

const newParams: any = await ctx.promptFor<P>(unsatisfiedParameters);
function chainTransforms<P = NoParameters>(...transforms: Array<CodeTransform<any>>): CodeTransform<P> {
return async (p, sdmc, params) => {
let cumulativeResult: TransformResult = {
target: p,
success: true,
edited: false,
};
try {
for (const t of transforms) {
const lastResult = await t(p, sdmc, params);
cumulativeResult = combineResults(toTransformResult(p, lastResult), cumulativeResult);
}
return cumulativeResult;
} catch (error) {
return { target: p, edited: cumulativeResult.edited, success: false, error };
}
};
}

function toTransformResult(p: Project, tr: TransformReturnable): TransformResult {
const maybe = tr as TransformResult;
if (maybe && maybe.success !== undefined) {
return maybe;
} else {
return { target: p, success: true, edited: undefined };
}
}

for (const name of Object.getOwnPropertyNames(newParams)) {
ctx.parameters[name] = newParams[name];
}
function combineResults(r1: TransformResult, r2: TransformResult): TransformResult {
return {
...r1,
...r2,
edited: r1.edited || r2.edited ? true : r1.edited === false && r2.edited === false ? false : undefined,
success: r1.success && r2.success,
};
}
17 changes: 9 additions & 8 deletions lib/pack/universal-generator/generatorSupport.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright © 2019 Atomist, Inc.
* Copyright © 2020 Atomist, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -14,14 +14,15 @@
* limitations under the License.
*/

import { Project } from "@atomist/automation-client";
import { Project } from "@atomist/automation-client/lib/project/Project";
import {
CodeTransform,
ExtensionPack,
GeneratorRegistration,
metadata,
ParametersObject,
CodeTransform,
ExtensionPack,
GeneratorRegistration,
metadata,
ParametersObject,
} from "@atomist/sdm";
import * as _ from "lodash";
import { toArray } from "../../util/misc/array";
import { universalGenerator } from "./generator";

Expand All @@ -34,7 +35,7 @@ export interface UniversalTransform<PARAMS = any> {
/** CodeTransforms to execute */
transforms: CodeTransform<PARAMS> | Array<CodeTransform<PARAMS>>;
/** Additional parameters the CodeTransforms need */
parameters?: ParametersObject<PARAMS>;
parameters?: ((params: any) => ParametersObject<PARAMS>) | ParametersObject<PARAMS>;
}

/**
Expand Down
34 changes: 18 additions & 16 deletions lib/pack/universal-generator/test/assertGenerator.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright © 2019 Atomist, Inc.
* Copyright © 2020 Atomist, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -14,28 +14,26 @@
* limitations under the License.
*/

import {
guid,
HandlerContext,
Maker,
MessageClient,
Project,
ProjectOperationCredentials,
RepoId,
} from "@atomist/automation-client";
import { successOn } from "@atomist/automation-client/lib/action/ActionResult";
import { HandleCommand } from "@atomist/automation-client/lib/HandleCommand";
import { HandleEvent } from "@atomist/automation-client/lib/HandleEvent";
import { HandlerContext } from "@atomist/automation-client/lib/HandlerContext";
import { guid } from "@atomist/automation-client/lib/internal/util/string";
import { ProjectOperationCredentials } from "@atomist/automation-client/lib/operations/common/ProjectOperationCredentials";
import { RepoId } from "@atomist/automation-client/lib/operations/common/RepoId";
import { Project } from "@atomist/automation-client/lib/project/Project";
import { BuildableAutomationServer } from "@atomist/automation-client/lib/server/BuildableAutomationServer";
import { MessageClient } from "@atomist/automation-client/lib/spi/message/MessageClient";
import { Maker } from "@atomist/automation-client/lib/util/constructionUtils";
import {
AbstractSoftwareDeliveryMachine,
GeneratorRegistration,
generatorRegistrationToCommand,
SoftwareDeliveryMachineConfiguration,
AbstractSoftwareDeliveryMachine,
GeneratorRegistration,
generatorRegistrationToCommand,
SoftwareDeliveryMachineConfiguration,
} from "@atomist/sdm";
import * as assert from "assert";
import * as flatten from "flat";
import * as _ from "lodash";
import * as assert from "power-assert";
import { defaultSoftwareDeliveryMachineConfiguration } from "../../../machine/defaultSoftwareDeliveryMachineConfiguration";
import { toArray } from "../../../util/misc/array";
import { invokeCommand } from "../../job/invokeCommand";
Expand Down Expand Up @@ -106,7 +104,11 @@ export async function assertUniversalGenerator(generatorUnderTest: GeneratorRegi
// Invoke the generator with the initial set of parameters
let result = await invokeCommand(generatorUnderTest, initialParams, mockHandlerContext(messageClient, initialParams));
assert.deepStrictEqual(result.code, 0, `Generator failed during initial execution: ${result.message}`);
assert.deepStrictEqual(parameterSpecs.map(p => p.name).sort(), _.keys(promptForParams).sort());
const keys = _.keys(promptForParams).sort();
assert.deepStrictEqual(
parameterSpecs.map(p => p.name).some(n => !keys.includes(n)),
false,
`Some expected parameters not provided: ${parameterSpecs.map(p => p.name).join(", ")}`);

if (!!project) {
return {
Expand Down
31 changes: 24 additions & 7 deletions test/pack/universal-generator/universalGenerator.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright © 2019 Atomist, Inc.
* Copyright © 2020 Atomist, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -14,15 +14,13 @@
* limitations under the License.
*/

import {
GitHubRepoRef,
} from "@atomist/automation-client";
import { GitHubRepoRef } from "@atomist/automation-client/lib/operations/common/GitHubRepoRef";
import { GeneratorRegistration } from "@atomist/sdm";
import * as assert from "assert";
import { UniversalTransform } from "../../../lib/pack/universal-generator/generatorSupport";
import {
AssertGeneratorResult,
assertUniversalGenerator,
AssertGeneratorResult,
assertUniversalGenerator,
} from "../../../lib/pack/universal-generator/test/assertGenerator";

const SpringGeneratorRegistration: GeneratorRegistration<{ name: string }> = {
Expand All @@ -47,6 +45,24 @@ const Trans1UniversalTransform: UniversalTransform<{ firstName: string }> = {
},
};

const Trans2UniversalTransform: UniversalTransform<{ middleName: string } | {}> = {
parameters: params => {
if (params.firstName === "Mickey") {
return {
middleName: {
required: true,
},
}
} else {
return {};
}
},
test: async p => true,
transforms: async (p, papi: any) => {
await p.addFile("trans2", `${papi.parameters.middleName} was here`);
},
};

describe("universalGenerator", () => {

it("should fail to generate project with missing initial parameters", async () => {
Expand Down Expand Up @@ -124,11 +140,12 @@ describe("universalGenerator", () => {

const promptForParams = {
firstName: "Mickey",
middleName: "Foo",
};

const result = await assertUniversalGenerator(
SpringGeneratorRegistration,
Trans1UniversalTransform,
[Trans1UniversalTransform, Trans2UniversalTransform],
params,
promptForParams);

Expand Down

0 comments on commit a7b8fe4

Please sign in to comment.