@swell/cli 2.2.0 → 2.3.0
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/dist/app-command.js +1 -1
- package/dist/commands/api/delete.js +4 -1
- package/dist/commands/api/get.js +1 -0
- package/dist/commands/api/index.js +4 -1
- package/dist/commands/api/post.js +1 -0
- package/dist/commands/api/put.js +1 -0
- package/dist/commands/app/dev.js +5 -3
- package/dist/commands/app/init.d.ts +8 -7
- package/dist/commands/app/init.js +36 -18
- package/dist/commands/app/pull.js +2 -2
- package/dist/commands/app/push.js +2 -2
- package/dist/commands/app/version.d.ts +1 -0
- package/dist/commands/app/version.js +14 -4
- package/dist/commands/create/app.d.ts +8 -4
- package/dist/commands/create/app.js +68 -51
- package/dist/commands/create/content.d.ts +5 -6
- package/dist/commands/create/content.js +22 -38
- package/dist/commands/create/function.d.ts +6 -7
- package/dist/commands/create/function.js +94 -31
- package/dist/commands/create/index.js +13 -1
- package/dist/commands/create/model.d.ts +5 -6
- package/dist/commands/create/model.js +21 -33
- package/dist/commands/create/notification.d.ts +9 -9
- package/dist/commands/create/notification.js +117 -95
- package/dist/commands/create/setting.d.ts +21 -0
- package/dist/commands/create/setting.js +120 -0
- package/dist/commands/create/tests.d.ts +14 -0
- package/dist/commands/create/tests.js +76 -0
- package/dist/commands/create/webhook.d.ts +22 -0
- package/dist/commands/create/webhook.js +176 -0
- package/dist/commands/inspect/content.js +1 -1
- package/dist/commands/schema.d.ts +1 -0
- package/dist/commands/schema.js +51 -5
- package/dist/commands/theme/init.d.ts +8 -4
- package/dist/commands/theme/init.js +21 -8
- package/dist/create-app-command.d.ts +1 -0
- package/dist/create-app-command.js +15 -10
- package/dist/create-config-command.js +2 -2
- package/dist/help/custom-help.d.ts +89 -0
- package/dist/help/custom-help.js +337 -0
- package/dist/help/types.d.ts +75 -0
- package/dist/help/types.js +1 -0
- package/dist/lib/apps/app-config.js +2 -2
- package/dist/lib/apps/index.d.ts +2 -1
- package/dist/lib/apps/index.js +21 -4
- package/dist/lib/apps/paths.js +7 -6
- package/dist/lib/create/notification.d.ts +1 -0
- package/dist/lib/create/schemas.d.ts +1 -0
- package/dist/lib/create/schemas.js +1 -0
- package/dist/lib/create/setting.d.ts +15 -0
- package/dist/lib/create/setting.js +27 -0
- package/dist/lib/create/tests/templates/env-dts.d.ts +1 -0
- package/dist/lib/create/tests/templates/env-dts.js +15 -0
- package/dist/lib/create/tests/templates/index.d.ts +8 -0
- package/dist/lib/create/tests/templates/index.js +8 -0
- package/dist/lib/create/tests/templates/integration-test.d.ts +1 -0
- package/dist/lib/create/tests/templates/integration-test.js +19 -0
- package/dist/lib/create/tests/templates/mock-request.d.ts +2 -0
- package/dist/lib/create/tests/templates/mock-request.js +112 -0
- package/dist/lib/create/tests/templates/setup-globals.d.ts +1 -0
- package/dist/lib/create/tests/templates/setup-globals.js +23 -0
- package/dist/lib/create/tests/templates/swell-client.d.ts +1 -0
- package/dist/lib/create/tests/templates/swell-client.js +128 -0
- package/dist/lib/create/tests/templates/tsconfig.d.ts +1 -0
- package/dist/lib/create/tests/templates/tsconfig.js +17 -0
- package/dist/lib/create/tests/templates/unit-test.d.ts +1 -0
- package/dist/lib/create/tests/templates/unit-test.js +42 -0
- package/dist/lib/create/tests/templates/vitest-config.d.ts +2 -0
- package/dist/lib/create/tests/templates/vitest-config.js +122 -0
- package/dist/lib/create/tests/types.d.ts +21 -0
- package/dist/lib/create/tests/types.js +1 -0
- package/dist/lib/create/tests.d.ts +4 -0
- package/dist/lib/create/tests.js +113 -0
- package/dist/lib/create/webhook.d.ts +32 -0
- package/dist/lib/create/webhook.js +52 -0
- package/dist/swell-api-command.d.ts +14 -0
- package/dist/swell-api-command.js +113 -7
- package/oclif.manifest.json +2879 -0
- package/package.json +2 -1
package/dist/lib/apps/index.js
CHANGED
|
@@ -46,6 +46,18 @@ const ConfigTypeBatchOrder = [
|
|
|
46
46
|
ConfigType.FRONTEND,
|
|
47
47
|
ConfigType.FILE,
|
|
48
48
|
];
|
|
49
|
+
// All available configs
|
|
50
|
+
const AllConfigTypes = [
|
|
51
|
+
'ASSET',
|
|
52
|
+
'CONTENT',
|
|
53
|
+
'FRONTEND',
|
|
54
|
+
'FUNCTION',
|
|
55
|
+
'MODEL',
|
|
56
|
+
'NOTIFICATION',
|
|
57
|
+
'SETTING',
|
|
58
|
+
'THEME',
|
|
59
|
+
'WEBHOOK',
|
|
60
|
+
];
|
|
49
61
|
// Configs available for admin/integration app types
|
|
50
62
|
const StandardConfigTypes = [
|
|
51
63
|
'MODEL',
|
|
@@ -57,11 +69,13 @@ const StandardConfigTypes = [
|
|
|
57
69
|
'SETTING',
|
|
58
70
|
'WEBHOOK',
|
|
59
71
|
];
|
|
72
|
+
// Configs available for storefront app types
|
|
60
73
|
export const StorefrontConfigTypes = [
|
|
61
74
|
'FRONTEND',
|
|
62
75
|
'ASSET',
|
|
63
76
|
'SETTING',
|
|
64
77
|
];
|
|
78
|
+
// Configs available for theme types
|
|
65
79
|
export const ThemeConfigTypes = ['THEME', 'ASSET'];
|
|
66
80
|
const getConfigPaths = (types) =>
|
|
67
81
|
// eslint-disable-next-line unicorn/no-array-reduce
|
|
@@ -76,7 +90,10 @@ Object.keys(ConfigType).reduce((acc, type) => {
|
|
|
76
90
|
}
|
|
77
91
|
return acc;
|
|
78
92
|
}, {});
|
|
79
|
-
export const
|
|
93
|
+
export const AllConfigPaths = {
|
|
94
|
+
...getConfigPaths(AllConfigTypes),
|
|
95
|
+
};
|
|
96
|
+
export const StandardConfigPaths = {
|
|
80
97
|
...getConfigPaths(StandardConfigTypes),
|
|
81
98
|
};
|
|
82
99
|
export const StorefrontConfigPaths = {
|
|
@@ -217,8 +234,8 @@ export function getFrontendProjectType(appPath) {
|
|
|
217
234
|
return undefined;
|
|
218
235
|
}
|
|
219
236
|
export function getConfigTypeFromPath(path) {
|
|
220
|
-
for (const type in
|
|
221
|
-
if (
|
|
237
|
+
for (const type in AllConfigPaths) {
|
|
238
|
+
if (AllConfigPaths[type] === path) {
|
|
222
239
|
const configType = ConfigType[type];
|
|
223
240
|
return configType;
|
|
224
241
|
}
|
|
@@ -289,7 +306,7 @@ export function hashString(...contents) {
|
|
|
289
306
|
return hash.digest('hex', { length: 16 });
|
|
290
307
|
}
|
|
291
308
|
export function appAssetImage(appPath, fileName) {
|
|
292
|
-
const assetsDirPath = path.join(appPath,
|
|
309
|
+
const assetsDirPath = path.join(appPath, AllConfigPaths['ASSET']);
|
|
293
310
|
if (!filePathExists(assetsDirPath)) {
|
|
294
311
|
return;
|
|
295
312
|
}
|
package/dist/lib/apps/paths.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { globby, globbySync, isGitIgnored, } from 'globby';
|
|
2
2
|
import * as fs from 'node:fs';
|
|
3
3
|
import * as path from 'node:path';
|
|
4
|
-
import {
|
|
4
|
+
import { AllConfigPaths, ConfigType, StandardConfigPaths, StorefrontConfigPaths, ThemeConfigPaths, filePathExists, } from './index.js';
|
|
5
5
|
function globOptions(appPath, options) {
|
|
6
6
|
return {
|
|
7
7
|
cwd: appPath,
|
|
@@ -35,7 +35,7 @@ export function getGlobIgnorePathsChecker(appPath) {
|
|
|
35
35
|
function getConfigDirsPaths(appPath) {
|
|
36
36
|
const configDirsPaths = [];
|
|
37
37
|
for (const [type, configType] of Object.entries(ConfigType)) {
|
|
38
|
-
const configPath =
|
|
38
|
+
const configPath = AllConfigPaths[type];
|
|
39
39
|
if (!configPath) {
|
|
40
40
|
continue;
|
|
41
41
|
}
|
|
@@ -57,7 +57,7 @@ export function* allConfigFilesPaths(appPath) {
|
|
|
57
57
|
}
|
|
58
58
|
}
|
|
59
59
|
export function* allConfigFilesPathsByType(appPath, configTypeKey) {
|
|
60
|
-
const configPath =
|
|
60
|
+
const configPath = AllConfigPaths[configTypeKey];
|
|
61
61
|
yield* allConfigFilesInDir(appPath, configPath, ConfigType[configTypeKey]);
|
|
62
62
|
}
|
|
63
63
|
export function* allConfigFilesInDir(appPath, dirPath, configType) {
|
|
@@ -76,16 +76,17 @@ export function* allBaseFilesInDir(appPath) {
|
|
|
76
76
|
yield path;
|
|
77
77
|
}
|
|
78
78
|
}
|
|
79
|
+
// this function is used to create folders for app
|
|
79
80
|
export function getAllConfigPaths(appType) {
|
|
80
81
|
const configPaths = [];
|
|
81
82
|
const ConfigPathsByType = appType === 'storefront'
|
|
82
83
|
? StorefrontConfigPaths
|
|
83
84
|
: appType === 'theme'
|
|
84
85
|
? ThemeConfigPaths
|
|
85
|
-
:
|
|
86
|
+
: StandardConfigPaths;
|
|
86
87
|
for (const type of Object.keys(ConfigPathsByType)) {
|
|
87
|
-
if (Object.hasOwn(
|
|
88
|
-
const configPath =
|
|
88
|
+
if (Object.hasOwn(AllConfigPaths, type)) {
|
|
89
|
+
const configPath = AllConfigPaths[type];
|
|
89
90
|
configPaths.push(configPath);
|
|
90
91
|
}
|
|
91
92
|
}
|
|
@@ -3,4 +3,5 @@ export declare const SCHEMAS: {
|
|
|
3
3
|
readonly MODEL: "https://json.swell.store/model.json";
|
|
4
4
|
readonly NOTIFICATION: "https://json.swell.store/notification.json";
|
|
5
5
|
readonly SETTING: "https://json.swell.store/setting.json";
|
|
6
|
+
readonly WEBHOOK: "https://json.swell.store/webhook.json";
|
|
6
7
|
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
interface CreateSettingFileBody {
|
|
2
|
+
$schema: string;
|
|
3
|
+
description?: string;
|
|
4
|
+
fields: SettingFieldProps[];
|
|
5
|
+
label?: string;
|
|
6
|
+
}
|
|
7
|
+
interface SettingFieldProps {
|
|
8
|
+
id: string;
|
|
9
|
+
label: string;
|
|
10
|
+
type?: SettingFieldType;
|
|
11
|
+
}
|
|
12
|
+
type SettingFieldType = 'asset' | 'basic_html' | 'boolean' | 'category_lookup' | 'checkbox' | 'checkboxes' | 'collection' | 'color' | 'color_scheme' | 'color_scheme_group' | 'currency' | 'customer_lookup' | 'date' | 'datetime' | 'document' | 'dropdown' | 'email' | 'field_group' | 'font_family' | 'generic_lookup' | 'html' | 'icon' | 'image' | 'long_text' | 'lookup' | 'markdown' | 'number' | 'percent' | 'phone' | 'product_lookup' | 'radio' | 'rich_html' | 'rich_text' | 'select' | 'short_text' | 'slider' | 'slug' | 'tags' | 'text' | 'textarea' | 'time' | 'toggle' | 'url' | 'variant_lookup' | 'video';
|
|
13
|
+
declare const parseFields: (fields: string[]) => SettingFieldProps[];
|
|
14
|
+
declare const toSettingLabel: (value: string) => string;
|
|
15
|
+
export { CreateSettingFileBody, parseFields, toSettingLabel };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { titleize } from 'inflection';
|
|
2
|
+
const parseFields = (fields) => {
|
|
3
|
+
if (fields.length === 0) {
|
|
4
|
+
return [];
|
|
5
|
+
}
|
|
6
|
+
const fieldResponse = [];
|
|
7
|
+
for (const fieldPair of fields) {
|
|
8
|
+
if (fieldPair.includes(':')) {
|
|
9
|
+
const [id, type] = fieldPair.split(':');
|
|
10
|
+
fieldResponse.push({
|
|
11
|
+
id,
|
|
12
|
+
label: titleize(id),
|
|
13
|
+
type,
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
else {
|
|
17
|
+
fieldResponse.push({
|
|
18
|
+
id: fieldPair,
|
|
19
|
+
label: titleize(fieldPair),
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return fieldResponse;
|
|
24
|
+
};
|
|
25
|
+
// Example: 'api-config' => 'Api Config'
|
|
26
|
+
const toSettingLabel = (value) => titleize(value).replaceAll('-', ' ');
|
|
27
|
+
export { parseFields, toSettingLabel };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function envDtsTemplate(): string;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export function envDtsTemplate() {
|
|
2
|
+
return `\
|
|
3
|
+
declare module "cloudflare:test" {
|
|
4
|
+
interface ProvidedEnv {
|
|
5
|
+
SWELL_STORE_ID: string;
|
|
6
|
+
SWELL_SESSION_ID: string;
|
|
7
|
+
SWELL_API_BASE_URL: string;
|
|
8
|
+
SWELL_APP_ID: string;
|
|
9
|
+
SWELL_ENVIRONMENT?: string;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export {};
|
|
14
|
+
`;
|
|
15
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { envDtsTemplate } from './env-dts.js';
|
|
2
|
+
export { integrationTestTemplate } from './integration-test.js';
|
|
3
|
+
export { mockRequestTemplate } from './mock-request.js';
|
|
4
|
+
export { setupGlobalsTemplate } from './setup-globals.js';
|
|
5
|
+
export { swellClientTemplate } from './swell-client.js';
|
|
6
|
+
export { tsconfigTemplate } from './tsconfig.js';
|
|
7
|
+
export { unitTestTemplate } from './unit-test.js';
|
|
8
|
+
export { vitestConfigTemplate } from './vitest-config.js';
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { envDtsTemplate } from './env-dts.js';
|
|
2
|
+
export { integrationTestTemplate } from './integration-test.js';
|
|
3
|
+
export { mockRequestTemplate } from './mock-request.js';
|
|
4
|
+
export { setupGlobalsTemplate } from './setup-globals.js';
|
|
5
|
+
export { swellClientTemplate } from './swell-client.js';
|
|
6
|
+
export { tsconfigTemplate } from './tsconfig.js';
|
|
7
|
+
export { unitTestTemplate } from './unit-test.js';
|
|
8
|
+
export { vitestConfigTemplate } from './vitest-config.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function integrationTestTemplate(): string;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export function integrationTestTemplate() {
|
|
2
|
+
return `\
|
|
3
|
+
import { describe, it, expect } from "vitest";
|
|
4
|
+
import { createSwellClient } from "../helpers/swell-client";
|
|
5
|
+
|
|
6
|
+
describe("Store settings (read-only)", () => {
|
|
7
|
+
it("loads general store settings using CLI auth", async () => {
|
|
8
|
+
const swell = createSwellClient();
|
|
9
|
+
|
|
10
|
+
const general = await swell.get("/settings/general");
|
|
11
|
+
|
|
12
|
+
expect(general).toBeDefined();
|
|
13
|
+
expect(general.id).toBe("general");
|
|
14
|
+
expect(general.features).toBeDefined();
|
|
15
|
+
expect(typeof general.features.carts).toBe("boolean");
|
|
16
|
+
});
|
|
17
|
+
});
|
|
18
|
+
`;
|
|
19
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
export function mockRequestTemplate(options) {
|
|
2
|
+
return `\
|
|
3
|
+
import { env } from "cloudflare:test";
|
|
4
|
+
import { createSwellClient } from "./swell-client";
|
|
5
|
+
|
|
6
|
+
export interface MockRequestOptions {
|
|
7
|
+
data?: SwellData;
|
|
8
|
+
query?: { [key: string]: string };
|
|
9
|
+
session?: { [key: string]: any };
|
|
10
|
+
headers?: Record<string, string>;
|
|
11
|
+
method?: string;
|
|
12
|
+
url?: string;
|
|
13
|
+
swell?: Partial<SwellAPI>;
|
|
14
|
+
store?: Partial<SwellStore>;
|
|
15
|
+
appId?: string;
|
|
16
|
+
useRealSwell?: boolean;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function createDefaultSwellMock(): Partial<SwellAPI> {
|
|
20
|
+
const notMocked = (method: string) => () => {
|
|
21
|
+
throw new Error(
|
|
22
|
+
\`swell.\${method}() not mocked. Pass { swell: { \${method}: vi.fn() } } to createMockRequest().\`
|
|
23
|
+
);
|
|
24
|
+
};
|
|
25
|
+
return {
|
|
26
|
+
get: notMocked("get"),
|
|
27
|
+
post: notMocked("post"),
|
|
28
|
+
put: notMocked("put"),
|
|
29
|
+
delete: notMocked("delete"),
|
|
30
|
+
settings: notMocked("settings"),
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function createMockRequest(options: MockRequestOptions = {}): SwellRequest {
|
|
35
|
+
const {
|
|
36
|
+
data = {},
|
|
37
|
+
query = {},
|
|
38
|
+
session = {},
|
|
39
|
+
headers = {},
|
|
40
|
+
method = "POST",
|
|
41
|
+
url = "https://example.com/test",
|
|
42
|
+
swell,
|
|
43
|
+
store,
|
|
44
|
+
appId,
|
|
45
|
+
useRealSwell = false,
|
|
46
|
+
} = options;
|
|
47
|
+
|
|
48
|
+
const requestHeaders = new Headers(headers);
|
|
49
|
+
const originalRequest = new Request(url, {
|
|
50
|
+
method,
|
|
51
|
+
headers: requestHeaders,
|
|
52
|
+
body: method === "GET" ? undefined : JSON.stringify(data),
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
const storeId = store?.id || env.SWELL_STORE_ID || "test-store";
|
|
56
|
+
|
|
57
|
+
const resolvedStore = {
|
|
58
|
+
id: storeId,
|
|
59
|
+
url: store?.url || "",
|
|
60
|
+
admin_url: store?.admin_url || "",
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const context = {
|
|
64
|
+
waitUntil: async (promise: Promise<unknown>) => {
|
|
65
|
+
await promise;
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const swellClient =
|
|
70
|
+
swell || (useRealSwell ? createSwellClient() : createDefaultSwellMock());
|
|
71
|
+
|
|
72
|
+
const resolvedAppId = appId || env.SWELL_APP_ID || "${options.appId}";
|
|
73
|
+
|
|
74
|
+
const req: Partial<SwellRequest> & { appId: string; storeId: string } = {
|
|
75
|
+
originalRequest,
|
|
76
|
+
context,
|
|
77
|
+
url,
|
|
78
|
+
method,
|
|
79
|
+
headers: requestHeaders,
|
|
80
|
+
referrer: originalRequest.referrer,
|
|
81
|
+
credentials: "include",
|
|
82
|
+
appId: resolvedAppId,
|
|
83
|
+
storeId,
|
|
84
|
+
accessToken: null,
|
|
85
|
+
publicKey: null,
|
|
86
|
+
store: resolvedStore,
|
|
87
|
+
session,
|
|
88
|
+
apiHost: env.SWELL_API_BASE_URL || "",
|
|
89
|
+
logParams: undefined,
|
|
90
|
+
swell: swellClient as SwellAPI,
|
|
91
|
+
body: data,
|
|
92
|
+
data,
|
|
93
|
+
query,
|
|
94
|
+
initialize: async () => {},
|
|
95
|
+
parseJson: (input: string) => JSON.parse(input),
|
|
96
|
+
appValues: (idOrValues: string | SwellData, values?: SwellData) => {
|
|
97
|
+
const targetAppId =
|
|
98
|
+
typeof idOrValues === "string" ? idOrValues : resolvedAppId;
|
|
99
|
+
const appValues = typeof idOrValues === "string" ? values : idOrValues;
|
|
100
|
+
if (!targetAppId || !appValues) return undefined;
|
|
101
|
+
return {
|
|
102
|
+
$app: {
|
|
103
|
+
[targetAppId]: appValues,
|
|
104
|
+
},
|
|
105
|
+
};
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
return req as SwellRequest;
|
|
110
|
+
}
|
|
111
|
+
`;
|
|
112
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function setupGlobalsTemplate(): string;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export function setupGlobalsTemplate() {
|
|
2
|
+
return `\
|
|
3
|
+
class SwellErrorImpl extends Error {
|
|
4
|
+
status: number;
|
|
5
|
+
|
|
6
|
+
constructor(message: string | object, options: { status?: number } = {}) {
|
|
7
|
+
const text =
|
|
8
|
+
typeof message === "string"
|
|
9
|
+
? message
|
|
10
|
+
: JSON.stringify(message, null, 2);
|
|
11
|
+
|
|
12
|
+
super(text);
|
|
13
|
+
this.name = "SwellError";
|
|
14
|
+
this.status = options.status ?? 500;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
19
|
+
(globalThis as any).SwellError = SwellErrorImpl;
|
|
20
|
+
|
|
21
|
+
export {};
|
|
22
|
+
`;
|
|
23
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function swellClientTemplate(): string;
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
export function swellClientTemplate() {
|
|
2
|
+
return `\
|
|
3
|
+
import { env } from "cloudflare:test";
|
|
4
|
+
|
|
5
|
+
type HttpMethod = "GET" | "POST" | "PUT" | "DELETE";
|
|
6
|
+
|
|
7
|
+
type BasicSwellClient = Pick<
|
|
8
|
+
SwellAPI,
|
|
9
|
+
"get" | "post" | "put" | "delete" | "settings"
|
|
10
|
+
>;
|
|
11
|
+
|
|
12
|
+
async function makeRequest(
|
|
13
|
+
method: HttpMethod,
|
|
14
|
+
url: string,
|
|
15
|
+
data?: any
|
|
16
|
+
): Promise<any> {
|
|
17
|
+
const baseUrl = env.SWELL_API_BASE_URL || "https://api.swell.store";
|
|
18
|
+
const sessionId = env.SWELL_SESSION_ID;
|
|
19
|
+
const environment = env.SWELL_ENVIRONMENT || "test";
|
|
20
|
+
|
|
21
|
+
if (!sessionId) {
|
|
22
|
+
throw new Error(
|
|
23
|
+
"Missing SWELL_SESSION_ID binding. Run \\\`swell login\\\` or set it explicitly for tests.",
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
let endpointUrl = String(url).startsWith("/") ? url.substring(1) : String(url);
|
|
28
|
+
if (!endpointUrl.startsWith("data/")) {
|
|
29
|
+
endpointUrl = \`data/\${endpointUrl}\`;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
let fullUrl = \`\${baseUrl}/\${endpointUrl}\`;
|
|
33
|
+
const headers: HeadersInit = {
|
|
34
|
+
"Content-Type": "application/json;charset=UTF-8",
|
|
35
|
+
"User-Agent": "swell-app-tests/1.0",
|
|
36
|
+
"X-Session": sessionId,
|
|
37
|
+
"Swell-Env": environment,
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const options: RequestInit = {
|
|
41
|
+
method,
|
|
42
|
+
headers,
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
if (data) {
|
|
46
|
+
if (method === "GET") {
|
|
47
|
+
const params = new URLSearchParams();
|
|
48
|
+
|
|
49
|
+
function append(prefix: string, value: any) {
|
|
50
|
+
if (value === null || value === undefined) return;
|
|
51
|
+
if (typeof value === "object" && !Array.isArray(value)) {
|
|
52
|
+
for (const [k, v] of Object.entries(value)) {
|
|
53
|
+
append(\`\${prefix}[\${k}]\`, v);
|
|
54
|
+
}
|
|
55
|
+
} else if (Array.isArray(value)) {
|
|
56
|
+
for (const v of value) {
|
|
57
|
+
append(\`\${prefix}[]\`, v);
|
|
58
|
+
}
|
|
59
|
+
} else {
|
|
60
|
+
params.append(prefix, String(value));
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
for (const [key, value] of Object.entries(data)) {
|
|
65
|
+
append(key, value);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const query = params.toString();
|
|
69
|
+
if (query) {
|
|
70
|
+
fullUrl += \`?\${query}\`;
|
|
71
|
+
}
|
|
72
|
+
} else {
|
|
73
|
+
options.body = JSON.stringify(data);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const response = await fetch(fullUrl, options);
|
|
78
|
+
const text = await response.text();
|
|
79
|
+
|
|
80
|
+
let result: any;
|
|
81
|
+
try {
|
|
82
|
+
result = text ? JSON.parse(text) : null;
|
|
83
|
+
} catch {
|
|
84
|
+
result = text;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (!response.ok) {
|
|
88
|
+
throw new (globalThis as any).SwellError(result || text || "Request failed", {
|
|
89
|
+
status: response.status,
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (result?.errors) {
|
|
94
|
+
throw new (globalThis as any).SwellError(result.errors, {
|
|
95
|
+
status: 400,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return result;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function createSwellClient(): BasicSwellClient {
|
|
103
|
+
return {
|
|
104
|
+
get(url: string, query?: any) {
|
|
105
|
+
return makeRequest("GET", url, query);
|
|
106
|
+
},
|
|
107
|
+
post(url: string, data?: any) {
|
|
108
|
+
return makeRequest("POST", url, data);
|
|
109
|
+
},
|
|
110
|
+
put(url: string, data?: any) {
|
|
111
|
+
return makeRequest("PUT", url, data);
|
|
112
|
+
},
|
|
113
|
+
delete(url: string, data?: any) {
|
|
114
|
+
return makeRequest("DELETE", url, data);
|
|
115
|
+
},
|
|
116
|
+
settings(id?: string) {
|
|
117
|
+
const appId = id || env.SWELL_APP_ID;
|
|
118
|
+
if (!appId) {
|
|
119
|
+
throw new Error(
|
|
120
|
+
"Missing app id. Pass an id to settings() or set SWELL_APP_ID binding.",
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
return makeRequest("GET", \`/settings/\${appId}\`);
|
|
124
|
+
},
|
|
125
|
+
} as BasicSwellClient;
|
|
126
|
+
}
|
|
127
|
+
`;
|
|
128
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function tsconfigTemplate(): string;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export function tsconfigTemplate() {
|
|
2
|
+
const config = {
|
|
3
|
+
extends: '../tsconfig.json',
|
|
4
|
+
compilerOptions: {
|
|
5
|
+
moduleResolution: 'bundler',
|
|
6
|
+
skipLibCheck: true,
|
|
7
|
+
types: [
|
|
8
|
+
'@cloudflare/vitest-pool-workers',
|
|
9
|
+
'@swell/app-types',
|
|
10
|
+
'vitest/globals',
|
|
11
|
+
],
|
|
12
|
+
},
|
|
13
|
+
include: ['./**/*.ts', '../functions/**/*.ts'],
|
|
14
|
+
exclude: ['../node_modules'],
|
|
15
|
+
};
|
|
16
|
+
return `${JSON.stringify(config, null, 2)}\n`;
|
|
17
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function unitTestTemplate(): string;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export function unitTestTemplate() {
|
|
2
|
+
return `\
|
|
3
|
+
import { describe, it, expect, vi } from "vitest";
|
|
4
|
+
import { createMockRequest } from "../helpers/mock-request";
|
|
5
|
+
|
|
6
|
+
// Import your functions to test:
|
|
7
|
+
// import handler from "../../functions/my-handler";
|
|
8
|
+
|
|
9
|
+
describe("Unit test examples", () => {
|
|
10
|
+
it("creates a mock request with data and session", () => {
|
|
11
|
+
const req = createMockRequest({
|
|
12
|
+
data: { product_id: "prod_123" },
|
|
13
|
+
session: { account_id: "acc_456" },
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
expect(req.data.product_id).toBe("prod_123");
|
|
17
|
+
expect(req.session.account_id).toBe("acc_456");
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it("mocks swell.get", async () => {
|
|
21
|
+
const mockGet = vi.fn().mockResolvedValue({ id: "123", name: "Test" });
|
|
22
|
+
const req = createMockRequest({ swell: { get: mockGet } });
|
|
23
|
+
|
|
24
|
+
const result = await req.swell.get("/products/{id}", { id: "123" });
|
|
25
|
+
|
|
26
|
+
expect(result.name).toBe("Test");
|
|
27
|
+
expect(mockGet).toHaveBeenCalledWith("/products/{id}", { id: "123" });
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("mocks swell.settings", async () => {
|
|
31
|
+
const mockSettings = vi.fn().mockResolvedValue({
|
|
32
|
+
feature: { enabled: true },
|
|
33
|
+
});
|
|
34
|
+
const req = createMockRequest({ swell: { settings: mockSettings } });
|
|
35
|
+
|
|
36
|
+
const settings = await req.swell.settings();
|
|
37
|
+
|
|
38
|
+
expect(settings.feature.enabled).toBe(true);
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
`;
|
|
42
|
+
}
|