diff --git a/package.json b/package.json
index e7e9983f..a6f1f3de 100644
--- a/package.json
+++ b/package.json
@@ -3,6 +3,9 @@
"version": "6.10.5",
"description": "Export design tokens from Figma",
"main": "plugin.js",
+ "engines": {
+ "node": "~18.20.3"
+ },
"repository": {
"type": "git",
"url": "https://github.com/lukasoppermann/design-tokens.git"
diff --git a/src/config/config.ts b/src/config/config.ts
index 70a92a24..1cd66bcf 100644
--- a/src/config/config.ts
+++ b/src/config/config.ts
@@ -25,6 +25,7 @@ export default {
authType: {
token: 'token',
gitlabToken: 'gitlab_token',
+ gitlabCommit: 'gitlab_commit',
basic: 'Basic',
bearer: 'Bearer'
}
diff --git a/src/ui/components/UrlExportSettings.tsx b/src/ui/components/UrlExportSettings.tsx
index 4f1dcb5c..b33f86a0 100644
--- a/src/ui/components/UrlExportSettings.tsx
+++ b/src/ui/components/UrlExportSettings.tsx
@@ -111,7 +111,7 @@ export const UrlExportSettings = () => {
type='text'
required
pattern='^https://.*'
- placeholder='https://api.github.com/repos/:username/:repo/dispatches'
+ placeholder= {(settings.authType === config.key.authType.gitlabCommit && 'https://gitlab.com/api/v4/projects/:projectId')|| 'https://api.github.com/repos/:username/:repo/dispatches'}
value={settings.serverUrl}
onChange={value => updateSettings(draft => { draft.serverUrl = value })}
/>
@@ -156,6 +156,10 @@ export const UrlExportSettings = () => {
label: '(Gitlab) token',
value: config.key.authType.gitlabToken
},
+ {
+ label: '(Gitlab) Project Token',
+ value: config.key.authType.gitlabCommit
+ },
{
label: 'Basic authentication',
value: config.key.authType.basic
@@ -198,6 +202,25 @@ export const UrlExportSettings = () => {
>}
+ {config.key.authType.gitlabCommit === settings.authType &&
+ <>
+
Branch
+
+
+ updateSettings(draft => { draft.reference = value })}
+ />
+
+ >}
+
About This Export
Commit Message
diff --git a/src/ui/modules/gitlabRepository.ts b/src/ui/modules/gitlabRepository.ts
new file mode 100644
index 00000000..5fbce131
--- /dev/null
+++ b/src/ui/modules/gitlabRepository.ts
@@ -0,0 +1,122 @@
+import { utf8ToBase64 } from "@src/utilities/base64";
+import {
+ urlExportRequestBody,
+ urlExportSettings,
+} from "@typings/urlExportData";
+
+export class GitlabRepository {
+ baseUrl: string;
+ token: string;
+
+ constructor(props: { baseUrl: string; token: string }) {
+ this.baseUrl = props.baseUrl;
+ this.token = props.token;
+ }
+
+ async upload(
+ { client_payload: clientPayload }: urlExportRequestBody,
+ { reference: branch }: urlExportSettings,
+ responseHandler: {
+ onError: () => void;
+ onLoaded: (request: XMLHttpRequest) => void;
+ }
+ ) {
+ const encodedContent = utf8ToBase64(clientPayload.tokens);
+ const encodedFilepath = encodeURIComponent(clientPayload.filename);
+
+ let isFileExist: boolean;
+ try {
+ isFileExist = await this._checkFile(encodedFilepath, branch);
+ } catch (error) {
+ if (error && error.request && error.code === 401) {
+ responseHandler.onLoaded(error.request);
+ }
+ return;
+ }
+
+ const uploadRequest = new XMLHttpRequest();
+ uploadRequest.onerror = (_err) => responseHandler.onError();
+ uploadRequest.onload = (event) =>
+ responseHandler.onLoaded(event.target as XMLHttpRequest);
+
+ this._uploadFile({
+ request: uploadRequest,
+ content: encodedContent,
+ commitMessage: clientPayload.commitMessage,
+ filepath: encodedFilepath,
+ branch: branch,
+ isFileExist: isFileExist,
+ });
+ }
+
+ private _checkFile(
+ encodedFilepath: string,
+ branch: string
+ ): Promise {
+ return new Promise((resolve, reject) => {
+ const request = new XMLHttpRequest();
+ request.open(
+ "GET",
+ `${this.baseUrl}/repository/files/${encodedFilepath}?ref=${branch}`
+ );
+ this._setRequestHeader(request);
+
+ request.onreadystatechange = (_ev: ProgressEvent) => {
+ if (request.readyState !== XMLHttpRequest.DONE) {
+ return;
+ }
+
+ const statusCode = request.status;
+ if (statusCode === 200) {
+ resolve(true);
+ return;
+ }
+
+ if (statusCode === 404) {
+ resolve(false);
+ return;
+ }
+
+ reject({
+ code: statusCode,
+ message: request.response,
+ request: request,
+ });
+ };
+
+ request.send();
+ });
+ }
+
+ private _uploadFile(args: {
+ request: XMLHttpRequest;
+ filepath: string;
+ content: string;
+ commitMessage: string;
+ branch: string;
+ isFileExist: boolean;
+ }) {
+ const { isFileExist, request, branch, content, commitMessage, filepath } = args;
+
+ const body = {
+ branch: branch,
+ content: content,
+ commit_message: commitMessage || `Design token update at ${Date.now()}`,
+ encoding: "base64",
+ };
+ const encodedFilepath = encodeURIComponent(filepath);
+
+ request.open(
+ isFileExist ? "PUT" : "POST",
+ `${this.baseUrl}/repository/files/${encodedFilepath}`
+ );
+ this._setRequestHeader(request);
+
+ request.send(JSON.stringify(body));
+ }
+
+ private _setRequestHeader(request: XMLHttpRequest) {
+ request.setRequestHeader("Authorization", `Bearer ${this.token}`);
+ request.setRequestHeader("Content-Type", `application/json`);
+ }
+}
diff --git a/src/ui/modules/urlExport.ts b/src/ui/modules/urlExport.ts
index b73784f9..d7351235 100644
--- a/src/ui/modules/urlExport.ts
+++ b/src/ui/modules/urlExport.ts
@@ -3,8 +3,9 @@ import { commands } from '@config/commands'
import config from '@config/config'
import { PluginMessage } from '@typings/pluginEvent'
import { urlExportRequestBody, urlExportSettings } from '@typings/urlExportData'
+import { GitlabRepository } from './gitlabRepository'
-const responeHandler = (request: XMLHttpRequest): string => {
+const responseHandler = (request: XMLHttpRequest): string => {
// 401
if (request.status === 401) {
return '🚨 401: Check your access token'
@@ -40,31 +41,45 @@ const addUrlExportRequestHeaders = (request: XMLHttpRequest, exportSettings: url
}
}
-const addUrlExportRequestEvents = (request: XMLHttpRequest) => {
- // on error
- request.onerror = (event) => {
- // @ts-ignore
- parent.postMessage({
+function requestErrorHandler() {
+ parent.postMessage(
+ {
pluginMessage: {
command: commands.closePlugin,
payload: {
- notification: '🚨 An error occurred while sending the tokens: check your settings & your server.'
- }
- } as PluginMessage
- }, '*')
- }
- // show message on successful push
- request.onload = (progressEvent: ProgressEvent) => {
- // @ts-ignore
- parent.postMessage({
+ notification:
+ "🚨 An error occurred while sending the tokens: check your settings & your server.",
+ },
+ } as PluginMessage,
+ },
+ "*"
+ );
+}
+
+function requestLoadedHandler(request: XMLHttpRequest) {
+ // @ts-ignore
+ parent.postMessage(
+ {
pluginMessage: {
command: commands.closePlugin,
payload: {
- notification: responeHandler(progressEvent.target as XMLHttpRequest)
- }
- } as PluginMessage
- }, '*')
- }
+ notification: responseHandler(request),
+ },
+ } as PluginMessage,
+ },
+ "*"
+ );
+}
+
+const addUrlExportRequestEvents = (request: XMLHttpRequest) => {
+ // on error
+ request.onerror = (_event) => {
+ requestErrorHandler();
+ };
+ // show message on successful push
+ request.onload = (progressEvent: ProgressEvent) => {
+ requestLoadedHandler(progressEvent.target as XMLHttpRequest);
+ };
}
const generateUrlExportRequestBody = (exportSettings: urlExportSettings, requestBody: urlExportRequestBody) => {
@@ -96,6 +111,19 @@ const urlExport = (parent, exportSettings: urlExportSettings, requestBody: urlEx
} as PluginMessage
}, '*')
}
+
+ if (exportSettings.authType === config.key.authType.gitlabCommit) {
+ const gitlabRepo = new GitlabRepository({
+ baseUrl: exportSettings.url,
+ token: exportSettings.accessToken,
+ });
+ gitlabRepo.upload(requestBody, exportSettings, {
+ onError: requestErrorHandler,
+ onLoaded: requestLoadedHandler,
+ });
+ return;
+ }
+
// init request
const request = new XMLHttpRequest()
// send to user defined url
diff --git a/src/utilities/base64.ts b/src/utilities/base64.ts
new file mode 100644
index 00000000..4eb7d3ec
--- /dev/null
+++ b/src/utilities/base64.ts
@@ -0,0 +1,9 @@
+const utf8ToBase64 = (text: string): string => {
+ const utf8EncodedBytes = new TextEncoder().encode(text)
+ const binString = Array.from(utf8EncodedBytes, (byte) =>
+ String.fromCodePoint(byte)
+ ).join('')
+ return btoa(binString)
+}
+
+export { utf8ToBase64 }