@netlify/api 13.3.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2020 Netlify <team@netlify.com>
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,182 @@
1
+ A Netlify [OpenAPI](https://github.com/netlify/open-api) client that works in the browser and Node.js.
2
+
3
+ ## Usage
4
+
5
+ ```js
6
+ import { NetlifyAPI } from '@netlify/api'
7
+
8
+ const client = new NetlifyAPI('1234myAccessToken')
9
+ const sites = await client.listSites()
10
+ ```
11
+
12
+ ## Using OpenAPI operations
13
+
14
+ ```js
15
+ import { NetlifyAPI } from '@netlify/api'
16
+
17
+ const client = new NetlifyAPI('1234myAccessToken')
18
+
19
+ // Fetch sites
20
+ const sites = await client.listSites()
21
+
22
+ // Create a site. Notice `body` here for sending OpenAPI body
23
+ const site = await client.createSite({
24
+ body: {
25
+ name: `my-awesome-site`,
26
+ // ... https://open-api.netlify.com/#/default/createSite
27
+ },
28
+ })
29
+
30
+ // Delete site. Notice `site_id` is a path parameter https://open-api.netlify.com/#/default/deleteSite
31
+ await client.deleteSite({ site_id: siteId })
32
+ ```
33
+
34
+ ## API
35
+
36
+ ### `client = new NetlifyAPI([accessToken], [opts])`
37
+
38
+ Create a new instance of the Netlify API client with the provided `accessToken`.
39
+
40
+ `accessToken` is optional. Without it, you can't make authorized requests.
41
+
42
+ `opts` includes:
43
+
44
+ ```js
45
+ const opts = {
46
+ userAgent: 'netlify/js-client',
47
+ scheme: 'https',
48
+ host: 'api.netlify.com',
49
+ pathPrefix: '/api/v1',
50
+ accessToken: '1234myAccessToken',
51
+ agent: undefined, // e.g. HttpsProxyAgent
52
+ globalParams: {}, // parameters you want available for every request.
53
+ // Global params are only sent of the OpenAPI spec specifies the provided params.
54
+ }
55
+ ```
56
+
57
+ ### `client.accessToken`
58
+
59
+ A setter/getter that returns the `accessToken` that the client is configured to use. You can set this after the class is
60
+ instantiated, and all subsequent calls will use the newly set `accessToken`.
61
+
62
+ ### `client.basePath`
63
+
64
+ A getter that returns the formatted base URL of the endpoint the client is configured to use.
65
+
66
+ ### OpenAPI Client methods
67
+
68
+ The client is dynamically generated from the [OpenAPI](https://github.com/netlify/open-api) definition file. Each method
69
+ is is named after the `operationId` name of each operation. **To see a list of available operations, please see the
70
+ [OpenAPI website](https://open-api.netlify.com/)**.
71
+
72
+ Every OpenAPI operation has the following signature:
73
+
74
+ #### `response = await client.operationId([params], [opts])`
75
+
76
+ Performs a call to the given endpoint corresponding with the `operationId`. Returns a promise resolved with the body of
77
+ the response, or rejected with an error with the details about the request attached. Rejects if the `status` > 400.
78
+
79
+ - `params` is an object that includes any of the required or optional endpoint parameters.
80
+ - `params.body` should be an object which gets serialized to JSON automatically. Any object can live here but refer to
81
+ the OpenAPI specification for allowed fields in a particular request body. It can also be a function returning an
82
+ object.
83
+ - If the endpoint accepts `binary`, `params.body` can be a Node.js readable stream or a function returning one (e.g.
84
+ `() => fs.createReadStream('./foo')`). Using a function is recommended.
85
+
86
+ ```js
87
+ // example params
88
+ const params = {
89
+ any_param_needed: anyParamNeeded,
90
+ paramsCanAlsoBeCamelCase,
91
+ body: {
92
+ an: 'arbitrary js object',
93
+ },
94
+ }
95
+ ```
96
+
97
+ Optional `opts` can include any property you want passed to [`node-fetch`](https://github.com/bitinn/node-fetch). The
98
+ `headers` property is merged with some `defaultHeaders`.
99
+
100
+ ```js
101
+ // example opts
102
+ const opts = {
103
+ headers: {
104
+ // Default headers
105
+ 'User-agent': 'netlify-js-client',
106
+ accept: 'application/json',
107
+ },
108
+ // any other properties for node-fetch
109
+ }
110
+ ```
111
+
112
+ All operations are conveniently consumed with async/await:
113
+
114
+ ```js
115
+ try {
116
+ const siteDeploy = await client.getSiteDeploy({
117
+ siteId: '1234abcd',
118
+ deploy_id: '4567',
119
+ })
120
+ // Calls may fail!
121
+ } catch {
122
+ // handle error
123
+ }
124
+ ```
125
+
126
+ If the response includes `json` in the `contentType` header, fetch will deserialize the JSON body. Otherwise the `text`
127
+ of the response is returned.
128
+
129
+ ### API Flow Methods
130
+
131
+ Some methods have been added in addition to the open API operations that make certain actions simpler to perform.
132
+
133
+ #### `accessToken = await client.getAccessToken(ticket, [opts])`
134
+
135
+ Pass in a [`ticket`](https://open-api.netlify.com/#model-ticket) and get back an `accessToken`. Call this with the
136
+ response from a `client.createTicket({ client_id })` call. Automatically sets the `accessToken` to `this.accessToken`
137
+ and returns `accessToken` for the consumer to save for later.
138
+
139
+ Optional `opts` include:
140
+
141
+ ```js
142
+ const opts = {
143
+ poll: 1000, // number of ms to wait between polling
144
+ timeout: 3.6e6, // number of ms to wait before timing out
145
+ }
146
+ ```
147
+
148
+ See the [authenticating](https://www.netlify.com/docs/api/#authenticating) docs for more context.
149
+
150
+ ```js
151
+ import open from 'open'
152
+
153
+ const ticket = await client.createTicket({ clientId: CLIENT_ID })
154
+ // Open browser for authentication
155
+ await open(`https://app.netlify.com/authorize?response_type=ticket&ticket=${ticket.id}`)
156
+
157
+ // API is also set up to use the returned access token as a side effect
158
+ // Save this for later so you can quickly set up an authenticated client
159
+ const accessToken = await client.getAccessToken(ticket)
160
+ ```
161
+
162
+ ## Proxy support
163
+
164
+ **Node.js only**: If this client is used behind a corporate proxy, you can pass an `HttpsProxyAgent` or any other
165
+ `http.Agent` that can handle your situation as `agent` option:
166
+
167
+ ```js
168
+ import HttpsProxyAgent from 'https-proxy-agent'
169
+
170
+ const proxyUri = 'http(s)://[user:password@]proxyhost:port'
171
+ const agent = new HttpsProxyAgent(proxyUri)
172
+ const client = new NetlifyAPI('1234myAccessToken', { agent })
173
+ ```
174
+
175
+ ## Site deployment
176
+
177
+ Support for site deployment has been removed from this package in version 7.0.0. You should consider using the
178
+ [`deploy` command](https://cli.netlify.com/commands/deploy/) of Netlify CLI.
179
+
180
+ ## License
181
+
182
+ MIT. See [LICENSE](./LICENSE) for more details.
package/lib/index.d.ts ADDED
@@ -0,0 +1,54 @@
1
+ import type { DynamicMethods } from './types.js';
2
+ type APIOptions = {
3
+ /** @example 'netlify/js-client' */
4
+ userAgent?: string;
5
+ /** @example 'https' */
6
+ scheme?: string;
7
+ /** @example 'api.netlify.com' */
8
+ host?: string;
9
+ /** @example '/api/v1' */
10
+ pathPrefix?: string;
11
+ accessToken?: string;
12
+ /** @example 'HttpsProxyAgent' */
13
+ agent?: string;
14
+ /**
15
+ * parameters you want available for every request.
16
+ * Global params are only sent of the OpenAPI spec specifies the provided params.
17
+ */
18
+ globalParams?: Record<string, unknown>;
19
+ };
20
+ /**
21
+ * The Netlify API client.
22
+ * @see {@link https://open-api.netlify.com | Open API Reference}
23
+ * @see {@link https://docs.netlify.com/api/get-started | Online Documentation}
24
+ *
25
+ * @example
26
+ * ```ts
27
+ * const client = new NetlifyAPI('YOUR_ACCESS_TOKEN')
28
+ * const sites = await client.listSites()
29
+ * ```
30
+ */
31
+ export interface NetlifyAPI extends DynamicMethods {
32
+ }
33
+ export declare class NetlifyAPI {
34
+ #private;
35
+ defaultHeaders: Record<string, string>;
36
+ /** The protocol is used like `https` */
37
+ scheme: string;
38
+ host: string;
39
+ pathPrefix: string;
40
+ agent?: string;
41
+ globalParams: Record<string, unknown>;
42
+ constructor(options?: APIOptions);
43
+ constructor(accessToken: string | undefined, options?: APIOptions);
44
+ /** Retrieves the access token */
45
+ get accessToken(): string | undefined | null;
46
+ set accessToken(token: string | undefined | null);
47
+ get basePath(): string;
48
+ getAccessToken(ticket: any, { poll, timeout }?: {
49
+ poll?: number | undefined;
50
+ timeout?: number | undefined;
51
+ }): Promise<string | undefined>;
52
+ }
53
+ export declare const methods: any[];
54
+ export {};
package/lib/index.js ADDED
@@ -0,0 +1,78 @@
1
+ import pWaitFor from 'p-wait-for';
2
+ import { getMethods } from './methods/index.js';
3
+ import { openApiSpec } from './open_api.js';
4
+ import { getOperations } from './operations.js';
5
+ // 1 second
6
+ const DEFAULT_TICKET_POLL = 1e3;
7
+ // 1 hour
8
+ const DEFAULT_TICKET_TIMEOUT = 3.6e6;
9
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging
10
+ export class NetlifyAPI {
11
+ #accessToken = null;
12
+ defaultHeaders = {
13
+ accept: 'application/json',
14
+ };
15
+ /** The protocol is used like `https` */
16
+ scheme;
17
+ host;
18
+ pathPrefix;
19
+ agent;
20
+ globalParams = {};
21
+ constructor(firstArg, secondArg) {
22
+ // variadic arguments
23
+ const [accessTokenInput, options = {}] = typeof firstArg === 'object' ? [null, firstArg] : [firstArg, secondArg];
24
+ this.globalParams = options.globalParams || {};
25
+ this.agent = options.agent;
26
+ this.scheme = options.scheme || openApiSpec.schemes[0];
27
+ this.host = options.host || openApiSpec.host;
28
+ this.pathPrefix = options.pathPrefix || openApiSpec.basePath;
29
+ // use the setter to set the header as well
30
+ this.accessToken = options.accessToken || accessTokenInput || null;
31
+ this.defaultHeaders['User-agent'] = options.userAgent || 'netlify/js-client';
32
+ const methods = getMethods({
33
+ basePath: this.basePath,
34
+ defaultHeaders: this.defaultHeaders,
35
+ agent: this.agent,
36
+ globalParams: this.globalParams,
37
+ });
38
+ Object.assign(this, { ...methods });
39
+ }
40
+ /** Retrieves the access token */
41
+ get accessToken() {
42
+ return this.#accessToken;
43
+ }
44
+ set accessToken(token) {
45
+ if (!token) {
46
+ delete this.defaultHeaders.Authorization;
47
+ this.#accessToken = null;
48
+ return;
49
+ }
50
+ this.#accessToken = token;
51
+ this.defaultHeaders.Authorization = `Bearer ${this.#accessToken}`;
52
+ }
53
+ get basePath() {
54
+ return `${this.scheme}://${this.host}${this.pathPrefix}`;
55
+ }
56
+ async getAccessToken(ticket, { poll = DEFAULT_TICKET_POLL, timeout = DEFAULT_TICKET_TIMEOUT } = {}) {
57
+ const { id } = ticket;
58
+ // ticket capture
59
+ let authorizedTicket;
60
+ const checkTicket = async () => {
61
+ const t = await this.showTicket({ ticketId: id });
62
+ if (t.authorized) {
63
+ authorizedTicket = t;
64
+ }
65
+ return Boolean(t.authorized);
66
+ };
67
+ await pWaitFor(checkTicket, {
68
+ interval: poll,
69
+ timeout,
70
+ message: 'Timeout while waiting for ticket grant',
71
+ });
72
+ const accessTokenResponse = await this.exchangeTicket({ ticketId: authorizedTicket.id });
73
+ // See https://open-api.netlify.com/#/default/exchangeTicket for shape
74
+ this.accessToken = accessTokenResponse.access_token;
75
+ return accessTokenResponse.access_token;
76
+ }
77
+ }
78
+ export const methods = getOperations();
@@ -0,0 +1 @@
1
+ export function addBody(body: any, parameters: any, opts: any): any;
@@ -0,0 +1,25 @@
1
+ // Handle request body
2
+ export const addBody = function (body, parameters, opts) {
3
+ if (!body) {
4
+ return opts;
5
+ }
6
+ const bodyA = typeof body === 'function' ? body() : body;
7
+ if (isBinaryBody(parameters)) {
8
+ return {
9
+ ...opts,
10
+ body: bodyA,
11
+ headers: { 'Content-Type': 'application/octet-stream', ...opts.headers },
12
+ };
13
+ }
14
+ return {
15
+ ...opts,
16
+ body: JSON.stringify(bodyA),
17
+ headers: { 'Content-Type': 'application/json', ...opts.headers },
18
+ };
19
+ };
20
+ const isBinaryBody = function (parameters) {
21
+ return Object.values(parameters.body).some(isBodyParam);
22
+ };
23
+ const isBodyParam = function ({ schema }) {
24
+ return schema && schema.format === 'binary';
25
+ };
@@ -0,0 +1,6 @@
1
+ export function getMethods({ basePath, defaultHeaders, agent, globalParams }: {
2
+ basePath: any;
3
+ defaultHeaders: any;
4
+ agent: any;
5
+ globalParams: any;
6
+ }): any;
@@ -0,0 +1,84 @@
1
+ import fetch from 'node-fetch';
2
+ import { getOperations } from '../operations.js';
3
+ import { addBody } from './body.js';
4
+ import { getRequestParams } from './params.js';
5
+ import { parseResponse, getFetchError } from './response.js';
6
+ import { shouldRetry, waitForRetry, MAX_RETRY } from './retry.js';
7
+ import { getUrl } from './url.js';
8
+ // For each OpenAPI operation, add a corresponding method.
9
+ // The `operationId` is the method name.
10
+ export const getMethods = function ({ basePath, defaultHeaders, agent, globalParams }) {
11
+ const operations = getOperations();
12
+ const methods = operations.map((method) => getMethod({ method, basePath, defaultHeaders, agent, globalParams }));
13
+ return Object.assign({}, ...methods);
14
+ };
15
+ const getMethod = function ({ method, basePath, defaultHeaders, agent, globalParams }) {
16
+ return {
17
+ [method.operationId](params, opts) {
18
+ return callMethod({ method, basePath, defaultHeaders, agent, globalParams, params, opts });
19
+ },
20
+ };
21
+ };
22
+ const callMethod = async function ({ method, basePath, defaultHeaders, agent, globalParams, params, opts }) {
23
+ const requestParams = { ...globalParams, ...params };
24
+ const url = getUrl(method, basePath, requestParams);
25
+ const response = await makeRequestOrRetry({ url, method, defaultHeaders, agent, requestParams, opts });
26
+ const parsedResponse = await parseResponse(response);
27
+ return parsedResponse;
28
+ };
29
+ const getOpts = function ({ method: { verb, parameters }, defaultHeaders, agent, requestParams, opts }) {
30
+ const { body } = requestParams;
31
+ const optsA = addHttpMethod(verb, opts);
32
+ const optsB = addHeaderParams(parameters, requestParams, optsA);
33
+ const optsC = addDefaultHeaders(defaultHeaders, optsB);
34
+ const optsD = addBody(body, parameters, optsC);
35
+ const optsE = addAgent(agent, optsD);
36
+ return optsE;
37
+ };
38
+ // Add header parameters
39
+ const addHeaderParams = function (parameters, requestParams, opts) {
40
+ if (parameters.header === undefined) {
41
+ return opts;
42
+ }
43
+ return { ...opts, headers: getRequestParams(parameters.header, requestParams, 'header parameter') };
44
+ };
45
+ // Add the HTTP method based on the OpenAPI definition
46
+ const addHttpMethod = function (verb, opts) {
47
+ return { ...opts, method: verb.toUpperCase() };
48
+ };
49
+ // Assign default HTTP headers
50
+ const addDefaultHeaders = function (defaultHeaders, opts) {
51
+ return { ...opts, headers: { ...defaultHeaders, ...opts.headers } };
52
+ };
53
+ // Assign fetch agent (like for example HttpsProxyAgent) if there is one
54
+ const addAgent = function (agent, opts) {
55
+ if (agent) {
56
+ return { ...opts, agent };
57
+ }
58
+ return opts;
59
+ };
60
+ const makeRequestOrRetry = async function ({ url, method, defaultHeaders, agent, requestParams, opts }) {
61
+ // Using a loop is simpler here
62
+ for (let index = 0; index <= MAX_RETRY; index++) {
63
+ const optsA = getOpts({ method, defaultHeaders, agent, requestParams, opts });
64
+ const { response, error } = await makeRequest(url, optsA);
65
+ if (shouldRetry({ response, error, method }) && index !== MAX_RETRY) {
66
+ await waitForRetry(response);
67
+ continue;
68
+ }
69
+ if (error !== undefined) {
70
+ throw error;
71
+ }
72
+ return response;
73
+ }
74
+ };
75
+ const makeRequest = async function (url, opts) {
76
+ try {
77
+ const response = await fetch(url, opts);
78
+ return { response };
79
+ }
80
+ catch (error) {
81
+ const errorA = getFetchError(error, url, opts);
82
+ return { error: errorA };
83
+ }
84
+ };
@@ -0,0 +1 @@
1
+ export function getRequestParams(params: any, requestParams: any, name: any): any;
@@ -0,0 +1,14 @@
1
+ import { camelCase } from 'lodash-es';
2
+ export const getRequestParams = function (params, requestParams, name) {
3
+ const entries = Object.values(params).map((param) => getRequestParam(param, requestParams, name));
4
+ return Object.assign({}, ...entries);
5
+ };
6
+ const getRequestParam = function (param, requestParams, name) {
7
+ const value = requestParams[param.name] || requestParams[camelCase(param.name)];
8
+ if (value !== undefined) {
9
+ return { [param.name]: value };
10
+ }
11
+ if (param.required) {
12
+ throw new Error(`Missing required ${name} '${param.name}'`);
13
+ }
14
+ };
@@ -0,0 +1,2 @@
1
+ export function parseResponse(response: any): Promise<any>;
2
+ export function getFetchError(error: any, url: any, opts: any): any;
@@ -0,0 +1,44 @@
1
+ import { JSONHTTPError, TextHTTPError } from 'micro-api-client';
2
+ import omit from '../omit.js';
3
+ // Read and parse the HTTP response
4
+ export const parseResponse = async function (response) {
5
+ const responseType = getResponseType(response);
6
+ const textResponse = await response.text();
7
+ const parsedResponse = parseJsonResponse(response, textResponse, responseType);
8
+ if (!response.ok) {
9
+ const ErrorType = responseType === 'json' ? JSONHTTPError : TextHTTPError;
10
+ throw addFallbackErrorMessage(new ErrorType(response, parsedResponse), textResponse);
11
+ }
12
+ return parsedResponse;
13
+ };
14
+ const getResponseType = function ({ headers }) {
15
+ const contentType = headers.get('Content-Type');
16
+ if (contentType != null && contentType.includes('json')) {
17
+ return 'json';
18
+ }
19
+ return 'text';
20
+ };
21
+ const parseJsonResponse = function (response, textResponse, responseType) {
22
+ if (responseType === 'text') {
23
+ return textResponse;
24
+ }
25
+ try {
26
+ return JSON.parse(textResponse);
27
+ }
28
+ catch {
29
+ throw addFallbackErrorMessage(new TextHTTPError(response, textResponse), textResponse);
30
+ }
31
+ };
32
+ const addFallbackErrorMessage = function (error, textResponse) {
33
+ error.message = error.message || textResponse;
34
+ return error;
35
+ };
36
+ export const getFetchError = function (error, url, opts) {
37
+ const data = omit(opts, ['Authorization']);
38
+ if (error.name !== 'FetchError') {
39
+ error.name = 'FetchError';
40
+ }
41
+ error.url = url;
42
+ error.data = data;
43
+ return error;
44
+ };
@@ -0,0 +1,7 @@
1
+ export function shouldRetry({ response, error, method }: {
2
+ response?: {} | undefined;
3
+ error?: {} | undefined;
4
+ method?: {} | undefined;
5
+ }): boolean;
6
+ export function waitForRetry(response: any): Promise<void>;
7
+ export const MAX_RETRY: 5;
@@ -0,0 +1,40 @@
1
+ // We retry:
2
+ // - when receiving a rate limiting response
3
+ // - on network failures due to timeouts
4
+ export const shouldRetry = function ({ response = {}, error = {}, method = {} }) {
5
+ if (response.status === RATE_LIMIT_STATUS || RETRY_ERROR_CODES.has(error.code)) {
6
+ return true;
7
+ }
8
+ // Special case for the `getLatestPluginRuns` endpoint.
9
+ // See https://github.com/netlify/bitballoon/issues/9616.
10
+ if (method.operationId === 'getLatestPluginRuns' && response.status === 500) {
11
+ return true;
12
+ }
13
+ return false;
14
+ };
15
+ export const waitForRetry = async function (response) {
16
+ const delay = getDelay(response);
17
+ await sleep(delay);
18
+ };
19
+ const getDelay = function (response) {
20
+ if (response === undefined) {
21
+ return DEFAULT_RETRY_DELAY;
22
+ }
23
+ const rateLimitReset = response.headers.get(RATE_LIMIT_HEADER);
24
+ if (!rateLimitReset) {
25
+ return DEFAULT_RETRY_DELAY;
26
+ }
27
+ return Math.max(Number(rateLimitReset) * SECS_TO_MSECS - Date.now(), MIN_RETRY_DELAY);
28
+ };
29
+ const sleep = function (ms) {
30
+ return new Promise((resolve) => {
31
+ setTimeout(resolve, ms);
32
+ });
33
+ };
34
+ const DEFAULT_RETRY_DELAY = 5e3;
35
+ const MIN_RETRY_DELAY = 1e3;
36
+ const SECS_TO_MSECS = 1e3;
37
+ export const MAX_RETRY = 5;
38
+ const RATE_LIMIT_STATUS = 429;
39
+ const RATE_LIMIT_HEADER = 'X-RateLimit-Reset';
40
+ const RETRY_ERROR_CODES = new Set(['ETIMEDOUT', 'ECONNRESET']);
@@ -0,0 +1,4 @@
1
+ export function getUrl({ path, parameters }: {
2
+ path: any;
3
+ parameters: any;
4
+ }, basePath: any, requestParams: any): any;
@@ -0,0 +1,24 @@
1
+ import queryString from 'qs';
2
+ import { getRequestParams } from './params.js';
3
+ // Replace path parameters and query parameters in the URI, using the OpenAPI
4
+ // definition
5
+ export const getUrl = function ({ path, parameters }, basePath, requestParams) {
6
+ const url = `${basePath}${path}`;
7
+ const urlA = addPathParams(url, parameters, requestParams);
8
+ const urlB = addQueryParams(urlA, parameters, requestParams);
9
+ return urlB;
10
+ };
11
+ const addPathParams = function (url, parameters, requestParams) {
12
+ const pathParams = getRequestParams(parameters.path, requestParams, 'path variable');
13
+ return Object.entries(pathParams).reduce(addPathParam, url);
14
+ };
15
+ const addPathParam = function (url, [name, value]) {
16
+ return url.replace(`{${name}}`, value);
17
+ };
18
+ const addQueryParams = function (url, parameters, requestParams) {
19
+ const queryParams = getRequestParams(parameters.query, requestParams, 'query variable');
20
+ if (Object.keys(queryParams).length === 0) {
21
+ return url;
22
+ }
23
+ return `${url}?${queryString.stringify(queryParams, { arrayFormat: 'brackets' })}`;
24
+ };
package/lib/omit.d.ts ADDED
@@ -0,0 +1 @@
1
+ export default function omit<T extends Record<string | number | symbol, unknown>, K extends keyof T>(obj: T, keys: K[]): Omit<T, K>;
package/lib/omit.js ADDED
@@ -0,0 +1,7 @@
1
+ export default function omit(obj, keys) {
2
+ const shallowCopy = { ...obj };
3
+ for (const key of keys) {
4
+ delete shallowCopy[key];
5
+ }
6
+ return shallowCopy;
7
+ }
@@ -0,0 +1 @@
1
+ export const openApiSpec: any;
@@ -0,0 +1,5 @@
1
+ import { createRequire } from 'module';
2
+ // TODO: remove once Node.js supports JSON imports with pure ES modules without
3
+ // any experimental flags
4
+ const require = createRequire(import.meta.url);
5
+ export const openApiSpec = require('@netlify/open-api');
@@ -0,0 +1 @@
1
+ export function getOperations(): any[];
@@ -0,0 +1,19 @@
1
+ import omit from './omit.js';
2
+ import { openApiSpec } from './open_api.js';
3
+ // Retrieve all OpenAPI operations
4
+ export const getOperations = function () {
5
+ return Object.entries(openApiSpec.paths).flatMap(([path, pathItem]) => {
6
+ const operations = omit(pathItem, ['parameters']);
7
+ return Object.entries(operations).map(([method, operation]) => {
8
+ const parameters = getParameters(pathItem.parameters, operation.parameters);
9
+ return { ...operation, verb: method, path, parameters };
10
+ });
11
+ });
12
+ };
13
+ const getParameters = function (pathParameters = [], operationParameters = []) {
14
+ const parameters = [...pathParameters, ...operationParameters];
15
+ return parameters.reduce(addParameter, { path: {}, query: {}, body: {} });
16
+ };
17
+ const addParameter = function (parameters, param) {
18
+ return { ...parameters, [param.in]: { ...parameters[param.in], [param.name]: param } };
19
+ };
package/lib/types.d.ts ADDED
@@ -0,0 +1,129 @@
1
+ import type { ReadStream } from 'node:fs';
2
+ import type { operations } from '@netlify/open-api';
3
+ import type { RequestInit } from 'node-fetch';
4
+ /**
5
+ * Determines whether all keys in T are optional.
6
+ */
7
+ type AreAllOptional<T> = keyof T extends never ? true : T extends Record<string, any> ? {
8
+ [K in keyof T]-?: undefined extends T[K] ? never : K;
9
+ }[keyof T] extends never ? true : false : false;
10
+ /**
11
+ * Determines whether `path` and `query` are both optional.
12
+ */
13
+ type IsPathAndQueryOptional<K extends keyof operations> = 'parameters' extends keyof operations[K] ? AreAllOptional<'path' extends keyof operations[K]['parameters'] ? operations[K]['parameters']['path'] : object> extends true ? AreAllOptional<'query' extends keyof operations[K]['parameters'] ? operations[K]['parameters']['query'] : object> extends true ? true : false : false : true;
14
+ /**
15
+ * Converts snake_case to camelCase for TypeScript types.
16
+ */
17
+ type CamelCase<S extends string> = S extends `${infer T}_${infer U}` ? `${T}${Capitalize<CamelCase<U>>}` : S;
18
+ /**
19
+ * Creates a union of both snake_case and camelCase keys with their respective types.
20
+ */
21
+ type SnakeToCamel<T> = {
22
+ [K in keyof T as CamelCase<K & string>]: T[K];
23
+ };
24
+ /**
25
+ * Combines snake_case and camelCase parameters into one Params type.
26
+ */
27
+ type Params<T> = SnakeToCamel<T> | T;
28
+ type HasRequestBody<K extends keyof operations> = 'requestBody' extends keyof operations[K] ? operations[K]['requestBody'] extends {
29
+ content: any;
30
+ } ? 'application/json' extends keyof operations[K]['requestBody']['content'] ? true : 'application/octet-stream' extends keyof operations[K]['requestBody']['content'] ? true : false : false : false;
31
+ /**
32
+ * Extracts the request body type from the operation.
33
+ */
34
+ type RequestBody<K extends keyof operations> = 'requestBody' extends keyof operations[K] ? operations[K]['requestBody'] extends {
35
+ content: any;
36
+ } ? 'application/json' extends keyof operations[K]['requestBody']['content'] ? operations[K]['requestBody']['content']['application/json'] : 'application/octet-stream' extends keyof operations[K]['requestBody']['content'] ? ReadStream | (() => ReadStream) : never : never : never;
37
+ type IsRequestBodyJson<K extends keyof operations> = 'requestBody' extends keyof operations[K] ? operations[K]['requestBody'] extends {
38
+ content: any;
39
+ } ? 'application/json' extends keyof operations[K]['requestBody']['content'] ? true : false : false : false;
40
+ type IsRequestBodyOctetStream<K extends keyof operations> = 'requestBody' extends keyof operations[K] ? operations[K]['requestBody'] extends {
41
+ content: any;
42
+ } ? 'application/octet-stream' extends keyof operations[K]['requestBody']['content'] ? true : false : false : false;
43
+ type RequestBodyParam<K extends keyof operations> = HasRequestBody<K> extends true ? IsRequestBodyOptional<K> extends true ? DetailedRequestBodyOptional<K> : DetailedRequestBody<K> : never;
44
+ type DetailedRequestBody<K extends keyof operations> = IsRequestBodyJson<K> extends true ? {
45
+ /**
46
+ * The request body for `application/json`.
47
+ * Automatically serialized to JSON based on the operation.
48
+ * Can be a JSON object or a function returning one.
49
+ */
50
+ body: RequestBody<K> | (() => RequestBody<K>);
51
+ } : IsRequestBodyOctetStream<K> extends true ? {
52
+ /**
53
+ * The request body for `application/octet-stream`.
54
+ * Can be a Node.js readable stream or a function returning one
55
+ * @example
56
+ * fs.createReadStream('./file')
57
+ * @example
58
+ * () => fs.createReadStream('./file')
59
+ */
60
+ body: ReadStream | (() => ReadStream);
61
+ } : never;
62
+ type DetailedRequestBodyOptional<K extends keyof operations> = IsRequestBodyJson<K> extends true ? {
63
+ /**
64
+ * The request body for `application/json`.
65
+ * Automatically serialized to JSON based on the operation.
66
+ * Can be a JSON object or a function returning one.
67
+ */
68
+ body?: RequestBody<K> | (() => RequestBody<K>);
69
+ } : IsRequestBodyOctetStream<K> extends true ? {
70
+ /**
71
+ * The request body for `application/octet-stream`.
72
+ * Can be a Node.js readable stream or a function returning one
73
+ * @example
74
+ * fs.createReadStream('./file')
75
+ * @example
76
+ * () => fs.createReadStream('./file')
77
+ */
78
+ body?: ReadStream | (() => ReadStream);
79
+ } : never;
80
+ /**
81
+ * Determines whether all properties in the request body are optional.
82
+ */
83
+ type IsRequestBodyOptional<K extends keyof operations> = HasRequestBody<K> extends true ? (AreAllOptional<RequestBody<K>> extends true ? true : false) : true;
84
+ /**
85
+ * Determines whether any parameters or request body are required.
86
+ */
87
+ type IsParamsOrRequestBodyRequired<K extends keyof operations> = 'parameters' extends keyof operations[K] ? IsPathAndQueryOptional<K> extends true ? IsRequestBodyOptional<K> extends true ? false : true : true : false;
88
+ /**
89
+ * Extracts and combines `path` and `query` parameters into a single type.
90
+ */
91
+ type ExtractPathAndQueryParameters<K extends keyof operations> = 'parameters' extends keyof operations[K] ? 'path' extends keyof operations[K]['parameters'] ? 'query' extends keyof operations[K]['parameters'] ? Params<Omit<operations[K]['parameters']['path'], keyof operations[K]['parameters']['query']> & operations[K]['parameters']['query']> : Params<operations[K]['parameters']['path']> : 'query' extends keyof operations[K]['parameters'] ? Params<operations[K]['parameters']['query']> : undefined : undefined;
92
+ /**
93
+ * Combines path, query, and request body parameters into a single type.
94
+ */
95
+ type CombinedParamsAndRequestBody<K extends keyof operations> = HasRequestBody<K> extends true ? ExtractPathAndQueryParameters<K> & RequestBodyParam<K> : ExtractPathAndQueryParameters<K>;
96
+ type OperationParams<K extends keyof operations> = IsParamsOrRequestBodyRequired<K> extends false ? CombinedParamsAndRequestBody<K> | void | undefined : CombinedParamsAndRequestBody<K>;
97
+ type SuccessHttpStatusCodes = 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 226;
98
+ /**
99
+ * Extracts the response type from the operation.
100
+ */
101
+ type OperationResponse<K extends keyof operations> = 'responses' extends keyof operations[K] ? SuccessHttpStatusCodes extends infer StatusKeys ? StatusKeys extends keyof operations[K]['responses'] ? 'content' extends keyof operations[K]['responses'][StatusKeys] ? 'application/json' extends keyof operations[K]['responses'][StatusKeys]['content'] ? operations[K]['responses'][StatusKeys]['content']['application/json'] : never : never : never : never : never;
102
+ export type DynamicMethods = {
103
+ [K in keyof operations]: (params: OperationParams<K>,
104
+ /**
105
+ * Any properties you want passed to `node-fetch`.
106
+ *
107
+ * The `headers` property is merged with some `defaultHeaders`:
108
+ * ```ts
109
+ * {
110
+ * 'User-agent': 'netlify-js-client',
111
+ * 'accept': 'application/json',
112
+ * }
113
+ * ```
114
+ *
115
+ * @example
116
+ * ```ts
117
+ * const site = await client.getSite(
118
+ * { site_id: 'YOUR_SITE_ID' },
119
+ * {
120
+ * headers: {
121
+ * 'X-Example-Header': 'Example Value',
122
+ * },
123
+ * },
124
+ * )
125
+ * ```
126
+ */
127
+ opts?: RequestInit | void | undefined) => Promise<OperationResponse<K>>;
128
+ };
129
+ export {};
package/lib/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
package/package.json ADDED
@@ -0,0 +1,66 @@
1
+ {
2
+ "name": "@netlify/api",
3
+ "description": "Netlify Node.js API client",
4
+ "version": "13.3.5",
5
+ "type": "module",
6
+ "exports": "./lib/index.js",
7
+ "main": "./lib/index.js",
8
+ "types": "./lib/index.d.ts",
9
+ "files": [
10
+ "lib/**"
11
+ ],
12
+ "scripts": {
13
+ "prebuild": "rm -rf lib",
14
+ "build": "tsc",
15
+ "test": "ava",
16
+ "test:dev": "ava -w",
17
+ "test:ci": "c8 -r lcovonly -r text -r json ava"
18
+ },
19
+ "license": "MIT",
20
+ "author": "Netlify Inc.",
21
+ "contributors": [
22
+ "Mathias Biilmann <matt@netlify.com> (https://twitter.com/biilmann)",
23
+ "David Calavera <david@netlify.com> (https://twitter.com/calavera)",
24
+ "David Wells <david.wells@netlify.com> (https://davidwells.io/)",
25
+ "Bret Comnes <bcomnes@gmail.com> (https://bret.io)",
26
+ "Lukas Holzer <lukas.holzer@netlify.com> (https://twitter.com/luka5c0m)"
27
+ ],
28
+ "homepage": "https://github.com/netlify/build",
29
+ "repository": {
30
+ "type": "git",
31
+ "url": "https://github.com/netlify/build.git",
32
+ "directory": "packages/js-client"
33
+ },
34
+ "bugs": {
35
+ "url": "https://github.com/netlify/build/issues"
36
+ },
37
+ "keywords": [
38
+ "api client",
39
+ "js client",
40
+ "netlify",
41
+ "node client"
42
+ ],
43
+ "dependencies": {
44
+ "@netlify/open-api": "^2.37.0",
45
+ "lodash-es": "^4.17.21",
46
+ "micro-api-client": "^3.3.0",
47
+ "node-fetch": "^3.0.0",
48
+ "p-wait-for": "^5.0.0",
49
+ "qs": "^6.9.6"
50
+ },
51
+ "devDependencies": {
52
+ "@types/lodash-es": "^4.17.6",
53
+ "@types/node": "^14.18.53",
54
+ "ava": "^4.0.0",
55
+ "c8": "^7.11.0",
56
+ "from2-string": "^1.1.0",
57
+ "nock": "^13.0.0",
58
+ "ts-node": "^10.9.1",
59
+ "typescript": "^5.0.0",
60
+ "uuid": "^9.0.0"
61
+ },
62
+ "engines": {
63
+ "node": "^14.16.0 || >=16.0.0"
64
+ },
65
+ "gitHead": "27ddc1b91d3d66780166483b42a0f6efddaa14ea"
66
+ }