Skip to content

Commit c058ce6

Browse files
author
Antoine ORY-LAMBALLE
committed
WIP github: send all code to branch and PR
1 parent 23307ba commit c058ce6

File tree

26 files changed

+320
-166
lines changed

26 files changed

+320
-166
lines changed

backend-clapy/src/features/export-code/1-code-controller.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,12 @@ export class CodeController {
2424

2525
const generationHistoryId = await this.userService.saveInHistoryUserCodeGeneration(figmaNode, user);
2626
const res = await exportCode(figmaNode, uploadToCsb, user);
27-
await this.userService.updateUserCodeGeneration(res, user, figmaNode.extraConfig.output, generationHistoryId);
28-
return res;
27+
const res2 = await this.userService.updateUserCodeGeneration(
28+
res,
29+
user,
30+
figmaNode.extraConfig.target,
31+
generationHistoryId,
32+
);
33+
return res2;
2934
}
3035
}

backend-clapy/src/features/export-code/2-create-ts-compiler.ts

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,14 @@ import { perfMeasure } from '../../common/perf-utils.js';
88
import { flags } from '../../env-and-config/app-config.js';
99
import { env } from '../../env-and-config/env.js';
1010
import { warnOrThrow } from '../../utils.js';
11-
import type { CSBResponse, Dict, ExportCodePayload } from '../sb-serialize-preview/sb-serialize.model.js';
11+
import { sendCodeToGithub } from '../github/github-service.js';
12+
import type { Dict, ExportCodePayload } from '../sb-serialize-preview/sb-serialize.model.js';
13+
import { UserSettingsTarget } from '../sb-serialize-preview/sb-serialize.model.js';
1214
import type { AccessTokenDecoded } from '../user/user.utils.js';
1315
import { hasRoleNoCodeSandbox } from '../user/user.utils.js';
1416
import { createNodeContext, generateAllComponents, mkModuleContext } from './3-gen-component.js';
1517
import { formatTsFiles, prepareCssFiles, prepareHtmlFiles } from './8-format-ts-files.js';
18+
import type { CSBResponse } from './9-upload-to-csb.js';
1619
import { makeZip, patchViteJSConfigForDev, uploadToCSB, writeToDisk } from './9-upload-to-csb.js';
1720
import type { ModuleContext, ParentNode, ProjectContext } from './code.model.js';
1821
import { readTemplateFile, readTemplateFiles } from './create-ts-compiler/0-read-template-files.js';
@@ -41,17 +44,17 @@ function getCSSVariablesFileName(cssExt: string) {
4144
const enableMUIInDev = false;
4245

4346
export async function exportCode(
44-
{ root, components, svgs, images, styles, extraConfig, tokens, page }: ExportCodePayload,
47+
{ root, components, svgs, images, styles, extraConfig, tokens, page, githubAccessToken }: ExportCodePayload,
4548
uploadToCsb = true,
4649
user: AccessTokenDecoded,
4750
) {
4851
if (env.localPreviewInsteadOfCsb) {
4952
uploadToCsb = false;
5053
}
51-
if (!extraConfig.output) {
52-
extraConfig.output = extraConfig.zip ? 'zip' : 'csb';
54+
if (!extraConfig.target) {
55+
extraConfig.target = extraConfig.zip ? UserSettingsTarget.zip : UserSettingsTarget.csb;
5356
}
54-
extraConfig.useZipProjectTemplate = env.localPreviewInsteadOfCsb || extraConfig.output === 'zip';
57+
extraConfig.useZipProjectTemplate = env.localPreviewInsteadOfCsb || extraConfig.target === 'zip';
5558
const fwConnector = frameworkConnectors[extraConfig.framework || 'react'];
5659
if (extraConfig.framework === 'angular' && !extraConfig.angularPrefix) {
5760
extraConfig.angularPrefix = 'cl';
@@ -200,12 +203,13 @@ export async function exportCode(
200203
400,
201204
);
202205
}
203-
const isZip = extraConfig.output === 'zip';
204-
if (!env.isDev || uploadToCsb || isZip) {
206+
if (!env.isDev || uploadToCsb || extraConfig.target !== UserSettingsTarget.csb) {
205207
const isNoCodesandboxUser = hasRoleNoCodeSandbox(user);
206-
if (isZip) {
208+
if (extraConfig.target === UserSettingsTarget.zip) {
207209
const zipResponse = await makeZip(csbFiles);
208210
return new StreamableFile(zipResponse as Readable);
211+
} else if (extraConfig.target === UserSettingsTarget.github) {
212+
return sendCodeToGithub(projectContext, githubAccessToken, user, csbFiles);
209213
} else {
210214
if (isNoCodesandboxUser) {
211215
throw new Error("You don't have the permission to upload the generated code to CodeSandbox.");

backend-clapy/src/features/export-code/9-upload-to-csb.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,12 @@ import { promisify } from 'util';
99
import { flags } from '../../env-and-config/app-config.js';
1010
import { env } from '../../env-and-config/env.js';
1111
import { dockerPluginCompDir, localGenClapyDir } from '../../root.js';
12-
import type { CSBResponse } from '../sb-serialize-preview/sb-serialize.model.js';
1312
import type { CodeDict, CsbDict, ModuleContext, ProjectContext } from './code.model.js';
1413

14+
export interface CSBResponse {
15+
sandbox_id: string;
16+
}
17+
1518
export async function uploadToCSB(files: CsbDict) {
1619
const { data } = await axios.post<CSBResponse>('https://codesandbox.io/api/v1/sandboxes/define?json=1', {
1720
files,

backend-clapy/src/features/export-code/create-ts-compiler/9-to-csb-files.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,6 @@ export function toCSBFiles(...files: CodeDict[]) {
1414
return csbFiles;
1515
}
1616

17-
export function isBinaryUrl(content: string) {
17+
function isBinaryUrl(content: string) {
1818
return content.startsWith('https://');
1919
}

backend-clapy/src/features/export-code/frameworks/angular/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ function patchProjectConfigFiles(projectContext: ProjectContext, extraConfig: Ex
194194
angularJson.cli.analytics = false;
195195
}
196196

197-
if (extraConfig.output !== 'csb') {
197+
if (extraConfig.target !== 'csb') {
198198
// Legacy config that is required on CodeSandbox.
199199
delete angularJson.defaultProject;
200200
}

backend-clapy/src/features/github/1-github-controller.ts

Lines changed: 19 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
import { Body, Controller, Post, Req } from '@nestjs/common';
22
import type { RestEndpointMethodTypes } from '@octokit/rest';
33
import type { RequestPrivate } from '../../typings/express-jwt.js';
4-
import type { CodeDict } from '../export-code/code.model.js';
54
import { fetchGithubAccessToken } from './github-api-fetch.js';
65
import type { GHContext } from './github-service.js';
7-
import { commitChanges, listBranches, fetchUser, listRepos } from './github-service.js';
6+
import { listBranches, fetchUser, listRepos } from './github-service.js';
87
import { getOctokit } from './octokit.js';
98

109
interface GithubCredentials {
@@ -87,24 +86,24 @@ export class GithubController {
8786
return listBranches(context);
8887
}
8988

90-
@Post('gencode-tmp')
91-
async genCodeTmp(@Req() req: RequestPrivate, @Body() body: GenCodeReq) {
92-
const auth0UserId = req.auth.sub;
93-
let { githubAccessToken: accessToken, owner, repo, codegenBranch, mergeToBranch } = body;
94-
if (!owner) throw new Error('Missing `owner` in body, cannot generate code.');
95-
if (!repo) throw new Error('Missing `repo` in body, cannot generate code.');
96-
if (!codegenBranch) throw new Error('Missing `codegenBranch` in body, cannot generate code.');
97-
if (!mergeToBranch) throw new Error('Missing `mergeToBranch` in body, cannot generate code.');
98-
99-
const octokit = getOctokit(accessToken);
100-
const context: GHContext = { accessToken, auth0UserId, octokit, owner, repo, codegenBranch, mergeToBranch };
101-
102-
const files: CodeDict = {
103-
'codegen/foo.ts': "console.log('Hello world!');\n",
104-
};
105-
106-
return commitChanges(context, files);
107-
}
89+
// @Post('gencode-tmp')
90+
// async genCodeTmp(@Req() req: RequestPrivate, @Body() body: GenCodeReq) {
91+
// const auth0UserId = req.auth.sub;
92+
// let { githubAccessToken: accessToken, owner, repo, codegenBranch, mergeToBranch } = body;
93+
// if (!owner) throw new Error('Missing `owner` in body, cannot generate code.');
94+
// if (!repo) throw new Error('Missing `repo` in body, cannot generate code.');
95+
// if (!codegenBranch) throw new Error('Missing `codegenBranch` in body, cannot generate code.');
96+
// if (!mergeToBranch) throw new Error('Missing `mergeToBranch` in body, cannot generate code.');
97+
//
98+
// const octokit = getOctokit(accessToken);
99+
// const context: GHContext = { accessToken, auth0UserId, octokit, owner, repo, codegenBranch, mergeToBranch };
100+
//
101+
// const files: CsbDict = {
102+
// 'codegen/foo.ts': { content: "console.log('Hello world!');\n" },
103+
// };
104+
//
105+
// return commitChanges(context, files);
106+
// }
108107

109108
// @Post('search-repos')
110109
// async searchRepos(@Req() req: RequestPrivate, @Body() body: TokenPayload) {

backend-clapy/src/features/github/github-service.ts

Lines changed: 45 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import type { Octokit, RestEndpointMethodTypes } from '@octokit/rest';
22
import axios from 'axios';
3-
import type { CodeDict } from '../export-code/code.model.js';
4-
import { isBinaryUrl } from '../export-code/create-ts-compiler/9-to-csb-files.js';
3+
import type { CsbDict, ProjectContext } from '../export-code/code.model.js';
4+
import type { AccessTokenDecoded } from '../user/user.utils.js';
5+
import { getOctokit } from './octokit.js';
56

67
// Good sources of inspiration:
78
// https://github.com/mheap/octokit-commit-multiple-files/blob/main/create-or-update-files.js
@@ -82,7 +83,45 @@ export async function listBranches(context: GHContext) {
8283
// ).data;
8384
// }
8485

85-
export async function commitChanges(context: GHContext, files: CodeDict) {
86+
export interface GitHubResponse {
87+
url: string;
88+
}
89+
90+
export async function sendCodeToGithub(
91+
projectContext: ProjectContext,
92+
githubAccessToken: string | undefined,
93+
user: AccessTokenDecoded,
94+
files: CsbDict,
95+
) {
96+
if (!githubAccessToken) throw new Error('BUG Missing `githubAccessToken` to send code to github.');
97+
const auth0UserId = user.sub;
98+
const { githubSettings } = projectContext.extraConfig;
99+
if (!githubSettings) throw new Error('BUG Missing `githubSettings` to send code to github.');
100+
const { repository, codegenBranch, mergeToBranch } = githubSettings;
101+
if (!repository) throw new Error('BUG Missing `repository` to send code to github.');
102+
const { owner, repo } = repository;
103+
if (!owner) throw new Error('Missing `owner` in body, cannot generate code.');
104+
if (!repo) throw new Error('Missing `repo` in body, cannot generate code.');
105+
if (!codegenBranch) throw new Error('Missing `codegenBranch` in body, cannot generate code.');
106+
if (!mergeToBranch) throw new Error('Missing `mergeToBranch` in body, cannot generate code.');
107+
108+
const octokit = getOctokit(githubAccessToken);
109+
const context: GHContext = {
110+
accessToken: githubAccessToken,
111+
auth0UserId,
112+
octokit,
113+
owner,
114+
repo,
115+
codegenBranch,
116+
mergeToBranch,
117+
};
118+
119+
const githubResp = await commitChanges(context, files);
120+
const res: GitHubResponse = { url: githubResp.html_url };
121+
return res;
122+
}
123+
124+
export async function commitChanges(context: GHContext, files: CsbDict) {
86125
const message = 'Clapy generated code 2';
87126

88127
const branchCommitSha = await getBranchCommitSha(context);
@@ -112,13 +151,13 @@ async function getBranchCommitSha(context: GHContext) {
112151
).data.object.sha;
113152
}
114153

115-
async function codeDictToTree(context: GHContext, files: CodeDict) {
116-
const promises = Object.entries(files).map(async ([path, content]) => {
154+
async function codeDictToTree(context: GHContext, files: CsbDict) {
155+
const promises = Object.entries(files).map(async ([path, { content, isBinary }]) => {
117156
if (!content) {
118157
throw new Error(`No file content provided for ${path}`);
119158
}
120159
let treeItem: GitTreeItem;
121-
if (isBinaryUrl(content)) {
160+
if (isBinary) {
122161
const { data } = await axios.get(content, { responseType: 'arraybuffer' });
123162
const contentBase64 = Buffer.from(data, 'binary').toString('base64');
124163
const blob = await createBlobBase64(context, contentBase64);

backend-clapy/src/features/sb-serialize-preview/sb-serialize.model.ts

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,18 @@ export enum UserSettingsTarget {
256256
github = 'github',
257257
}
258258

259+
export interface SelectedRepo {
260+
fullName: string;
261+
owner: string;
262+
repo: string;
263+
}
264+
265+
export interface GithubSettings {
266+
repository?: SelectedRepo;
267+
codegenBranch?: string;
268+
mergeToBranch?: string;
269+
}
270+
259271
export interface UserSettings extends AngularConfig, ReactConfig {
260272
page?: boolean;
261273
zip?: boolean;
@@ -265,14 +277,14 @@ export interface UserSettings extends AngularConfig, ReactConfig {
265277
target: UserSettingsTarget;
266278
// Unused for now, only shows/hides the CSS block
267279
customCss?: boolean;
280+
githubSettings?: GithubSettings;
268281
}
269282

270283
export type ExtraConfig = {
271284
isClapyFile?: boolean;
272285
isFTD?: boolean;
273286
enableMUIFramework?: boolean;
274287
// Next props are derived from user settings
275-
output?: 'csb' | 'zip';
276288
useZipProjectTemplate?: boolean;
277289
} & UserSettings;
278290

@@ -287,6 +299,7 @@ export interface ExportCodePayload {
287299
extraConfig: ExtraConfig;
288300
tokens?: Dict; // TODO better typing
289301
page: PageConfig;
302+
githubAccessToken?: string;
290303
}
291304

292305
export interface FigmaStyles {
@@ -425,8 +438,9 @@ export const extractionBlacklist = [
425438

426439
export type FrameNodeBlackList = Exclude<typeof extractionBlacklist[number], 'mainComponent' /* | 'children' */>;
427440

428-
export interface CSBResponse {
429-
sandbox_id: string;
441+
export interface GenCodeResponse {
442+
sandbox_id?: string;
443+
url?: string;
430444
quotas: number;
431445
quotasMax: number;
432446
isLicenseExpired?: boolean;

backend-clapy/src/features/user/user.service.ts

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,17 @@ import { DataSource, Not } from 'typeorm';
77
import { appConfig } from '../../env-and-config/app-config.js';
88
import { env } from '../../env-and-config/env.js';
99
import { GenerationHistoryEntity } from '../export-code/generation-history.entity.js';
10-
import type { CSBResponse, ExportCodePayload, GenerationHistory } from '../sb-serialize-preview/sb-serialize.model.js';
10+
import { UserSettingsTarget } from '../sb-serialize-preview/sb-serialize.model.js';
11+
import type {
12+
ExportCodePayload,
13+
GenerationHistory,
14+
GenCodeResponse,
15+
} from '../sb-serialize-preview/sb-serialize.model.js';
1116
import { StripeService } from '../stripe/stripe.service.js';
1217
import type { AccessTokenDecoded } from './user.utils.js';
1318
import { hasRoleIncreasedQuota, hasRoleNoCodeSandbox } from './user.utils.js';
19+
import type { CSBResponse } from '../export-code/9-upload-to-csb.js';
20+
import type { GitHubResponse } from '../github/github-service.js';
1421

1522
@Injectable()
1623
export class UserService {
@@ -25,7 +32,7 @@ export class UserService {
2532
user: AccessTokenDecoded,
2633
) {
2734
const isNoCodesandboxUser = hasRoleNoCodeSandbox(user);
28-
if (isNoCodesandboxUser && (figmaNode.extraConfig.output === 'csb' || !figmaNode.extraConfig.zip)) {
35+
if (isNoCodesandboxUser && figmaNode.extraConfig.target === 'csb') {
2936
throw new Error("You don't have the permission to upload the generated code to CodeSandbox.");
3037
}
3138
}
@@ -93,21 +100,22 @@ export class UserService {
93100
}
94101

95102
async updateUserCodeGeneration(
96-
res: StreamableFile | CSBResponse | undefined,
103+
res: StreamableFile | CSBResponse | GitHubResponse | undefined,
97104
user: AccessTokenDecoded,
98-
genType: 'csb' | 'zip' | undefined,
105+
genType: UserSettingsTarget | undefined,
99106
generationHistoryId: string | undefined,
100107
) {
101108
if (!res) {
102109
return res;
103110
}
104111
const generationHistory = new GenerationHistoryEntity();
105112
generationHistory.id = generationHistoryId;
106-
if (genType === 'csb') {
107-
res = res as CSBResponse;
108-
generationHistory.generatedLink = res.sandbox_id;
113+
if (genType === UserSettingsTarget.csb || genType === UserSettingsTarget.github) {
114+
const genLink = 'sandbox_id' in res ? res.sandbox_id : 'url' in res ? res.url : undefined;
115+
generationHistory.generatedLink = genLink;
109116
await this.generationHistoryRepository.save(generationHistory);
110-
res = Object.assign(res, this.getUserSubscriptionData(user));
117+
const res2: GenCodeResponse = Object.assign({}, res, await this.getUserSubscriptionData(user));
118+
return res2;
111119
// TODO: si une erreur survient, ne pas bloquer l'exécution du code et envoyer la réponse à l'utilisateur dans ce cas.
112120
} else if (genType === 'zip') {
113121
generationHistory.generatedLink = '_zip';

figma-plugin-clapy/src/backend/routes/2-user/github-settings.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import type { GithubSettings, SelectedRepo } from '../../../common/app-models.js';
21
import { setRepoInSettings } from '../../../common/github-shared-utils.js';
2+
import type { GithubSettings, SelectedRepo } from '../../../common/sb-serialize.model.js';
33

44
export async function getGithubSettings() {
55
return figma.clientStorage.getAsync('githubSettings') as Promise<GithubSettings | undefined>;

0 commit comments

Comments
 (0)