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 broken cookie auth and allow app-based TOTP #5

Merged
merged 4 commits into from
Jul 30, 2024
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
117 changes: 117 additions & 0 deletions CATEGORIES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
## Design tools

Plugin categories

### Accessibility tools – `accessibility`

`b9dc27fc-7de7-4c54-a06b-937a24b60d2e`

### Other – `design-tools-other`

`8407c5a0-a869-4575-b2c9-4e2ecf09fe14`

### Import & export – `import-export`

`a848ebc4-b1e5-477e-bcd9-b016ea972ba3`

### File organization – `file-organization`

`78909ce9-256c-49a6-88c8-39911c07f508`

### Editing & effects – `editing-effects`

`a9198d63-f5dc-4527-8264-40fcbdea59e7`

### Prototyping & animation – `prototyping-animation`

`b83419e1-1f7f-4364-a040-280b26348f10`

*Other plugin types*

## Design templates

### Portfolio templates – `portfolio-templates`

`e82de578-d9b4-4e98-b2d0-71d139b6c46d`

### Desktop apps & websites – `desktop-apps-websites`

`9875a6bf-477f-4090-baf9-595dd56e06af`

### Mobile app design templates – `mobile-apps`

`acb17ca2-5589-408a-bb21-f64ceb54c483`

### Calendar templates – `calendar-templates`

`04e643a3-3e4c-47a4-bbf3-aade7ec7cff9`

### Other – `design-templates-other`

`ee24a81c-8d9c-445e-bff1-1fe89b9db440`

### Resume templates – `resume-templates`

`10cb0a4c-4685-469b-85c7-147883cb4164`

## Presentation templates – `presentations`

`94c586f5-6263-46e2-9fcf-49718805db07`

## Visual assets

### Other – `visual-assets-other`

`32730997-8f6b-404d-aa8c-e7008f79d9be`

### Shapes & colors – `shapes-colors`

`168d4235-8665-4de5-9281-ef45800c133a`

### Fonts & typography templates – `fonts-typography`

`464c111d-1bff-4e6c-b487-2b7f750c3cbe`

### Stock photography – `stock-photography`

`7e7e0b5d-37fc-4965-99a3-c539d219a678`

### Illustrations library – `illustrations`

`9b78c860-a803-4bbc-b68e-aef07c634deb`

## Education

### Classroom activities – `classroom-activities`

`73430a8b-f51f-448e-8c7f-95073607b54d`

### Other – `education-other`

`6ac04c4f-8acb-454e-aa17-a38e6044d6f2`

### Tutorial templates – `tutorials`

`3888b245-6bde-41c1-8e47-24c939a36852`

## Libraries

### UI kits – `ui-kits`

`39a29ef5-9033-44fa-8c63-156d280eef15`

### Other – `libraries-other`

`b9558673-223d-4d83-b8ef-56272b66a2db`

### Wireframes – `wireframes`

`39683760-7e2d-42bb-8fd8-812e38536822`

## Software development templates – `development`

`ba697d28-282d-465d-8354-01a3f485ed83`

## Icon Packs – `icons`

`a0f2fdd9-ab75-44c8-8671-7801bf768b94`
130 changes: 110 additions & 20 deletions bin/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,56 @@

// index.js
const fs = require('fs');
// const crypto = require('crypto');
// const path = require('path');
const { Command, Option } = require("commander");
const { authenticate } = require('../src/auth-helper');
const { getPluginInfo, prepareRelease, uploadCodeBundle, publishRelease } = require("../src/figma-helper");
const { getPluginInfo, prepareRelease, uploadCodeBundle, publishRelease, getCategories, getFigmaCookie, getFigmaApiToken } = require("../src/figma-helper");
const { updatePackageVersion } = require("../src/package-json-helper");

// Get SHA1 hash for cover image upload
// const getHash = (filePath) => new Promise((resolve, reject) => {
// const hash = crypto.createHash('sha1');
// const rs = fs.createReadStream(path.resolve(filePath));
// rs.on('error', reject);
// rs.on('data', chunk => hash.update(chunk));
// rs.on('end', () => resolve(hash.digest('hex')));
// });

const parseMedia = (carouselMediaUrls, carouselVideoUrls) => {
return {
carouselMedia: Object.keys(carouselMediaUrls).length > 0
? Object.keys(carouselMediaUrls).map((index) => ({
carousel_position: index,
sha1: carouselMediaUrls[index].sha1,
}))
: undefined,
carouselVideos: Object.keys(carouselVideoUrls).length > 0
? Object.keys(carouselVideoUrls).map((index) => ({
carousel_position: index,
sha1: carouselVideoUrls[index].sha1,
}))
: undefined,
};
}

function isValidUUID(uuid) {
const uuidRegex = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;
return uuidRegex.test(uuid);
}
function kebabToSnakeCase(str) {
return str.replace(/-/g, '_');
}

const authnTokenOption = (new Option('-t, --authn-token <string>', 'Figma AuthN Token'))
.makeOptionMandatory(true)
.env('FIGMA_WEB_AUTHN_TOKEN');

const recentUserDataOption = (new Option('-u, --recent-user-data <string>', 'Figma recent user data header'))
.makeOptionMandatory(true)
.env('FIGMA_RECENT_USER_DATA');
const cookieOption = (new Option('-ck, --cookie <string>', 'Figma cookies for auth'))
.env('FIGMA_COOKIE');

const tsidOption = (new Option('-tsid, --tsid <string>', 'Figma session id'))
.env('FIGMA_TSID');

const manifestFileOption = (new Option('-m, --manifest-file <string>', 'Filepath to your plugins manifest.json')).default('./manifest.json')

Expand All @@ -29,63 +67,74 @@ async function main() {
program
.command("auth")
.description("Authenticate in Figma to get the AuthN Token needed to publish using Figmas web api")
.addOption(cookieOption)
.addOption(tsidOption)
.action(authenticate)

program
.command("current-version")
.description("Get the current Version of the plugin")
.addOption(authnTokenOption)
.addOption(recentUserDataOption)
.addOption(cookieOption)
.addOption(manifestFileOption)
.action(async ({authnToken, recentUserData, manifestFile}) => {
const currentPluginInfo = await getPluginInfo(manifestFile, authnToken, recentUserData);
.action(async ({authnToken, cookie, manifestFile}) => {
const currentPluginInfo = await getPluginInfo(manifestFile, authnToken, cookie);

console.log('Current Plugin Version' + currentPluginInfo.currentVersionNumber);
})

program
.command("get-cookies")
.action(async () => {
const { cookie, tsid } = await getFigmaCookie();
console.log(`FIGMA_COOKIE='${cookie}' FIGMA_TSID=${tsid}`);
})

program
.command("prepare")
.description("Sets the package.json files minor part of the version to the next version of the figma plugin. Expects a valid authN token from figma set to the FIGMA_WEB_AUTHN ENV variable!")
.option('-p, --package-file <string>', 'Filepath to your package.json', 'package.json')
.addOption(authnTokenOption)
.addOption(recentUserDataOption)
.addOption(cookieOption)
.addOption(manifestFileOption)
.action(async ({manifestFile, recentUserData, packageFile, authnToken}) => {
const currentVersionNumber = (await getPluginInfo(manifestFile, authnToken, recentUserData)).currentVersionNumber;
.action(async ({manifestFile, cookie, packageFile, authnToken}) => {
const currentVersionNumber = (await getPluginInfo(manifestFile, authnToken, cookie)).currentVersionNumber;
updatePackageVersion(packageFile, currentVersionNumber + 1)
console.log('Minor Version in '+ packageFile + ' updated to '+ (currentVersionNumber+1))
})

program
.command("create-api-key")
.description("Create a Figma api key")
.addOption(recentUserDataOption)
.addOption(cookieOption)
.addOption(authnTokenOption)
.option('-e, --expiriation <number>', 'Exporiration in seconds (default: 240 Seconds)', 240)
.option('-exp, --expiriation <number>', 'Exporiration in seconds (default: 240 Seconds)', 240) // Deprecated
.option('-e, --expiration <number>', 'Expiration in seconds (default: 240 Seconds)', 240)
.option('-d, --description <number>', 'Description of the token', 'parrot-cd-generated-token')
.option('-s, --scopes <scopes...>', 'Scopes for the token', ['files:read'])
.action(async ({authnToken, recentUserData, expiriation, description, scopes}) => {
const apiToken = getFigmaApiToken(authnToken, recentUserData, description, expiriation, scopes)
.action(async ({authnToken, cookie, expiration, expiriation, description, scopes}) => {
const apiToken = await getFigmaApiToken(authnToken, cookie, description, expiration || expiriation, scopes)
console.log(apiToken);
})

program
.command("release")
.description("Release a new version - expects the build to been done in an earlier step")
.addOption(authnTokenOption)
.addOption(recentUserDataOption)
.addOption(cookieOption)
.addOption(manifestFileOption)
.option('-n, --store-name <string>', 'Name of the plugin apearing on the store - falling back to the current one in the store if not specified')
.option('-d, --store-description <string>', 'Description of the figma plugin')
.option('-df, --store-description-file <string>', 'Path to a file containing the Description of the figma plugin')
.option('-tl, --tagline <string>', 'Tagline - falling back to the current one in the store if not specified')
.option('-t, --tags <string>...', 'Tags - falling back to the current ones in the store if not specified')
.option('-tg, --tags <string...>', 'Tags - falling back to the current ones in the store if not specified')
.option('-c, --enable-comments <boolean>', 'Enable Comments - falling back to the current ones in the store if not specified')
.option('-rn, --release-notes <string>', 'Release Notes', '')
.option('-ctg, --category <string>', 'Category')
.option('-rnf, --release-notes-file <string>', 'Release Notes file containing the description of what has changed - {{VERSION}} will be replaced with version figma of the plugin')
.action(async ({authnToken, recentUserData, manifestFile, storeName, storeDescription, storeDescriptionFile, tagline, tags, releaseNotes, releaseNotesFile}) => {
.action(async ({authnToken, manifestFile, storeName, storeDescription, storeDescriptionFile, tagline, tags, releaseNotes, releaseNotesFile, category, cookie}) => {

const currentPluginInfo = await getPluginInfo(manifestFile, authnToken, recentUserData)
const currentPluginInfo = await getPluginInfo(manifestFile, authnToken)
if (storeDescriptionFile !== undefined && storeDescriptionFile !== '') {
storeDescription = fs.readFileSync(storeDescriptionFile, 'utf8');
} if (storeDescription === undefined) {
Expand All @@ -105,27 +154,68 @@ async function main() {
console.log('--store-name not provided using current store name')
}

if (!category) {
category = currentPluginInfo.category_id;
console.log('--category not provided using current category id');
} else if (!isValidUUID(category)) {
const categories = await getCategories(authnToken);
const foundCategory = categories.find(({ url_slug }) => (kebabToSnakeCase(url_slug) === kebabToSnakeCase(category)));
if (foundCategory && foundCategory.id && foundCategory.parent_category_id) {
console.log('--category is not a valid UUID, category ID fetched from URL slug');
category = foundCategory.id;
} else {
console.warn('--category is not a valid UUID, could not find valid category');
}
}

if (tagline === undefined) {
tagline = currentPluginInfo.currentVersion.tagline;
}

if (tags === undefined) {
tags = currentPluginInfo.tags;
}
const { carouselMedia, carouselVideos } = parseMedia(currentPluginInfo.carousel_media_urls, currentPluginInfo.carousel_videos);

console.log('Preparing release....');
const preparedRelease = await prepareRelease(manifestFile, storeName, storeDescription, releaseNotes, tagline, tags, authnToken, recentUserData);
console.log({ manifestFile, storeName, storeDescription, releaseNotes, category, tagline, tags });

// // const carouselImages = ['dist/cover.png'];
// const carouselImages = undefined;

// const carouselImageShas = carouselImages ? await Promise.all(
// carouselImages?.map(async (p, i) => ({
// "carousel_position": i,
// "sha1": await getHash(p)
// }))
// ) : undefined;
// console.log({ carouselImageShas })

const preparedRelease = await prepareRelease(manifestFile, storeName, storeDescription, releaseNotes, tagline, tags, authnToken, category, cookie);
console.log('...Release Prepared');
const preparedVersionId = preparedRelease.version_id;
const signature = preparedRelease.signature;

// await uploadIconBundle('dist/icon.png', preparedRelease.icon_upload_url);
// await uploadCoverImageBundle('dist/cover.png', preparedRelease.cover_image_upload_url);

// if (preparedRelease.carousel_images) {
// let i = 0;
// for (const carouselImage of carouselImages) {
// if (preparedRelease.carousel_images[i]) {
// await uploadCarouselImages(carouselImage, preparedRelease.carousel_images[i]);
// console.log('Creating and uploading cover image...', preparedRelease.carousel_images[i].url, carouselImage);
// }
// i++;
// }
// }

console.log('Creating and uploading code bundle....');
await uploadCodeBundle(manifestFile, preparedRelease.code_upload_url);
console.log('...Creation and Upload done');

console.log('Releasing prepared Publishing version (' + preparedRelease.version_id + ')...');
const publishedVersion = await publishRelease(manifestFile, preparedVersionId, signature, authnToken, recentUserData);
const publishedVersion = await publishRelease(manifestFile, preparedVersionId, signature, authnToken, cookie, carouselMedia, carouselVideos);
console.log('Version '+ publishedVersion.plugin.versions[preparedVersionId].version +' (' + preparedVersionId + ') published');

})
Expand Down
Loading