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

fix: download introspection schema when apiId is passed #684

Merged
merged 3 commits into from
Sep 13, 2023
Merged
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
57 changes: 30 additions & 27 deletions packages/amplify-codegen/src/commands/add.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,9 @@ async function add(context, apiId = null, region = 'us-east-1') {
}

const schemaPath = ['schema.graphql', 'schema.json'].map(p => path.join(process.cwd(), p)).find(p => fs.existsSync(p));
if (withoutInit && !schemaPath) {
if (withoutInit && !(apiId || schemaPath)) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the case that schema.json is already present when running amplify codegen add, the schema.json will be overwritten with no warning.

throw Error(
`Please download schema.graphql or schema.json and place in ${process.cwd()} before adding codegen when not in an amplify project`,
`Provide an AppSync API ID with --apiId or manually download schema.graphql or schema.json and place in ${process.cwd()} before adding codegen when not in an amplify project`,
);
}
// Grab the frontend
Expand Down Expand Up @@ -78,36 +78,37 @@ async function add(context, apiId = null, region = 'us-east-1') {
throw new Error(constants.ERROR_CODEGEN_SUPPORT_MAX_ONE_API);
}
let apiDetails;
if (!withoutInit) {
if (!apiId) {
const availableAppSyncApis = getAppSyncAPIDetails(context); // published and un-published
if (availableAppSyncApis.length === 0) {
throw new NoAppSyncAPIAvailableError(constants.ERROR_CODEGEN_NO_API_AVAILABLE);
}
[apiDetails] = availableAppSyncApis;
apiDetails.isLocal = true;
} else {
let shouldRetry = true;
while (shouldRetry) {
const apiDetailSpinner = new Ora();
try {
apiDetailSpinner.start('Getting API details');
apiDetails = await getAppSyncAPIInfo(context, apiId, region);
apiDetailSpinner.succeed();
if (!withoutInit && !apiId) {
const availableAppSyncApis = getAppSyncAPIDetails(context); // published and un-published
if (availableAppSyncApis.length === 0) {
throw new NoAppSyncAPIAvailableError(constants.ERROR_CODEGEN_NO_API_AVAILABLE);
}
[apiDetails] = availableAppSyncApis;
apiDetails.isLocal = true;
} else if (apiId) {
let shouldRetry = true;
while (shouldRetry) {
const apiDetailSpinner = new Ora();
try {
apiDetailSpinner.start('Getting API details');
apiDetails = await getAppSyncAPIInfo(context, apiId, region);
if (!withoutInit) {
await updateAmplifyMeta(context, apiDetails);
break;
} catch (e) {
apiDetailSpinner.fail();
if (e instanceof AmplifyCodeGenAPINotFoundError) {
context.print.info(`AppSync API was not found in region ${region}`);
({ shouldRetry, region } = await changeAppSyncRegion(context, region));
} else {
throw e;
}
}
apiDetailSpinner.succeed();
break;
} catch (e) {
apiDetailSpinner.fail();
if (e instanceof AmplifyCodeGenAPINotFoundError) {
context.print.info(`AppSync API was not found in region ${region}`);
({ shouldRetry, region } = await changeAppSyncRegion(context, region));
} else {
throw e;
}
}
}
}
// else no appsync API, but has schema.graphql or schema.json

if (!withoutInit && !apiDetails) {
return;
Expand All @@ -123,6 +124,8 @@ async function add(context, apiId = null, region = 'us-east-1') {
} else {
schema = getSDLSchemaLocation(apiDetails.name);
}
} else if (apiDetails) {
schema = await downloadIntrospectionSchemaWithProgress(context, apiDetails.id, path.join(process.cwd(), 'schema.json'), region);
} else {
schema = schemaPath;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,11 @@ async function downloadIntrospectionSchema(context, apiId, downloadLocation, reg
const introspectionDir = dirname(downloadLocation);
fs.ensureDirSync(introspectionDir);
fs.writeFileSync(downloadLocation, schema, 'utf8');
return relative(amplify.getEnvInfo().projectPath, downloadLocation);
try {
return relative(amplify.getEnvInfo().projectPath, downloadLocation);
} catch {
return downloadLocation;
}
} catch (ex) {
if (ex.code === 'NotFoundException') {
throw new AmplifyCodeGenAPINotFoundError(constants.ERROR_APPSYNC_API_NOT_FOUND);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,23 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`command - add without init should download introspection schema when api id 1`] = `
Object {
"amplifyExtension": Object {
"apiId": "MOCK_API_ID",
"codeGenTarget": "TYPE_SCRIPT_OR_FLOW_OR_ANY_OTHER_LANGUAGE",
"docsFilePath": "MOCK_DOCS_FILE_PATH",
"framework": "react",
"frontend": "javascript",
"generatedFileName": "API.TS",
"region": "us-east-1",
},
"excludes": "MOCK_EXCLUDE",
"includes": "MOCK_INCLUDE",
"projectName": "Codegen Project",
"schema": "/user/foo/project/schema.json",
}
`;

exports[`command - add without init should read frontend and framework from options 1`] = `
Object {
"amplifyExtension": Object {
Expand All @@ -14,6 +32,24 @@ Object {
"excludes": "MOCK_EXCLUDE",
"includes": "MOCK_INCLUDE",
"projectName": "Codegen Project",
"schema": "/user/foo/project/schema.json",
}
`;

exports[`command - add without init should use existing schema if no api id 1`] = `
Object {
"amplifyExtension": Object {
"apiId": null,
"codeGenTarget": "TYPE_SCRIPT_OR_FLOW_OR_ANY_OTHER_LANGUAGE",
"docsFilePath": "MOCK_DOCS_FILE_PATH",
"framework": "react",
"frontend": "javascript",
"generatedFileName": "API.TS",
"region": "us-east-1",
},
"excludes": "MOCK_EXCLUDE",
"includes": "MOCK_INCLUDE",
"projectName": "Codegen Project",
"schema": "/user/foo/project/schema.graphql",
}
`;
Expand All @@ -32,6 +68,6 @@ Object {
"excludes": "MOCK_EXCLUDE",
"includes": "MOCK_INCLUDE",
"projectName": "Codegen Project",
"schema": "/user/foo/project/schema.graphql",
"schema": "/user/foo/project/schema.json",
}
`;
57 changes: 53 additions & 4 deletions packages/amplify-codegen/tests/commands/add.test.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const fs = require('fs');
const path = require('path');
const { loadConfig } = require('../../src/codegen-config');
const generateStatements = require('../../src/commands/statements');
const generateTypes = require('../../src/commands/types');
Expand All @@ -10,7 +11,13 @@ const { AmplifyCodeGenAPINotFoundError } = require('../../src/errors');

const add = require('../../src/commands/add');

const { getAppSyncAPIDetails, getAppSyncAPIInfo, getProjectAwsRegion, getSDLSchemaLocation } = require('../../src/utils');
const {
getAppSyncAPIDetails,
getAppSyncAPIInfo,
getProjectAwsRegion,
getSDLSchemaLocation,
downloadIntrospectionSchemaWithProgress,
} = require('../../src/utils');

const MOCK_CONTEXT = {
print: {
Expand All @@ -23,6 +30,7 @@ const MOCK_CONTEXT = {
options: {},
},
};
const mockProjectDir = '/user/foo/project';
jest.mock('fs');
jest.mock('../../src/walkthrough/add');
jest.mock('../../src/walkthrough/questions/selectFrontend');
Expand All @@ -33,7 +41,7 @@ jest.mock('../../src/commands/statements');
jest.mock('../../src/codegen-config');
jest.mock('../../src/utils');
jest.mock('process', () => ({
cwd: () => '/user/foo/project',
cwd: () => mockProjectDir,
}));

const MOCK_INCLUDE_PATTERN = 'MOCK_INCLUDE';
Expand Down Expand Up @@ -80,6 +88,7 @@ describe('command - add', () => {
loadConfig.mockReturnValue(LOAD_CONFIG_METHODS);
getProjectAwsRegion.mockReturnValue(MOCK_AWS_REGION);
getSDLSchemaLocation.mockReturnValue(MOCK_SCHEMA_FILE_LOCATION);
downloadIntrospectionSchemaWithProgress.mockReturnValue();
});

it('should walkthrough add questions', async () => {
Expand Down Expand Up @@ -170,12 +179,15 @@ describe('command - add', () => {

describe('without init', () => {
const getProjectMeta = jest.fn();
const schemaPath = path.join(mockProjectDir, 'schema.json');
beforeEach(() => {
loadConfig.mockReturnValue({ ...LOAD_CONFIG_METHODS, getProjects: jest.fn().mockReturnValue([]) });
askForFrontend.mockReturnValue('javascript');
askForFramework.mockReturnValue('react');
getProjectMeta.mockRejectedValue('no init');
fs.existsSync.mockReturnValue(true);
fs.existsSync.mockReturnValue(false);
getAppSyncAPIInfo.mockReturnValue(MOCK_APPSYNC_API_DETAIL);
downloadIntrospectionSchemaWithProgress.mockReturnValue(schemaPath);
});

afterEach(() => {
Expand All @@ -184,6 +196,27 @@ describe('command - add', () => {
askForFramework.mockReset();
getProjectMeta.mockReset();
fs.existsSync.mockReset();
getAppSyncAPIInfo.mockReset();
downloadIntrospectionSchemaWithProgress.mockReset();
});

it('should download introspection schema when api id', async () => {
const context = { ...MOCK_CONTEXT, amplify: { getProjectMeta } };
const defaultRegion = 'us-east-1';
await add(context, MOCK_API_ID);
expect(getAppSyncAPIInfo).toHaveBeenCalledWith(context, MOCK_API_ID, defaultRegion);
expect(downloadIntrospectionSchemaWithProgress).toHaveBeenCalledWith(context, MOCK_API_ID, schemaPath, defaultRegion);
expect(LOAD_CONFIG_METHODS.addProject.mock.calls[0][0]).toMatchSnapshot();
});

it('should use existing schema if no api id', async () => {
fs.existsSync.mockReturnValue(true);
const context = { ...MOCK_CONTEXT, amplify: { getProjectMeta } };
const defaultRegion = 'us-east-1';
await add(context);
expect(getAppSyncAPIInfo).not.toHaveBeenCalled();
expect(downloadIntrospectionSchemaWithProgress).not.toHaveBeenCalled();
expect(LOAD_CONFIG_METHODS.addProject.mock.calls[0][0]).toMatchSnapshot();
});

it('should read frontend and framework from options', async () => {
Expand All @@ -202,8 +235,10 @@ describe('command - add', () => {

it('should use region supplied when without init', async () => {
const region = 'us-west-2';
await add({ ...MOCK_CONTEXT, amplify: { getProjectMeta } }, MOCK_API_ID, region);
const context = { ...MOCK_CONTEXT, amplify: { getProjectMeta } };
await add(context, MOCK_API_ID, region);
expect(getProjectAwsRegion).not.toHaveBeenCalled();
expect(getAppSyncAPIInfo).toHaveBeenCalledWith(context, MOCK_API_ID, region);
expect(LOAD_CONFIG_METHODS.addProject).toHaveBeenCalled();
expect(LOAD_CONFIG_METHODS.addProject.mock.calls[0][0]).toMatchSnapshot();
});
Expand All @@ -230,5 +265,19 @@ describe('command - add', () => {
'Invalid framework provided',
);
});

it('should error if codegen project already exists', () => {
loadConfig.mockReturnValue({ ...LOAD_CONFIG_METHODS, getProjects: jest.fn().mockReturnValue(['foo']) });
expect(add({ ...MOCK_CONTEXT, amplify: { getProjectMeta } }, MOCK_API_ID)).rejects.toThrowError(
'Codegen support only one GraphQL API per project',
);
});

it('should error if codegen project already exists', () => {
fs.existsSync.mockReturnValue(false);
expect(add({ ...MOCK_CONTEXT, amplify: { getProjectMeta } })).rejects.toThrowError(
'Provide an AppSync API ID with --apiId or manually download schema.graphql or schema.json and place in /user/foo/project before adding codegen when not in an amplify project',
);
});
});
});