Skip to content
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
2 changes: 1 addition & 1 deletion README_INTERNAL.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ Add the line "HELLO WORLD" to the end of `README.md`
Then run this command to publish to your local Verdaccio instance:

```bash
./publish_local.sh
./scripts/publish_local.sh
```

## Install Your Updated NPM Package Locally
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "polyapi",
"version": "0.24.18",
"version": "0.24.19",
"description": "Poly is a CLI tool to help create and manage your Poly definitions.",
"license": "MIT",
"repository": {
Expand Down
70 changes: 61 additions & 9 deletions src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,21 +123,39 @@ export const createOrUpdateServerFunction = async (
).data;
};

export const getServerFunctionById = async (id: string) => {
return (
await axios.get<any, AxiosResponse<FunctionDetailsDto>>(
`${getApiBaseURL()}/functions/server/${id}`,
{
headers: {
'Content-Type': 'application/json',
...getApiHeaders(),
},
},
)
).data;
};

export const getServerFunctionByName = async (
context: string,
name: string,
detail = false
) => {
return (
await axios.get<any, AxiosResponse<FunctionBasicDto[]>>(
`${getApiBaseURL()}/functions/server`,
const basic = (
await axios.get<any, AxiosResponse<{ results: FunctionBasicDto[] }>>(
`${getApiBaseURL()}/functions/server?search=${encodeURIComponent(`${context}${context && name ? '.' : ''}${name}`)}`,
{
headers: {
'Content-Type': 'application/json',
...getApiHeaders(),
'x-poly-api-version': '2',
},
},
)
).data.find((fn) => fn.name === name && fn.context === context);
).data.results.find((fn) => fn.name === name && fn.context === context);
if (!detail || !basic) return basic;
return getServerFunctionById(basic.id);
};

export const deleteServerFunction = async (id: string) => {
Expand Down Expand Up @@ -178,21 +196,39 @@ export const createOrUpdateClientFunction = async (
).data;
};

export const getClientFunctionById = async (id: string) => {
return (
await axios.get<any, AxiosResponse<FunctionDetailsDto>>(
`${getApiBaseURL()}/functions/client/${id}`,
{
headers: {
'Content-Type': 'application/json',
...getApiHeaders(),
},
},
)
).data;
};

export const getClientFunctionByName = async (
context: string,
name: string,
detail = false,
) => {
return (
await axios.get<any, AxiosResponse<FunctionBasicDto[]>>(
`${getApiBaseURL()}/functions/client`,
const basic = (
await axios.get<any, AxiosResponse<{ results: FunctionBasicDto[] }>>(
`${getApiBaseURL()}/functions/client?search=${encodeURIComponent(`${context}${context && name ? '.' : ''}${name}`)}`,
{
headers: {
'Content-Type': 'application/json',
...getApiHeaders(),
'x-poly-api-version': '2',
},
},
)
).data.find((fn) => fn.name === name && fn.context === context);
).data.results.find((fn) => fn.name === name && fn.context === context);
if (!detail || !basic) return basic;
return getClientFunctionById(basic.id);
};

export const deleteClientFunction = async (id: string) => {
Expand Down Expand Up @@ -482,8 +518,22 @@ export const createOrUpdateWebhook = async (
).data;
};

export const getWebhookByName = async (context: string, name: string) => {
export const getWebhookById = async (id: string) => {
return (
await axios.get<any, AxiosResponse<WebhookHandleDto>>(
`${getApiBaseURL()}/webhooks/${id}`,
{
headers: {
'Content-Type': 'application/json',
...getApiHeaders(),
},
},
)
).data;
};

export const getWebhookByName = async (context: string, name: string, detail = false) => {
const basic = (
await axios.get<any, AxiosResponse<WebhookHandleBasicDto[]>>(
`${getApiBaseURL()}/webhooks`,
{
Expand All @@ -496,6 +546,8 @@ export const getWebhookByName = async (context: string, name: string) => {
).data.find(
(webhook) => webhook.name === name && webhook.context === context,
);
if (!detail || !basic) return basic;
return getWebhookById(basic.id);
};

export const deleteWebhook = async (webhookId: string) => {
Expand Down
93 changes: 66 additions & 27 deletions src/commands/sync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import {
getCacheDeploymentsRevision,
removeDeployableRecords,
prepareDeployableDirectory,
getDeployableFileRevision,
getRandomString,
} from '../deployables';
import {
createOrUpdateClientFunction,
Expand All @@ -19,14 +21,18 @@ import {
deleteClientFunction,
deleteServerFunction,
deleteWebhook,
getClientFunctionById,
getClientFunctionByName,
getServerFunctionById,
getServerFunctionByName,
getWebhookById,
getWebhookByName,
} from '../api';
import { FunctionDetailsDto, WebhookHandleDto } from '../types';

const DEPLOY_ORDER: DeployableTypes[] = [
'server-function',
'client-function',
'server-function',
'webhook',
];

Expand Down Expand Up @@ -105,6 +111,32 @@ const syncDeployableAndGetId = async (deployable, code) => {
throw new Error(`Unsupported deployable type: '${deployable.type}'`);
};

const getDeployableFromServer = async <T = FunctionDetailsDto | WebhookHandleDto>(
deployable: SyncDeployment,
): Promise<T | null | undefined> => {
try {
switch(deployable.type) {
case 'server-function': {
return deployable.id
? getServerFunctionById(deployable.id) as T
: getServerFunctionByName(deployable.context, deployable.name, true) as T;
}
case 'client-function': {
return deployable.id
? getClientFunctionById(deployable.id) as T
: getClientFunctionByName(deployable.context, deployable.name, true) as T;
}
case 'webhook': {
return deployable.id
? getWebhookById(deployable.id) as T
: getWebhookByName(deployable.context, deployable.name, true) as T;
}
}
} catch (err) {
return null;
}
}

const syncDeployable = async (
deployable: SyncDeployment,
): Promise<Deployment> => {
Expand Down Expand Up @@ -149,33 +181,51 @@ export const syncDeployables = async (
const previousDeployment = deployable.deployments.find(
(i) => i.instance === instance,
);
// Any deployable may be deployed to multiple instances/environments at the same time
// So we reduce the deployable record down to a single instance we want to deploy to
const syncDeployment: SyncDeployment = {
...deployable,
...previousDeployment, // flatten to grab name & context
type: deployable.type, // but make sure we use the latest type
description: deployable.description ?? deployable.types?.description,
instance,
};
const deployed = await getDeployableFromServer(syncDeployment);
const gitRevisionChanged = gitRevision !== deployable.gitRevision;
const fileRevisionChanged =
previousDeployment?.fileRevision !== deployable.fileRevision;
const serverFileRevision = !deployed
? ''
: type === 'webhook'
// TODO: Actually calculate real revision on webhook
? getRandomString(8)
: ((deployed as FunctionDetailsDto).hash || getDeployableFileRevision((deployed as FunctionDetailsDto).code));
const fileRevisionChanged = serverFileRevision !== deployable.fileRevision;
// TODO: If deployed variabnt exists AND was deployed after timestamp on previousDeployment then sync it back to the repo
let action = gitRevisionChanged
? 'REMOVED'
: !previousDeployment?.id
: !previousDeployment?.id && !deployed
? 'ADDED'
: fileRevisionChanged
? 'UPDATED'
: 'OK';
: 'SKIPPED';

if (!dryRun && (gitRevisionChanged || fileRevisionChanged)) {
if (!dryRun && action !== 'SKIPPED') {
// if user is changing type, ex. server -> client function or vice versa
// then try to cleanup the old type first
if (previousDeployment && deployable.type !== previousDeployment.type) {
await removeDeployable(previousDeployment);
}
// Any deployable may be deployed to multiple instances/environments at the same time
// So we reduce the deployable record down to a single instance we want to deploy to
const syncDeployment: SyncDeployment = {
...deployable,
...previousDeployment, // flatten to grab name & context
type: deployable.type, // but make sure we use the latest type
description: deployable.description ?? deployable.types?.description,
instance,
};
if (gitRevision === deployable.gitRevision) {
if (gitRevisionChanged) {
// This deployable no longer exists so let's remove it
const found = await removeDeployable(syncDeployment);
if (!found) action = 'NOT FOUND';
const removeIndex = allDeployables.findIndex(
(d) =>
d.name === deployable.name &&
d.context === deployable.context &&
d.file === deployable.file,
);
toRemove.push(...allDeployables.splice(removeIndex, 1));
} else {
const deployment = await syncDeployable(syncDeployment);
if (previousDeployment) {
previousDeployment.id = deployment.id;
Expand All @@ -187,17 +237,6 @@ export const syncDeployables = async (
} else {
deployable.deployments.unshift(deployment);
}
} else {
// This deployable no longer exists so let's remove it
const found = await removeDeployable(syncDeployment);
if (!found) action = 'NOT FOUND';
const removeIndex = allDeployables.findIndex(
(d) =>
d.name === deployable.name &&
d.context === deployable.context &&
d.file === deployable.file,
);
toRemove.push(...allDeployables.splice(removeIndex, 1));
}
}

Expand Down
15 changes: 9 additions & 6 deletions src/deployables.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,6 @@ const writeJsonFile = async <T = any>(
path: string,
contents: T,
): Promise<unknown> => {
await open(path, 'w');
return writeFile(path, JSON.stringify(contents, undefined, 2), {
encoding: 'utf8',
flag: 'w',
Expand Down Expand Up @@ -268,8 +267,14 @@ export const getDeployableFileRevision = (fileContents: string): string =>
fileContents.replace(/^(\/\/.*\n)+/, ''),
)
.digest('hex')
// Trimming to 7 characters to align with git revision format and to keep this nice and short!
.substring(0, 7);
// Trimming to 8 characters to align with git revision format and to keep this nice and short!
.substring(0, 8);

export const getRandomString = (length = 8) => {
return Array.from({ length }, () =>
Math.floor(Math.random() * 16).toString(16),
).join('');
}

export const getGitRevision = (branchOrTag = 'HEAD'): string => {
try {
Expand All @@ -281,9 +286,7 @@ export const getGitRevision = (branchOrTag = 'HEAD'): string => {
return result;
} catch (err) {
console.warn('Failed to get git revision. Falling back to random hash.');
return Array.from({ length: 8 }, () =>
Math.floor(Math.random() * 16).toString(16),
).join('');
return getRandomString(8);
}
};

Expand Down
9 changes: 9 additions & 0 deletions src/types/functions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,15 @@ export interface FunctionDetailsDto extends FunctionBasicDto {
* If there are some missing poly schemas in `returnTypeSchema`, they will be listed here.
*/
unresolvedReturnTypePolySchemaRefs?: SchemaRef[];
code: string;
language: string;
logsEnabled?: boolean;
serverSideAsync?: boolean;
minScale?: number | null;
maxScale?: number | null;
requirements?: string | null;
generateContexts?: string[] | null;
hash: string;
}

export interface EntrySource {
Expand Down
Loading