Skip to content

Commit

Permalink
feat: lambda support for yarn2/3 and pnpm package managers (#12750)
Browse files Browse the repository at this point in the history
* feat: add support for pnpm and yarn2 for lambda function
  • Loading branch information
lazpavel committed Jun 22, 2023
1 parent d19d17e commit fd18195
Show file tree
Hide file tree
Showing 39 changed files with 1,012 additions and 390 deletions.
1 change: 1 addition & 0 deletions .eslint-dictionary.json
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,7 @@
"pipfile",
"pkill",
"pluggable",
"pnpm",
"poller",
"polly",
"positionally",
Expand Down
2 changes: 0 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,6 @@ This section should get you running with **Amplify CLI** and get you familiar wi
npm install --global yarn
```

> If you are using Yarn v2, run `yarn set version classic` to change to Yarn Classic.
> Ensure that `.bin` directory is added to your PATH. For example, add `export PATH="<amplify-cli/.bin>:$PATH"` to your shell profile file on Linux or macOS.
2. Ensure you have [Java](https://aws.amazon.com/corretto/) installed and `java` command is available in your system. This is required for DynamoDB emulator.
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"scripts": {
"addwords": "node ./scripts/add-to-dict.js",
"all-cloud-e2e": "CURR_BRANCH=$(git branch | awk '/\\*/{printf \"%s\", $2}') && UPSTREAM_BRANCH=run-e2e/$USER/run-all-tests && git push $(git remote -v | grep aws-amplify/amplify-cli | head -n1 | awk '{print $1;}') $CURR_BRANCH:$UPSTREAM_BRANCH --no-verify --force && echo \"\n\n 🏃 E2E test are running at:\nhttps://app.circleci.com/pipelines/github/aws-amplify/amplify-cli?branch=$UPSTREAM_BRANCH\"",
"build-and-verify": "yarn clean && yarn build && yarn prettier-check && yarn lint-check && yarn verify-api-extract",
"build-and-verify": "yarn clean && yarn && yarn build && yarn prettier-check && yarn lint-check && yarn verify-api-extract",
"build-tests-changed": "lerna run build-tests --since dev",
"build-tests": "lerna run build-tests",
"build": "lerna run build",
Expand Down
1 change: 1 addition & 0 deletions packages/amplify-category-custom/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
},
"devDependencies": {
"@types/lodash": "^4.14.149",
"jest": "^29.5.0",
"rimraf": "^3.0.2"
},
"jest": {
Expand Down
1 change: 1 addition & 0 deletions packages/amplify-category-custom/resources/sample.npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
resolution-mode=highest
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,6 @@ describe('addCDKWalkthrough scenarios', () => {
expect(buildCustomResources_mock).toHaveBeenCalledWith(mockContext, 'customresoourcename');
expect(mockContext.amplify.openEditor).toHaveBeenCalledTimes(1);
expect(mockContext.amplify.updateamplifyMetaAfterResourceAdd).toHaveBeenCalledTimes(1);
expect(fs.writeFileSync).toHaveBeenCalledTimes(1);
expect(fs.writeFileSync).toHaveBeenCalledTimes(2);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ const buildResource = async (resource: ResourceMeta): Promise<void> => {
const packageManager = await getPackageManager(targetDir);

if (packageManager === null) {
throw new Error('No package manager found. Please install npm or yarn to compile overrides for this project.');
throw new Error('No package manager found. Please install npm, yarn, or pnpm to compile overrides for this project.');
}

try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,4 +57,7 @@ async function generateSkeletonDir(resourceName: string) {

const cdkFilepath = path.join(targetDir, 'cdk-stack.ts');
fs.writeFileSync(cdkFilepath, fs.readFileSync(path.join(srcResourceDirPath, 'cdk-stack.ts.sample')));

const npmRcPath = path.join(targetDir, '.npmrc');
fs.writeFileSync(npmRcPath, fs.readFileSync(path.join(srcResourceDirPath, 'sample.npmrc')));
}
3 changes: 2 additions & 1 deletion packages/amplify-category-function/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@
"uuid": "^8.3.2"
},
"devDependencies": {
"@types/folder-hash": "^4.0.1"
"@types/folder-hash": "^4.0.1",
"jest": "^29.5.0"
},
"jest": {
"collectCoverage": true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,11 @@ export const run = async (context: $TSContext) => {
await packageResource(context, resource);
}
} catch (err) {
context.print.info(err.stack);
context.print.error('There was an error building the function resources');
const amplifyError = new AmplifyError(
throw new AmplifyError(
'PackagingLambdaFunctionError',
{ message: `There was an error building the function resources ${err.message}` },
err,
);
void context.usageData.emitError(amplifyError);
process.exitCode = 1;
}
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
FunctionTriggerParameters,
LambdaLayer,
} from '@aws-amplify/amplify-function-plugin-interface';
import { printer } from '@aws-amplify/amplify-prompts';
import * as fs from 'fs-extra';
import _ from 'lodash';
import * as path from 'path';
Expand Down Expand Up @@ -127,19 +128,18 @@ export async function addFunctionResource(
if (completeParams.skipNextSteps) {
return completeParams.resourceName;
}
const { print } = context;

const customPoliciesPath = pathManager.getCustomPoliciesPath(category, completeParams.resourceName);

print.success(`Successfully added resource ${completeParams.resourceName} locally.`);
print.info('');
print.success('Next steps:');
print.info(`Check out sample function code generated in <project-dir>/amplify/backend/function/${completeParams.resourceName}/src`);
print.info('"amplify function build" builds all of your functions currently in the project');
print.info('"amplify mock function <functionName>" runs your function locally');
print.info(`To access AWS resources outside of this Amplify app, edit the ${customPoliciesPath}`);
print.info('"amplify push" builds all of your local backend resources and provisions them in the cloud');
print.info(
printer.success(`Successfully added resource ${completeParams.resourceName} locally.`);
printer.info('');
printer.success('Next steps:');
printer.info(`Check out sample function code generated in <project-dir>/amplify/backend/function/${completeParams.resourceName}/src`);
printer.info('"amplify function build" builds all of your functions currently in the project');
printer.info('"amplify mock function <functionName>" runs your function locally');
printer.info(`To access AWS resources outside of this Amplify app, edit the ${customPoliciesPath}`);
printer.info('"amplify push" builds all of your local backend resources and provisions them in the cloud');
printer.info(
'"amplify publish" builds all of your local backend and front-end resources (if you added hosting category) and provisions them in the cloud',
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ import { autoGeneratedParameters } from './autogeneratedParameters';
import { askExecRolePermissionsQuestions } from './execPermissionsWalkthrough';
import { generalQuestionsWalkthrough, settingsUpdateSelection } from './generalQuestionsWalkthrough';
import { scheduleWalkthrough } from './scheduleWalkthrough';
import { printer } from '@aws-amplify/amplify-prompts';
import { packageManagerWalkthrough } from './packageManagerWalkthrough';

/**
* Starting point for CLI walkthrough that generates a lambda function
Expand Down Expand Up @@ -72,10 +74,10 @@ export const createWalkthrough = async (
}

// list out the advanced settings before asking whether to configure them
context.print.info('');
context.print.success('Available advanced settings:');
printer.blankLine();
printer.success('Available advanced settings:');
advancedSettingsList.forEach((setting) => context.print.info('- '.concat(setting)));
context.print.info('');
printer.blankLine();

// ask whether to configure advanced settings
if (await context.amplify.confirmPrompt('Do you want to configure advanced settings?', false)) {
Expand Down Expand Up @@ -110,6 +112,9 @@ export const createWalkthrough = async (
Object.keys(getStoredEnvironmentVariables(templateParameters.functionName)),
),
);

// ask scheduling Lambda questions and merge in results
templateParameters = merge(templateParameters, await packageManagerWalkthrough(templateParameters.runtime.value));
}

return templateParameters;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { getPackageManagerByType, packageManagers, PackageManagerType } from '@aws-amplify/amplify-cli-core';
import { BuildType, FunctionParameters } from '@aws-amplify/amplify-function-plugin-interface';
import { minLength, prompter } from '@aws-amplify/amplify-prompts';

export const packageManagerWalkthrough = async (runtime: string): Promise<Partial<FunctionParameters>> => {
if (runtime === 'nodejs') {
const packageManagerOptions = Object.values(packageManagers).map((pm) => ({
name: pm.displayValue,
value: pm.packageManager as string,
}));

const packageManager = (await prompter.pick(
'Choose the package manager that you want to use:',
packageManagerOptions,
)) as PackageManagerType;

return {
scripts: {
build: await getBuildCommand(packageManager),
},
};
}

return {};
};

const getBuildCommand = async (packageManager: PackageManagerType): Promise<string> => {
if (packageManager === 'custom') {
return prompter.input('Enter command or script path to build your function:', {
validate: minLength(1),
});
} else {
const packageManagerInstance = getPackageManagerByType(packageManager);
return [packageManagerInstance.executable].concat(packageManagerInstance.getInstallArgs(BuildType.PROD)).join(' ');
}
};
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { $TSContext, pathManager, AmplifyError } from '@aws-amplify/amplify-cli-core';
import { BuildRequest, BuildType, FunctionRuntimeLifecycleManager } from '@aws-amplify/amplify-function-plugin-interface';
import { printer } from '@aws-amplify/amplify-prompts';
import { categoryName } from '../../../constants';

export const buildFunction = async (
Expand All @@ -15,31 +16,26 @@ export const buildFunction = async (

const depCheck = await runtimePlugin.checkDependencies(breadcrumbs.functionRuntime);
if (!depCheck.hasRequiredDependencies) {
context.print.error(depCheck.errorMessage || `You are missing dependencies required to package ${resourceName}`);
printer.error(depCheck.errorMessage || `You are missing dependencies required to package ${resourceName}`);
throw new AmplifyError('PackagingLambdaFunctionError', { message: `Missing required dependencies to package ${resourceName}` });
}

const prevBuildTime = lastBuildTimestamp ? new Date(lastBuildTimestamp) : undefined;

// build the function
let rebuilt = false;
if (breadcrumbs.scripts && breadcrumbs.scripts.build) {
// TODO
throw new AmplifyError('NotImplementedError', { message: 'Executing custom build scripts is not yet implemented' });
} else {
const buildRequest: BuildRequest = {
buildType,
srcRoot: pathManager.getResourceDirectoryPath(undefined, categoryName, resourceName),
runtime: breadcrumbs.functionRuntime,
legacyBuildHookParams: {
projectRoot: pathManager.findProjectRoot(),
resourceName,
},
lastBuildTimeStamp: prevBuildTime,
lastBuildType,
};
rebuilt = (await runtimePlugin.build(buildRequest)).rebuilt;
}
const buildRequest: BuildRequest = {
buildType,
srcRoot: pathManager.getResourceDirectoryPath(undefined, categoryName, resourceName),
runtime: breadcrumbs.functionRuntime,
legacyBuildHookParams: {
projectRoot: pathManager.findProjectRoot(),
resourceName,
},
lastBuildTimeStamp: prevBuildTime,
lastBuildType,
scripts: breadcrumbs.scripts,
};
const rebuilt = (await runtimePlugin.build(buildRequest)).rebuilt;
if (rebuilt) {
context.amplify.updateamplifyMetaAfterBuild({ category: categoryName, resourceName }, buildType.toString());
return new Date().toISOString();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
import inquirer from 'inquirer';
import { $TSAny, $TSContext } from '@aws-amplify/amplify-cli-core';
import {
FunctionParameters,
FunctionTemplateCondition,
FunctionRuntimeCondition,
FunctionRuntimeLifecycleManager,
FunctionRuntimeParameters,
FunctionTemplateCondition,
FunctionTemplateParameters,
FunctionRuntimeLifecycleManager,
RuntimeContributionRequest,
TemplateContributionRequest,
} from '@aws-amplify/amplify-function-plugin-interface';
import { ServiceName } from './constants';
import { printer } from '@aws-amplify/amplify-prompts';
import inquirer from 'inquirer';
import _ from 'lodash';
import { LayerParameters } from './layerParams';
import { $TSAny, $TSContext } from '@aws-amplify/amplify-cli-core';
import { categoryName } from '../../../constants';
import { ServiceName } from './constants';
import { LayerParameters } from './layerParams';

/*
* This file contains the logic for loading, selecting and executing function plugins (currently runtime and template plugins)
*/
Expand Down Expand Up @@ -86,9 +88,7 @@ export async function runtimeWalkthrough(
const plugin = await loadPluginFromFactory(selection.pluginPath, 'functionRuntimeContributorFactory', context);
const depCheck = await (plugin as FunctionRuntimeLifecycleManager).checkDependencies(selection.value);
if (!depCheck.hasRequiredDependencies) {
context.print.warning(
depCheck.errorMessage || 'Some dependencies required for building and packaging this runtime are not installed',
);
printer.warn(depCheck.errorMessage || 'Some dependencies required for building and packaging this runtime are not installed');
}
plugins.push(plugin);
}
Expand All @@ -113,6 +113,7 @@ async function _functionRuntimeWalkthroughHelper(
runtimes.push({
...contribution,
runtimePluginId: selections[i].pluginId,
scripts: selections[i].scripts,
});
}
return runtimes;
Expand All @@ -129,8 +130,8 @@ async function getSelectionsFromContributors<T>(
// get providers from context
const templateProviders = context.pluginPlatform.plugins[selectionOptions.pluginType];
if (!templateProviders) {
context.print.error(selectionOptions.notFoundMessage);
context.print.error(notFoundSuffix);
printer.error(selectionOptions.notFoundMessage);
printer.error(notFoundSuffix);
throw new Error('No plugins found for function configuration');
}

Expand All @@ -154,8 +155,8 @@ async function getSelectionsFromContributors<T>(
// sanity checks
let selection;
if (selections.length === 0) {
context.print.error(selectionOptions.notFoundMessage);
context.print.error(notFoundSuffix);
printer.error(selectionOptions.notFoundMessage);
printer.error(notFoundSuffix);
throw new Error('Plugins found but no selections supplied for function configuration');
} else if (selections.length === 1) {
// quick hack to print custom messages for single selection options
Expand All @@ -165,7 +166,7 @@ async function getSelectionsFromContributors<T>(
} else if (selectionOptions.listOptionsField === 'runtimes') {
singleOptionMsg = `Only one runtime detected: ${selections[0].name}. Learn more about additional runtimes at https://docs.amplify.aws/cli/function`;
}
context.print.info(singleOptionMsg);
printer.info(singleOptionMsg);
selection = selections[0].value;
} else if (isDefaultDefined(selectionOptions)) {
selection = selectionOptions.defaultSelection;
Expand All @@ -187,7 +188,7 @@ async function getSelectionsFromContributors<T>(
selection = [selection];
}

return selection.map((s) => {
return selection.map((s: string) => {
return {
value: s,
pluginPath: selectionMap.get(s).path,
Expand Down Expand Up @@ -244,6 +245,9 @@ interface PluginSelection {
pluginPath: string;
value: string;
pluginId: string;
scripts?: {
build: string;
};
}

interface ListOption {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -375,13 +375,15 @@ const createBreadcrumbs = (params: FunctionParameters | FunctionTriggerParameter
functionRuntime: 'nodejs',
useLegacyBuild: true,
defaultEditorFile: 'src/index.js',
scripts: params.scripts,
};
}
return {
pluginId: params.runtimePluginId,
functionRuntime: params.runtime.value,
useLegacyBuild: params.runtime.value === 'nodejs', // so we can update node builds in the future
defaultEditorFile: params.functionTemplate.defaultEditorFile,
scripts: params.scripts,
};
};

Expand Down

0 comments on commit fd18195

Please sign in to comment.