mirror of
https://github.com/xodio/xod.git
synced 2026-03-15 05:06:59 +01:00
feat(xod-pm, xod-client): add ability to publish library from IDE
Closes #872
This commit is contained in:
@@ -181,6 +181,8 @@ class App extends client.App {
|
||||
onClick(items.newPatch, this.props.actions.createPatch),
|
||||
items.separator,
|
||||
onClick(items.addLibrary, this.props.actions.showLibSuggester),
|
||||
items.separator,
|
||||
onClick(items.publish, this.props.actions.requestPublishProject),
|
||||
]
|
||||
),
|
||||
submenu(
|
||||
@@ -268,6 +270,7 @@ class App extends client.App {
|
||||
/>
|
||||
{this.renderPopupShowCode()}
|
||||
{this.renderPopupProjectPreferences()}
|
||||
{this.renderPopupPublishProject()}
|
||||
{this.renderPopupCreateNewProject()}
|
||||
</HotKeys>
|
||||
);
|
||||
@@ -285,14 +288,17 @@ App.propTypes = R.merge(client.App.propTypes, {
|
||||
const mapStateToProps = R.applySpec({
|
||||
hasUnsavedChanges: client.hasUnsavedChanges,
|
||||
project: client.getProject,
|
||||
user: client.getUser,
|
||||
currentPatchPath: client.getCurrentPatchPath,
|
||||
popups: {
|
||||
createProject: client.getPopupVisibility(client.POPUP_ID.CREATING_PROJECT),
|
||||
showCode: client.getPopupVisibility(client.POPUP_ID.SHOWING_CODE),
|
||||
projectPreferences: client.getPopupVisibility(client.POPUP_ID.EDITING_PROJECT_PREFERENCES),
|
||||
publishingProject: client.getPopupVisibility(client.POPUP_ID.PUBLISHING_PROJECT),
|
||||
},
|
||||
popupsData: {
|
||||
showCode: client.getPopupData(client.POPUP_ID.SHOWING_CODE),
|
||||
publishingProject: client.getPopupData(client.POPUP_ID.PUBLISHING_PROJECT),
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -430,6 +430,7 @@ class App extends client.App {
|
||||
onClick(items.newPatch, this.props.actions.createPatch),
|
||||
items.separator,
|
||||
onClick(items.addLibrary, this.props.actions.showLibSuggester),
|
||||
onClick(items.publish, this.props.actions.requestPublishProject),
|
||||
]
|
||||
),
|
||||
submenu(
|
||||
@@ -686,6 +687,7 @@ class App extends client.App {
|
||||
{this.renderPopupUploadConfig()}
|
||||
{this.renderPopupUploadProcess()}
|
||||
{this.renderPopupProjectPreferences()}
|
||||
{this.renderPopupPublishProject()}
|
||||
{this.renderPopupCreateNewProject()}
|
||||
<PopupSetWorkspace
|
||||
workspace={this.state.workspace}
|
||||
@@ -773,12 +775,16 @@ const mapStateToProps = R.applySpec({
|
||||
hasUnsavedChanges: client.hasUnsavedChanges,
|
||||
lastSavedProject: client.getLastSavedProject,
|
||||
project: client.getProject,
|
||||
user: client.getUser,
|
||||
upload: getUploadProcess,
|
||||
saveProcess: getSaveProcess,
|
||||
currentPatchPath: client.getCurrentPatchPath,
|
||||
selectedPort: getSelectedSerialPort,
|
||||
compileLimitLeft: client.getCompileLimitLeft,
|
||||
popups: {
|
||||
// TODO: make keys match with POPUP_IDs
|
||||
// (for example, `creatingProject` insteand of `createProject`)
|
||||
// this way we could make an util that takes an array of POPUP_IDs and generates a spec
|
||||
createProject: client.getPopupVisibility(client.POPUP_ID.CREATING_PROJECT),
|
||||
projectSelection: client.getPopupVisibility(client.POPUP_ID.OPENING_PROJECT),
|
||||
switchWorkspace: client.getPopupVisibility(client.POPUP_ID.SWITCHING_WORKSPACE),
|
||||
@@ -789,12 +795,14 @@ const mapStateToProps = R.applySpec({
|
||||
projectPreferences: client.getPopupVisibility(
|
||||
client.POPUP_ID.EDITING_PROJECT_PREFERENCES
|
||||
),
|
||||
publishingProject: client.getPopupVisibility(client.POPUP_ID.PUBLISHING_PROJECT),
|
||||
},
|
||||
popupsData: {
|
||||
projectSelection: client.getPopupData(client.POPUP_ID.OPENING_PROJECT),
|
||||
createWorkspace: client.getPopupData(client.POPUP_ID.CREATING_WORKSPACE),
|
||||
switchWorkspace: client.getPopupData(client.POPUP_ID.SWITCHING_WORKSPACE),
|
||||
showCode: client.getPopupData(client.POPUP_ID.SHOWING_CODE),
|
||||
publishingProject: client.getPopupData(client.POPUP_ID.PUBLISHING_PROJECT),
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ import sanctuaryPropType from '../../utils/sanctuaryPropType';
|
||||
import PopupPrompt from '../../utils/components/PopupPrompt';
|
||||
import PopupShowCode from '../../utils/components/PopupShowCode';
|
||||
import PopupProjectPreferences from '../../project/components/PopupProjectPreferences';
|
||||
import PopupPublishProject from '../../project/components/PopupPublishProject';
|
||||
|
||||
import * as actions from '../actions';
|
||||
|
||||
@@ -90,6 +91,20 @@ export default class App extends React.Component {
|
||||
);
|
||||
}
|
||||
|
||||
renderPopupPublishProject() {
|
||||
return (
|
||||
<PopupPublishProject
|
||||
isVisible={this.props.popups.publishingProject}
|
||||
project={this.props.project}
|
||||
user={this.props.user}
|
||||
isPublishing={this.props.popupsData.publishingProject.isPublishing}
|
||||
onPublish={this.props.actions.publishProject}
|
||||
onRequestToEditPreferences={this.props.actions.showProjectPreferences}
|
||||
onClose={this.props.actions.cancelPublishingProject}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
renderPopupCreateNewProject() {
|
||||
return (
|
||||
<PopupPrompt
|
||||
@@ -116,6 +131,7 @@ export default class App extends React.Component {
|
||||
|
||||
App.propTypes = {
|
||||
project: sanctuaryPropType(Project),
|
||||
user: PropTypes.object,
|
||||
currentPatchPath: PropTypes.string,
|
||||
popups: PropTypes.objectOf(PropTypes.bool),
|
||||
popupsData: PropTypes.objectOf(PropTypes.object),
|
||||
@@ -131,9 +147,12 @@ App.propTypes = {
|
||||
/* eslint-disable react/no-unused-prop-types */
|
||||
requestCreateProject: PropTypes.func.isRequired,
|
||||
requestRenameProject: PropTypes.func.isRequired,
|
||||
requestPublishProject: PropTypes.func.isRequired,
|
||||
cancelPublishingProject: PropTypes.func.isRequired,
|
||||
importProject: PropTypes.func.isRequired,
|
||||
openProject: PropTypes.func.isRequired,
|
||||
createPatch: PropTypes.func.isRequired,
|
||||
publishProject: PropTypes.func.isRequired,
|
||||
addComment: PropTypes.func.isRequired,
|
||||
undoCurrentPatch: PropTypes.func.isRequired,
|
||||
redoCurrentPatch: PropTypes.func.isRequired,
|
||||
@@ -162,8 +181,11 @@ App.actions = {
|
||||
createProject: actions.createProject,
|
||||
requestCreateProject: actions.requestCreateProject,
|
||||
requestRenameProject: actions.requestRenameProject,
|
||||
requestPublishProject: actions.requestPublishProject,
|
||||
cancelPublishingProject: actions.cancelPublishingProject,
|
||||
importProject: actions.importProject,
|
||||
openProject: actions.openProject,
|
||||
publishProject: actions.publishProject,
|
||||
createPatch: actions.requestCreatePatch,
|
||||
addComment: actions.addComment,
|
||||
undoCurrentPatch: actions.undoCurrentPatch,
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
.PopupPublishProject {
|
||||
.libName {
|
||||
color: $color-canvas-selected;
|
||||
}
|
||||
|
||||
.property {
|
||||
margin: 7px 0;
|
||||
|
||||
.propertyLabel {
|
||||
color: $color-canvas-face-light;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -37,6 +37,7 @@
|
||||
'components/PatchWrapper',
|
||||
'components/Pin',
|
||||
'components/PinLabel',
|
||||
'components/PopupPublishProject',
|
||||
'components/ProjectBrowser',
|
||||
'components/ProjectBrowserToolbar',
|
||||
'components/Sidebar',
|
||||
|
||||
@@ -17,4 +17,6 @@ export const POPUP_ID = { // eslint-disable-line import/prefer-default-export
|
||||
SHOWING_CODE: 'showingCode',
|
||||
|
||||
ARDUINO_IDE_NOT_FOUND: 'arduinoIDENotFound',
|
||||
|
||||
PUBLISHING_PROJECT: 'publishingProject',
|
||||
};
|
||||
|
||||
@@ -20,6 +20,11 @@ import {
|
||||
PATCH_RENAME,
|
||||
PROJECT_CREATE_REQUESTED,
|
||||
PROJECT_OPEN_REQUESTED,
|
||||
PROJECT_PUBLISH_REQUESTED,
|
||||
PROJECT_PUBLISH_CANCELLED,
|
||||
PROJECT_PUBLISH_START,
|
||||
PROJECT_PUBLISH_SUCCESS,
|
||||
PROJECT_PUBLISH_FAIL,
|
||||
PROJECT_UPDATE_META,
|
||||
PROJECT_CREATE,
|
||||
PROJECT_OPEN,
|
||||
@@ -72,6 +77,14 @@ const showOnePopup = R.curry(
|
||||
)
|
||||
);
|
||||
|
||||
const overPopupData = R.curry(
|
||||
(id, updaterFn, state) => R.over(
|
||||
R.compose(R.lensProp(id), dataLens),
|
||||
updaterFn,
|
||||
state
|
||||
)
|
||||
);
|
||||
|
||||
// :: State -> State
|
||||
export const showOnlyPopup = R.curry(
|
||||
(id, data, state) => R.compose(
|
||||
@@ -101,6 +114,28 @@ const popupsReducer = (state = initialState, action) => {
|
||||
case PROJECT_RENAME_REQUESTED:
|
||||
return showOnlyPopup(POPUP_ID.RENAMING_PROJECT, {}, state);
|
||||
|
||||
case PROJECT_PUBLISH_REQUESTED:
|
||||
return showOnlyPopup(
|
||||
POPUP_ID.PUBLISHING_PROJECT,
|
||||
{ isPublishing: false },
|
||||
state
|
||||
);
|
||||
case PROJECT_PUBLISH_CANCELLED:
|
||||
case PROJECT_PUBLISH_SUCCESS:
|
||||
return hideOnePopup(POPUP_ID.PUBLISHING_PROJECT, state);
|
||||
case PROJECT_PUBLISH_START:
|
||||
return overPopupData(
|
||||
POPUP_ID.PUBLISHING_PROJECT,
|
||||
R.assoc('isPublishing', true),
|
||||
state
|
||||
);
|
||||
case PROJECT_PUBLISH_FAIL:
|
||||
return overPopupData(
|
||||
POPUP_ID.PUBLISHING_PROJECT,
|
||||
R.assoc('isPublishing', false),
|
||||
state
|
||||
);
|
||||
|
||||
case SHOW_CODE_REQUESTED:
|
||||
return showOnlyPopup(POPUP_ID.SHOWING_CODE, action.payload, state);
|
||||
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
export const PROJECT_CREATE_REQUESTED = 'PROJECT_CREATE_REQUESTED';
|
||||
export const PROJECT_OPEN_REQUESTED = 'PROJECT_OPEN_REQUESTED';
|
||||
export const PROJECT_PUBLISH_REQUESTED = 'PROJECT_PUBLISH_REQUESTED';
|
||||
export const PROJECT_PUBLISH_CANCELLED = 'PROJECT_PUBLISH_CANCELLED';
|
||||
export const PROJECT_PUBLISH_START = 'PROJECT_PUBLISH_START';
|
||||
export const PROJECT_PUBLISH_SUCCESS = 'PROJECT_PUBLISH_SUCCESS';
|
||||
export const PROJECT_PUBLISH_FAIL = 'PROJECT_PUBLISH_FAIL';
|
||||
|
||||
export const PROJECT_CREATE = 'PROJECT_CREATE';
|
||||
export const PROJECT_RENAME = 'PROJECT_RENAME';
|
||||
|
||||
@@ -1,13 +1,21 @@
|
||||
import R from 'ramda';
|
||||
import * as XP from 'xod-project';
|
||||
import { publish } from 'xod-pm';
|
||||
|
||||
import { addError } from '../messages/actions';
|
||||
import { foldMaybe } from 'xod-func-tools';
|
||||
|
||||
import { addConfirmation, addError } from '../messages/actions';
|
||||
import { NODETYPE_ERROR_TYPES } from '../editor/constants';
|
||||
import { PROJECT_BROWSER_ERRORS, NODETYPE_ERRORS } from '../messages/constants';
|
||||
import * as ActionType from './actionTypes';
|
||||
import { isPatchPathTaken } from './utils';
|
||||
import { getCurrentPatchPath } from '../editor/selectors';
|
||||
import { getGrant } from '../user/selectors';
|
||||
import { fetchGrant } from '../user/actions';
|
||||
import { LOG_IN_TO_CONTINUE } from '../user/messages';
|
||||
import { SUCCESSFULLY_PUBLISHED } from './messages';
|
||||
import { getProject } from './selectors';
|
||||
import { getPmSwaggerUrl } from '../utils/urls';
|
||||
|
||||
//
|
||||
// Project
|
||||
@@ -65,6 +73,50 @@ export const openWorkspace = libs => ({
|
||||
payload: libs,
|
||||
});
|
||||
|
||||
export const requestPublishProject = () => (dispatch, getState) => {
|
||||
const isAuthorised = foldMaybe(false, R.T, getGrant(getState()));
|
||||
|
||||
if (!isAuthorised) {
|
||||
dispatch(addError(LOG_IN_TO_CONTINUE));
|
||||
// TODO: open account pane?
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch({ type: ActionType.PROJECT_PUBLISH_REQUESTED });
|
||||
};
|
||||
|
||||
export const cancelPublishingProject = () => ({
|
||||
type: ActionType.PROJECT_PUBLISH_CANCELLED,
|
||||
});
|
||||
|
||||
export const publishProject = () => (dispatch, getState) => {
|
||||
const state = getState();
|
||||
const project = getProject(state);
|
||||
|
||||
dispatch({ type: ActionType.PROJECT_PUBLISH_START });
|
||||
|
||||
dispatch(fetchGrant()) // to obtain freshest auth token
|
||||
.then((freshGrant) => {
|
||||
if (freshGrant == null) {
|
||||
// could happen if user logs out in another tab
|
||||
dispatch(addError(LOG_IN_TO_CONTINUE));
|
||||
dispatch({ type: ActionType.PROJECT_PUBLISH_FAIL });
|
||||
return;
|
||||
}
|
||||
|
||||
publish(getPmSwaggerUrl(), freshGrant, project).then(
|
||||
() => {
|
||||
dispatch(addConfirmation(SUCCESSFULLY_PUBLISHED));
|
||||
dispatch({ type: ActionType.PROJECT_PUBLISH_SUCCESS });
|
||||
},
|
||||
(err) => {
|
||||
dispatch({ type: ActionType.PROJECT_PUBLISH_FAIL });
|
||||
dispatch(addError(err.message));
|
||||
}
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
//
|
||||
// Patch
|
||||
//
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
import React from 'react';
|
||||
import Icon from 'react-fa';
|
||||
import * as XP from 'xod-project';
|
||||
|
||||
import sanctuaryPropType from '../../utils/sanctuaryPropType';
|
||||
import PopupForm from '../../utils/components/PopupForm';
|
||||
import { HOSTNAME } from '../../utils/urls';
|
||||
import Button from '../../core/components/Button';
|
||||
|
||||
const PopupPublishProject = ({
|
||||
isVisible,
|
||||
isPublishing,
|
||||
project,
|
||||
user,
|
||||
onPublish,
|
||||
onRequestToEditPreferences,
|
||||
onClose,
|
||||
}) => {
|
||||
const projectName = XP.getProjectName(project);
|
||||
const version = XP.getProjectVersion(project);
|
||||
const description = XP.getProjectDescription(project);
|
||||
const license = XP.getProjectLicense(project);
|
||||
|
||||
// When popup is hidden, `user` could be Nothing
|
||||
const { username } = user.getOrElse({});
|
||||
|
||||
return (
|
||||
<PopupForm
|
||||
className="PopupPublishProject"
|
||||
isVisible={isVisible}
|
||||
isClosable={!isPublishing}
|
||||
title={`You are about to publish on ${HOSTNAME}`}
|
||||
onClose={onClose}
|
||||
>
|
||||
<p className="property">
|
||||
<span className="propertyLabel">Name: </span>
|
||||
<span className="libName">{username}/{projectName}</span>
|
||||
</p>
|
||||
<p className="property">
|
||||
<span className="propertyLabel">Version: </span>
|
||||
{version}
|
||||
</p>
|
||||
<p className="property">
|
||||
<span className="propertyLabel">License: </span>
|
||||
{license}
|
||||
</p>
|
||||
<p className="property">
|
||||
<span className="propertyLabel">Description: </span>
|
||||
{description}
|
||||
</p>
|
||||
{isPublishing ? (
|
||||
<div className="ModalFooter">
|
||||
<Icon name="circle-o-notch" spin size="lg" /> Publishing…
|
||||
</div>
|
||||
) : (
|
||||
<div className="ModalFooter">
|
||||
<Button onClick={onPublish} autoFocus>Publish</Button>
|
||||
<Button onClick={onRequestToEditPreferences}>Edit</Button>
|
||||
<Button onClick={onClose}>Cancel</Button>
|
||||
</div>
|
||||
)}
|
||||
</PopupForm>
|
||||
);
|
||||
};
|
||||
|
||||
PopupPublishProject.propTypes = {
|
||||
isVisible: React.PropTypes.bool,
|
||||
isPublishing: React.PropTypes.bool,
|
||||
user: React.PropTypes.object,
|
||||
project: sanctuaryPropType(XP.Project),
|
||||
onPublish: React.PropTypes.func,
|
||||
onRequestToEditPreferences: React.PropTypes.func,
|
||||
onClose: React.PropTypes.func,
|
||||
};
|
||||
|
||||
PopupPublishProject.defaultProps = {
|
||||
isVisible: false,
|
||||
};
|
||||
|
||||
export default PopupPublishProject;
|
||||
2
packages/xod-client/src/project/messages.js
Normal file
2
packages/xod-client/src/project/messages.js
Normal file
@@ -0,0 +1,2 @@
|
||||
// eslint-disable-next-line import/prefer-default-export
|
||||
export const SUCCESSFULLY_PUBLISHED = 'Successfully published';
|
||||
@@ -41,6 +41,8 @@ export const fetchGrant = () => dispatch =>
|
||||
.then((grant) => {
|
||||
dispatch(setGrant(grant));
|
||||
dispatch(updateCompileLimit(false));
|
||||
|
||||
return grant;
|
||||
});
|
||||
|
||||
export const login = (username, password) => (dispatch) => {
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
export const INCORRECT_CREDENTIALS = 'Incorrect username or password';
|
||||
export const SERVICE_UNAVAILABLE = 'Service unavailable';
|
||||
export const LOG_IN_TO_CONTINUE = 'Please log in to continue';
|
||||
|
||||
@@ -24,7 +24,12 @@ export const isAuthorising = createSelector(
|
||||
R.prop('isAuthorising')
|
||||
);
|
||||
|
||||
export const getUser = createSelector(
|
||||
export const getGrant = createSelector(
|
||||
getUserState,
|
||||
R.pipe(R.path(['grant', 'user']), Maybe)
|
||||
R.pipe(R.prop('grant'), Maybe)
|
||||
);
|
||||
|
||||
export const getUser = createSelector(
|
||||
getGrant,
|
||||
R.map(R.prop('user'))
|
||||
);
|
||||
|
||||
@@ -51,6 +51,10 @@ const rawItems = {
|
||||
key: 'addLibrary',
|
||||
label: 'Add Library',
|
||||
},
|
||||
publish: {
|
||||
key: 'publish',
|
||||
label: 'Publish Library',
|
||||
},
|
||||
|
||||
edit: {
|
||||
key: 'edit',
|
||||
|
||||
@@ -30,7 +30,7 @@ export const getUtmSiteUrl = getUtmUrl(process.env.XOD_SITE_DOMAIN);
|
||||
// :: String -> String
|
||||
export const getUtmForumUrl = getUtmUrl(process.env.XOD_FORUM_DOMAIN, '', 'forum');
|
||||
|
||||
const HOSTNAME = process.env.XOD_HOSTNAME || 'xod.io';
|
||||
export const HOSTNAME = process.env.XOD_HOSTNAME || 'xod.io';
|
||||
|
||||
// :: () -> String
|
||||
export const getCompileLimitUrl = () =>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import R from 'ramda';
|
||||
import swaggerClient from 'swagger-client';
|
||||
import {
|
||||
retryOrFail,
|
||||
rejectFetchResult,
|
||||
@@ -11,7 +10,7 @@ import { fromXodballData, listMissingLibraryNames } from 'xod-project';
|
||||
|
||||
import * as ERR_CODES from './errorCodes';
|
||||
import * as MSG from './messages';
|
||||
import { parseLibQuery, unfoldMaybeLibQuery, rejectUnexistingVersion, getLibName } from './utils';
|
||||
import { parseLibQuery, unfoldMaybeLibQuery, rejectUnexistingVersion, getLibName, getSwaggerClient } from './utils';
|
||||
|
||||
// =============================================================================
|
||||
//
|
||||
@@ -25,22 +24,6 @@ const retryExcept404 = retryOrFail(
|
||||
res => (res.status && res.status === 404),
|
||||
);
|
||||
|
||||
// Create memoized function to prevent fetching swagger URL
|
||||
// on each fetch request
|
||||
const getSwaggerClient = (() => {
|
||||
const memo = {};
|
||||
|
||||
return (url) => {
|
||||
const index = JSON.stringify(url);
|
||||
|
||||
if (index in memo) {
|
||||
return memo[index];
|
||||
}
|
||||
|
||||
return (memo[index] = swaggerClient(url));
|
||||
};
|
||||
})();
|
||||
|
||||
// =============================================================================
|
||||
//
|
||||
// Fetching data
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
export * from './fetch';
|
||||
export { default as publish } from './publish';
|
||||
export * from './errorCodes';
|
||||
export {
|
||||
parseLibQuery,
|
||||
|
||||
14
packages/xod-pm/src/lib-uri.js
Normal file
14
packages/xod-pm/src/lib-uri.js
Normal file
@@ -0,0 +1,14 @@
|
||||
// TODO: duplicates xod-cli/lib-uri.js
|
||||
|
||||
import { Maybe } from 'ramda-fantasy';
|
||||
|
||||
export const createLibUri = (orgname, libname, tag) => ({ orgname, libname, tag });
|
||||
|
||||
export const parseLibUri = string => Maybe
|
||||
.toMaybe(string.match(/^([^@/]+?)\/([^@/]+?)(?:@([^@/]+?))?$/))
|
||||
.map(([, orgname, libname, tag]) =>
|
||||
createLibUri(orgname, libname, tag || 'latest'));
|
||||
|
||||
export const toStringWithoutTag = libUri => `${libUri.orgname}/${libUri.libname}`;
|
||||
|
||||
export const toString = libUri => `${toStringWithoutTag(libUri)}@${libUri.tag}`;
|
||||
61
packages/xod-pm/src/publish.js
Normal file
61
packages/xod-pm/src/publish.js
Normal file
@@ -0,0 +1,61 @@
|
||||
import * as XP from 'xod-project';
|
||||
|
||||
import { getSwaggerClient, swaggerError } from './utils';
|
||||
import { createLibUri, toString, toStringWithoutTag } from './lib-uri';
|
||||
|
||||
const packLibVersion = project => ({
|
||||
libname: XP.getProjectName(project),
|
||||
version: {
|
||||
description: XP.getProjectDescription(project),
|
||||
folder: { 'xodball.json': XP.toXodball(project) },
|
||||
semver: `v${XP.getProjectVersion(project)}`,
|
||||
},
|
||||
});
|
||||
|
||||
export default function publish(swaggerUrl, grant, project) {
|
||||
return getSwaggerClient(swaggerUrl)
|
||||
.then((swagger) => {
|
||||
const { Library, Organization, User, Version } = swagger.apis;
|
||||
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
swagger.authorizations = {
|
||||
bearer_token: `Bearer ${grant.accessToken}`,
|
||||
};
|
||||
const { username } = grant.user;
|
||||
const orgname = username;
|
||||
|
||||
const { libname, version } = packLibVersion(project);
|
||||
const libUri = createLibUri(orgname, libname, version.semver);
|
||||
|
||||
return Organization.getOrg({ orgname })
|
||||
.catch((err) => {
|
||||
if (err.status !== 404) throw swaggerError(err);
|
||||
return User.putUserOrg({ org: {}, orgname, username }).catch((err2) => {
|
||||
if (err2.status === 403) {
|
||||
throw new Error(`user "${username}" is not registered.`);
|
||||
}
|
||||
if (err2.status === 409) {
|
||||
throw new Error(`orgname "${orgname}" is already taken.`);
|
||||
}
|
||||
throw swaggerError(err2);
|
||||
});
|
||||
})
|
||||
.then(() => Library.getOrgLib({ libname, orgname }).catch((err) => {
|
||||
if (err.status !== 404) throw swaggerError(err);
|
||||
return Library.putOrgLib({ lib: {}, libname, orgname }).catch((err2) => {
|
||||
if (err2.status === 403) {
|
||||
throw new Error(`user "${username}" can't access ${
|
||||
toStringWithoutTag(libUri)}.`);
|
||||
}
|
||||
throw swaggerError(err2);
|
||||
});
|
||||
}))
|
||||
.then(() => Version.postLibVersion({ libname, orgname, version }).catch((err) => {
|
||||
if (err.status === 409) {
|
||||
throw new Error(`version "${toString(libUri)}" already exists.`);
|
||||
}
|
||||
throw swaggerError(err);
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import R from 'ramda';
|
||||
import swaggerClient from 'swagger-client';
|
||||
import { Maybe } from 'ramda-fantasy';
|
||||
import {
|
||||
notEmpty,
|
||||
@@ -120,3 +121,38 @@ export const getLibVersion = R.curry(
|
||||
)(versions);
|
||||
}
|
||||
);
|
||||
|
||||
// Create memoized function to prevent fetching swagger URL
|
||||
// on each fetch request
|
||||
export const getSwaggerClient = (() => {
|
||||
const memo = {};
|
||||
|
||||
return (url) => {
|
||||
const index = JSON.stringify(url);
|
||||
|
||||
if (index in memo) {
|
||||
return memo[index];
|
||||
}
|
||||
|
||||
return (memo[index] = swaggerClient(url));
|
||||
};
|
||||
})();
|
||||
|
||||
// TODO: duplicate in xod-cli
|
||||
export const swaggerError = (err) => {
|
||||
const { response, status } = err;
|
||||
let res;
|
||||
if (response.body && response.body.originalResponse) {
|
||||
res = JSON.parse(response.body.originalResponse);
|
||||
} else if (response.body) {
|
||||
res = response.body;
|
||||
} else {
|
||||
res = response;
|
||||
}
|
||||
|
||||
if (status === 400) {
|
||||
return new Error(R.pathOr('Bad request', ['errors', 0, 'errors', 0, 'message'], res));
|
||||
}
|
||||
|
||||
return new Error(`${status} ${JSON.stringify(res, null, 2)}`);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user