@swell/cli 2.9.26 → 2.9.28
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/commands/app/dev.d.ts +1 -0
- package/dist/commands/app/dev.js +26 -2
- package/dist/commands/create/component.d.ts +22 -0
- package/dist/commands/create/component.js +129 -0
- package/dist/commands/create/index.js +5 -1
- package/dist/commands/create/tests.js +11 -2
- package/dist/commands/inspect/extensions.js +3 -3
- package/dist/lib/apps/app-config.d.ts +15 -0
- package/dist/lib/apps/app-config.js +99 -30
- package/dist/lib/apps/component-extension.d.ts +23 -0
- package/dist/lib/apps/component-extension.js +60 -0
- package/dist/lib/apps/components.d.ts +32 -0
- package/dist/lib/apps/components.js +81 -0
- package/dist/lib/apps/frontend-scaffold.js +1 -1
- package/dist/lib/apps/index.d.ts +1 -0
- package/dist/lib/apps/index.js +5 -3
- package/dist/lib/bundle.d.ts +21 -1
- package/dist/lib/bundle.js +88 -5
- package/dist/lib/create/component.d.ts +22 -0
- package/dist/lib/create/component.js +194 -0
- package/dist/lib/create/content.d.ts +1 -1
- package/dist/lib/create/setting.d.ts +1 -1
- package/dist/lib/create/tests/templates/index.d.ts +0 -3
- package/dist/lib/create/tests/templates/index.js +0 -3
- package/dist/lib/create/tests/templates/mock-request.js +4 -10
- package/dist/lib/create/tests/templates/tsconfig.js +1 -1
- package/dist/lib/create/tests/templates/vitest-config.d.ts +1 -2
- package/dist/lib/create/tests/templates/vitest-config.js +17 -113
- package/dist/lib/create/tests/types.d.ts +2 -0
- package/dist/lib/create/tests.js +64 -12
- package/dist/lib/function-source-analysis.d.ts +11 -0
- package/dist/lib/function-source-analysis.js +33 -0
- package/dist/lib/inspect/extensions.d.ts +8 -1
- package/dist/lib/inspect/extensions.js +12 -2
- package/dist/lib/swell-function-wrapper.js +31 -0
- package/dist/push-app-command.js +11 -0
- package/oclif.manifest.json +82 -3
- package/package.json +3 -1
- package/dist/lib/create/tests/templates/env-dts.d.ts +0 -1
- package/dist/lib/create/tests/templates/env-dts.js +0 -15
- package/dist/lib/create/tests/templates/integration-test.d.ts +0 -1
- package/dist/lib/create/tests/templates/integration-test.js +0 -19
- package/dist/lib/create/tests/templates/swell-client.d.ts +0 -1
- package/dist/lib/create/tests/templates/swell-client.js +0 -128
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import * as fs from 'node:fs/promises';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
import { bundleComponentModule } from '../bundle.js';
|
|
4
|
+
import { getNameFromFilePath } from './index.js';
|
|
5
|
+
/** Path of component modules on the `swell app dev` proxy */
|
|
6
|
+
export const COMPONENT_DEV_PATH = '/.swell/components/';
|
|
7
|
+
const COMPONENT_EXTENSIONS = new Set(['.jsx', '.tsx']);
|
|
8
|
+
/**
|
|
9
|
+
* Whether a file is a component entry: a .tsx or .jsx file directly in components/
|
|
10
|
+
* @param {string} file path relative to the app
|
|
11
|
+
* @returns {boolean} true for a root component
|
|
12
|
+
*/
|
|
13
|
+
export function isRootComponentFile(file) {
|
|
14
|
+
const parts = file.split('/');
|
|
15
|
+
return (parts.length === 2 &&
|
|
16
|
+
parts[0] === 'components' &&
|
|
17
|
+
COMPONENT_EXTENSIONS.has(path.extname(parts[1])));
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Component entries of an app
|
|
21
|
+
* @param {string} appPath app directory
|
|
22
|
+
* @returns {Promise<string[]>} paths relative to the app, sorted
|
|
23
|
+
*/
|
|
24
|
+
export async function listRootComponentFiles(appPath) {
|
|
25
|
+
let names;
|
|
26
|
+
try {
|
|
27
|
+
names = await fs.readdir(path.join(appPath, 'components'));
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return [];
|
|
31
|
+
}
|
|
32
|
+
return names
|
|
33
|
+
.map((name) => `components/${name}`)
|
|
34
|
+
.filter((file) => isRootComponentFile(file))
|
|
35
|
+
.sort();
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Components to rebuild after a change: root components bundle their shared
|
|
39
|
+
* code, so a change under components/ outside the root rebuilds all of them
|
|
40
|
+
* @param {string} appPath app directory
|
|
41
|
+
* @param {string} changedFile changed file, relative to the app
|
|
42
|
+
* @returns {Promise<string[]>} root components to push again
|
|
43
|
+
*/
|
|
44
|
+
export async function componentsToRebuild(appPath, changedFile) {
|
|
45
|
+
if (!changedFile.startsWith('components/') ||
|
|
46
|
+
isRootComponentFile(changedFile)) {
|
|
47
|
+
return [];
|
|
48
|
+
}
|
|
49
|
+
return listRootComponentFiles(appPath);
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Builds and serves `/.swell/components/<Name>.js` for `swell app dev`
|
|
53
|
+
* @param {string} appPath app directory
|
|
54
|
+
* @param {string} pathname request path
|
|
55
|
+
* @param {http.ServerResponse} res response to write
|
|
56
|
+
* @returns {Promise<void>} resolves when the response is written
|
|
57
|
+
*/
|
|
58
|
+
export async function serveComponentModule(appPath, pathname, res) {
|
|
59
|
+
const headers = {
|
|
60
|
+
'Access-Control-Allow-Origin': '*',
|
|
61
|
+
'Cache-Control': 'no-store',
|
|
62
|
+
};
|
|
63
|
+
const name = /^\/\.swell\/components\/([\w-]+)\.js$/.exec(pathname)?.[1];
|
|
64
|
+
const files = name ? await listRootComponentFiles(appPath) : [];
|
|
65
|
+
const file = files.find((entry) => getNameFromFilePath(entry) === name);
|
|
66
|
+
if (!file) {
|
|
67
|
+
res.writeHead(404, headers).end('Component not found');
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
try {
|
|
71
|
+
const { code } = await bundleComponentModule(file, appPath);
|
|
72
|
+
res
|
|
73
|
+
.writeHead(200, { ...headers, 'Content-Type': 'text/javascript' })
|
|
74
|
+
.end(code);
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
res
|
|
78
|
+
.writeHead(500, { ...headers, 'Content-Type': 'text/plain' })
|
|
79
|
+
.end(error.message);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
@@ -252,7 +252,7 @@ export async function setupPackage(name, config, pkg) {
|
|
|
252
252
|
moduleResolution: 'bundler',
|
|
253
253
|
types: ['@swell/app-types'],
|
|
254
254
|
},
|
|
255
|
-
exclude: ['node_modules', 'frontend', 'test', 'vitest.config.
|
|
255
|
+
exclude: ['node_modules', 'frontend', 'test', 'vitest.config.mts'],
|
|
256
256
|
};
|
|
257
257
|
await writeJsonFile(path.join(configPath, 'package.json'), packageJson);
|
|
258
258
|
// create frontend can initialize a workspace around an existing manifest.
|
package/dist/lib/apps/index.d.ts
CHANGED
|
@@ -205,6 +205,7 @@ export declare function writeJsonFile(file: string, data?: any): Promise<void>;
|
|
|
205
205
|
export declare function hashFile(filePath: string, fileContents?: Buffer, relativePath?: string): string;
|
|
206
206
|
export declare function hashString(...contents: (Buffer | string)[]): string;
|
|
207
207
|
export declare function appAssetImage(appPath: string, fileName: string): Promise<string | undefined>;
|
|
208
|
+
export declare function getNameFromFilePath(filePath: string): string;
|
|
208
209
|
export declare function appConfigFromFile(filePath: string, configType: ConfigType, appPath: string): Promise<AppConfig>;
|
|
209
210
|
export declare function findAppConfig(app: App, filePath: string, configType: ConfigType): AppConfig | undefined;
|
|
210
211
|
interface BatchAppFiles {
|
package/dist/lib/apps/index.js
CHANGED
|
@@ -52,9 +52,11 @@ export var ConfigType;
|
|
|
52
52
|
ConfigType["THEME"] = "theme";
|
|
53
53
|
ConfigType["WEBHOOK"] = "webhook";
|
|
54
54
|
})(ConfigType || (ConfigType = {}));
|
|
55
|
-
// Models
|
|
55
|
+
// Models first; then components, which content and settings fields reference
|
|
56
|
+
// by name; then content
|
|
56
57
|
const ConfigTypeBatchOrder = [
|
|
57
58
|
ConfigType.MODEL,
|
|
59
|
+
ConfigType.COMPONENT,
|
|
58
60
|
ConfigType.CONTENT,
|
|
59
61
|
ConfigType.ASSET,
|
|
60
62
|
ConfigType.FUNCTION,
|
|
@@ -63,7 +65,6 @@ const ConfigTypeBatchOrder = [
|
|
|
63
65
|
ConfigType.WEBHOOK,
|
|
64
66
|
ConfigType.THEME,
|
|
65
67
|
ConfigType.FRONTEND,
|
|
66
|
-
ConfigType.COMPONENT,
|
|
67
68
|
ConfigType.FILE,
|
|
68
69
|
];
|
|
69
70
|
// All available configs
|
|
@@ -89,6 +90,7 @@ const StandardConfigTypes = [
|
|
|
89
90
|
'NOTIFICATION',
|
|
90
91
|
'SETTING',
|
|
91
92
|
'WEBHOOK',
|
|
93
|
+
'COMPONENT',
|
|
92
94
|
];
|
|
93
95
|
// Configs available for storefront app types
|
|
94
96
|
export const StorefrontConfigTypes = [
|
|
@@ -359,7 +361,7 @@ export async function appAssetImage(appPath, fileName) {
|
|
|
359
361
|
}
|
|
360
362
|
}
|
|
361
363
|
}
|
|
362
|
-
function getNameFromFilePath(filePath) {
|
|
364
|
+
export function getNameFromFilePath(filePath) {
|
|
363
365
|
return path.parse(filePath).name.replaceAll('_', '-');
|
|
364
366
|
}
|
|
365
367
|
export async function appConfigFromFile(filePath, configType, appPath) {
|
package/dist/lib/bundle.d.ts
CHANGED
|
@@ -9,4 +9,24 @@ import type { FunctionSourceAnalysis } from './function-source-analysis.js';
|
|
|
9
9
|
export declare function getLocalInputs(entryPoint: string, rootDir: string): Promise<string[]>;
|
|
10
10
|
export declare function bundleFunction(filePath: string): Promise<any>;
|
|
11
11
|
export declare function bundleWorkflow(filePath: string, analysis: FunctionSourceAnalysis): Promise<any>;
|
|
12
|
-
|
|
12
|
+
/**
|
|
13
|
+
* Legacy build for checkout payment components, which swell-js evaluates as an
|
|
14
|
+
* IIFE exposing `Component.default`, `Component.config` and `Component.preact`.
|
|
15
|
+
* @param {string} filePath absolute path of the component file
|
|
16
|
+
* @returns {Promise<string>} the bundle code
|
|
17
|
+
*/
|
|
18
|
+
export declare function bundleComponent(filePath: string): Promise<string>;
|
|
19
|
+
export interface ComponentModuleBuild {
|
|
20
|
+
/** ES module exporting mount, update and unmount */
|
|
21
|
+
code: string;
|
|
22
|
+
/** App files the component imports, relative to the app root */
|
|
23
|
+
inputs: string[];
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Builds a component into the module the component frame loads: generated
|
|
27
|
+
* mount, update and unmount functions render its default export with Preact.
|
|
28
|
+
* @param {string} filePath component file, relative to appRoot
|
|
29
|
+
* @param {string} appRoot app directory
|
|
30
|
+
* @returns {Promise<ComponentModuleBuild>} the module code and the app files it includes
|
|
31
|
+
*/
|
|
32
|
+
export declare function bundleComponentModule(filePath: string, appRoot: string): Promise<ComponentModuleBuild>;
|
package/dist/lib/bundle.js
CHANGED
|
@@ -84,6 +84,12 @@ export async function bundleWorkflow(filePath, analysis) {
|
|
|
84
84
|
throw new Error(`Unable to compile workflow "${filePath}": ${error.message}`, { cause: error });
|
|
85
85
|
}
|
|
86
86
|
}
|
|
87
|
+
/**
|
|
88
|
+
* Legacy build for checkout payment components, which swell-js evaluates as an
|
|
89
|
+
* IIFE exposing `Component.default`, `Component.config` and `Component.preact`.
|
|
90
|
+
* @param {string} filePath absolute path of the component file
|
|
91
|
+
* @returns {Promise<string>} the bundle code
|
|
92
|
+
*/
|
|
87
93
|
export async function bundleComponent(filePath) {
|
|
88
94
|
try {
|
|
89
95
|
const content = await fs.readFile(filePath, 'utf8');
|
|
@@ -113,11 +119,7 @@ export async function bundleComponent(filePath) {
|
|
|
113
119
|
write: false,
|
|
114
120
|
logLevel: 'silent',
|
|
115
121
|
});
|
|
116
|
-
|
|
117
|
-
// eslint-disable-next-line no-new-func
|
|
118
|
-
const evalFn = new Function(`${code}\nreturn { ...Component }`);
|
|
119
|
-
const { config } = evalFn();
|
|
120
|
-
return { code, config };
|
|
122
|
+
return buildResult.outputFiles[0].text;
|
|
121
123
|
}
|
|
122
124
|
catch (error) {
|
|
123
125
|
throw new Error(`Unable to compile component "${filePath}": ${error.message}`, { cause: error });
|
|
@@ -435,3 +437,84 @@ function replaceModuleExports(code) {
|
|
|
435
437
|
.replaceAll(/^module\.exports\b/gm, 'var moduleExports')
|
|
436
438
|
.replaceAll(/\bmodule\.exports\b/gm, 'moduleExports');
|
|
437
439
|
}
|
|
440
|
+
const COMPONENT_ENTRY = 'swell-component-entry.js';
|
|
441
|
+
// Components render with Preact; React imports in third-party libraries resolve to its compat layer
|
|
442
|
+
const COMPONENT_ALIASES = {
|
|
443
|
+
react: 'preact/compat',
|
|
444
|
+
'react-dom': 'preact/compat',
|
|
445
|
+
};
|
|
446
|
+
// The nearest tsconfig.json overrides esbuild's jsx options per file, and
|
|
447
|
+
// apps may still say "jsx": "react". A pragma wins over both, so the app's
|
|
448
|
+
// own .jsx/.tsx files always compile with Preact's automatic runtime, while
|
|
449
|
+
// the rest of its tsconfig (paths, baseUrl) still applies.
|
|
450
|
+
const preactJsxPlugin = {
|
|
451
|
+
name: 'swell-preact-jsx',
|
|
452
|
+
setup(build) {
|
|
453
|
+
build.onLoad({ filter: /\.[jt]sx$/ }, async (args) => {
|
|
454
|
+
if (args.path.split(path.sep).includes('node_modules')) {
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
457
|
+
const source = await fs.readFile(args.path, 'utf8');
|
|
458
|
+
return {
|
|
459
|
+
contents: `/** @jsxRuntime automatic @jsxImportSource preact */ ${source}`,
|
|
460
|
+
loader: args.path.endsWith('.tsx') ? 'tsx' : 'jsx',
|
|
461
|
+
};
|
|
462
|
+
});
|
|
463
|
+
},
|
|
464
|
+
};
|
|
465
|
+
/**
|
|
466
|
+
* Builds a component into the module the component frame loads: generated
|
|
467
|
+
* mount, update and unmount functions render its default export with Preact.
|
|
468
|
+
* @param {string} filePath component file, relative to appRoot
|
|
469
|
+
* @param {string} appRoot app directory
|
|
470
|
+
* @returns {Promise<ComponentModuleBuild>} the module code and the app files it includes
|
|
471
|
+
*/
|
|
472
|
+
export async function bundleComponentModule(filePath, appRoot) {
|
|
473
|
+
const entry = `./${filePath.split(path.sep).join('/')}`;
|
|
474
|
+
try {
|
|
475
|
+
const { metafile, outputFiles } = await esbuild.build({
|
|
476
|
+
absWorkingDir: appRoot,
|
|
477
|
+
alias: COMPONENT_ALIASES,
|
|
478
|
+
bundle: true,
|
|
479
|
+
format: 'esm',
|
|
480
|
+
jsx: 'automatic',
|
|
481
|
+
jsxImportSource: 'preact',
|
|
482
|
+
logLevel: 'silent',
|
|
483
|
+
metafile: true,
|
|
484
|
+
minify: true,
|
|
485
|
+
platform: 'browser',
|
|
486
|
+
plugins: [preactJsxPlugin],
|
|
487
|
+
stdin: {
|
|
488
|
+
contents: [
|
|
489
|
+
"import { h, render } from 'preact';",
|
|
490
|
+
`import Component from ${JSON.stringify(entry)};`,
|
|
491
|
+
'export function mount(root, props) { render(h(Component, props), root); }',
|
|
492
|
+
'export function update(root, props) { render(h(Component, props), root); }',
|
|
493
|
+
'export function unmount(root) { render(null, root); }',
|
|
494
|
+
].join('\n'),
|
|
495
|
+
loader: 'js',
|
|
496
|
+
resolveDir: appRoot,
|
|
497
|
+
sourcefile: COMPONENT_ENTRY,
|
|
498
|
+
},
|
|
499
|
+
target: 'es2020',
|
|
500
|
+
write: false,
|
|
501
|
+
});
|
|
502
|
+
const inputs = Object.keys(metafile.inputs).filter((input) => !input.includes('node_modules/') &&
|
|
503
|
+
!input.startsWith('<') &&
|
|
504
|
+
input !== COMPONENT_ENTRY);
|
|
505
|
+
return { code: outputFiles[0].text, inputs };
|
|
506
|
+
}
|
|
507
|
+
catch (error) {
|
|
508
|
+
const nonJs = /Cannot import "([^"]+)" into a JavaScript file|No loader is configured for "[^"]+" files: (\S+)/.exec(error.message);
|
|
509
|
+
let reason = error.message;
|
|
510
|
+
if (/Could not resolve "preact(?:\/[^"]*)?"/.test(error.message)) {
|
|
511
|
+
reason = 'install preact in the app (npm install preact)';
|
|
512
|
+
}
|
|
513
|
+
else if (nonJs) {
|
|
514
|
+
reason = `components cannot import CSS or other non-JavaScript files yet (${nonJs[1] ?? nonJs[2]}); use inline styles or a <style> element`;
|
|
515
|
+
}
|
|
516
|
+
throw new Error(`Unable to compile component "${filePath}": ${reason}`, {
|
|
517
|
+
cause: error,
|
|
518
|
+
});
|
|
519
|
+
}
|
|
520
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export declare const PREACT_VERSION = "^10.28.0";
|
|
2
|
+
export declare const APPS_SDK_VERSION = "^2.0.0-alpha.7";
|
|
3
|
+
export declare const toComponentName: (value: string) => string;
|
|
4
|
+
export interface ComponentTemplateOptions {
|
|
5
|
+
extension?: string;
|
|
6
|
+
targets?: string[];
|
|
7
|
+
}
|
|
8
|
+
export declare function getComponentTemplate(name: string, description: string, { extension, targets }?: ComponentTemplateOptions): string;
|
|
9
|
+
export interface ComponentSetupResult {
|
|
10
|
+
changed: string[];
|
|
11
|
+
dependenciesAdded: boolean;
|
|
12
|
+
warnings: string[];
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Adds preact and @swell/apps-sdk to package.json, and gives components their
|
|
16
|
+
* own tsconfig, excluded from the root one and checked by the typecheck script.
|
|
17
|
+
* Never overwrites values the app already has, except a 2.x SDK from before
|
|
18
|
+
* components.
|
|
19
|
+
* @param {string} appPath app directory
|
|
20
|
+
* @returns {Promise<ComponentSetupResult>} files changed and warnings to show
|
|
21
|
+
*/
|
|
22
|
+
export declare function setupComponentSupport(appPath: string): Promise<ComponentSetupResult>;
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import * as fs from 'node:fs/promises';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
import * as semver from 'semver';
|
|
4
|
+
export const PREACT_VERSION = '^10.28.0';
|
|
5
|
+
export const APPS_SDK_VERSION = '^2.0.0-alpha.7';
|
|
6
|
+
// The minimum SDK the scaffold needs
|
|
7
|
+
const COMPONENTS_SDK = semver.minVersion(APPS_SDK_VERSION);
|
|
8
|
+
// Example: 'color picker' => 'ColorPicker'
|
|
9
|
+
export const toComponentName = (value) => {
|
|
10
|
+
const name = value
|
|
11
|
+
.split('.')[0]
|
|
12
|
+
.split(/[^\dA-Za-z]+/)
|
|
13
|
+
.filter(Boolean)
|
|
14
|
+
.map((part) => part[0].toUpperCase() + part.slice(1))
|
|
15
|
+
.join('');
|
|
16
|
+
// A component name is also its function name, so it must start with a letter
|
|
17
|
+
return /^[A-Za-z]/.test(name) ? name : '';
|
|
18
|
+
};
|
|
19
|
+
export function getComponentTemplate(name, description, { extension, targets = [] } = {}) {
|
|
20
|
+
const config = [` description: ${JSON.stringify(description)},`];
|
|
21
|
+
if (extension) {
|
|
22
|
+
config.push(` extension: { id: ${JSON.stringify(extension)} },`);
|
|
23
|
+
}
|
|
24
|
+
if (targets.length > 0) {
|
|
25
|
+
const ids = targets.map((id) => `{ id: ${JSON.stringify(id)} }`);
|
|
26
|
+
config.push(` targets: [${ids.join(', ')}],`);
|
|
27
|
+
}
|
|
28
|
+
return `import type { ComponentProps } from '@swell/apps-sdk/components';
|
|
29
|
+
|
|
30
|
+
export const config = {
|
|
31
|
+
${config.join('\n')}
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export default function ${name}({ value, setValue, readonly }: ComponentProps<string>) {
|
|
35
|
+
return (
|
|
36
|
+
<input
|
|
37
|
+
value={value ?? ''}
|
|
38
|
+
disabled={readonly}
|
|
39
|
+
onInput={(event) => setValue(event.currentTarget.value)}
|
|
40
|
+
/>
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
`;
|
|
44
|
+
}
|
|
45
|
+
// undefined when the file is missing, null when it is not valid JSON
|
|
46
|
+
async function readJson(file) {
|
|
47
|
+
let text;
|
|
48
|
+
try {
|
|
49
|
+
text = await fs.readFile(file, 'utf8');
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return undefined;
|
|
53
|
+
}
|
|
54
|
+
try {
|
|
55
|
+
return JSON.parse(text);
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
const writeJson = (file, value) => fs.writeFile(file, `${JSON.stringify(value, null, 2)}\n`);
|
|
62
|
+
// Appended to the app's typecheck script, as the scaffold does for test/ and frontend/
|
|
63
|
+
const TYPECHECK_COMPONENTS = ' && ([ ! -f components/tsconfig.json ] || tsc --noEmit -p components)';
|
|
64
|
+
/**
|
|
65
|
+
* Components render in the browser with Preact, while the root tsconfig
|
|
66
|
+
* checks functions with the worker libs, which conflict with the DOM ones.
|
|
67
|
+
* Components get their own config instead.
|
|
68
|
+
* @param {boolean} extendsRoot whether the app has a root tsconfig to extend
|
|
69
|
+
* @returns {object} the contents of components/tsconfig.json
|
|
70
|
+
*/
|
|
71
|
+
function getComponentsTsconfig(extendsRoot) {
|
|
72
|
+
const compilerOptions = {
|
|
73
|
+
jsx: 'react-jsx',
|
|
74
|
+
jsxImportSource: 'preact',
|
|
75
|
+
lib: ['esnext', 'dom', 'dom.iterable'],
|
|
76
|
+
types: [],
|
|
77
|
+
// Relative to this file when no baseUrl is set
|
|
78
|
+
paths: {
|
|
79
|
+
react: ['../node_modules/preact/compat'],
|
|
80
|
+
'react-dom': ['../node_modules/preact/compat'],
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
if (extendsRoot) {
|
|
84
|
+
return {
|
|
85
|
+
extends: '../tsconfig.json',
|
|
86
|
+
compilerOptions,
|
|
87
|
+
include: ['**/*'],
|
|
88
|
+
// The root config excludes components, which would exclude every file here
|
|
89
|
+
exclude: [],
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
return {
|
|
93
|
+
compilerOptions: {
|
|
94
|
+
...compilerOptions,
|
|
95
|
+
module: 'esnext',
|
|
96
|
+
moduleResolution: 'bundler',
|
|
97
|
+
target: 'esnext',
|
|
98
|
+
noEmit: true,
|
|
99
|
+
},
|
|
100
|
+
include: ['**/*'],
|
|
101
|
+
exclude: [],
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Adds preact and @swell/apps-sdk to package.json, and gives components their
|
|
106
|
+
* own tsconfig, excluded from the root one and checked by the typecheck script.
|
|
107
|
+
* Never overwrites values the app already has, except a 2.x SDK from before
|
|
108
|
+
* components.
|
|
109
|
+
* @param {string} appPath app directory
|
|
110
|
+
* @returns {Promise<ComponentSetupResult>} files changed and warnings to show
|
|
111
|
+
*/
|
|
112
|
+
export async function setupComponentSupport(appPath) {
|
|
113
|
+
const result = {
|
|
114
|
+
changed: [],
|
|
115
|
+
dependenciesAdded: false,
|
|
116
|
+
warnings: [],
|
|
117
|
+
};
|
|
118
|
+
const packagePath = path.join(appPath, 'package.json');
|
|
119
|
+
const pkg = await readJson(packagePath);
|
|
120
|
+
if (pkg) {
|
|
121
|
+
if (!pkg.dependencies?.preact) {
|
|
122
|
+
pkg.dependencies = { ...pkg.dependencies, preact: PREACT_VERSION };
|
|
123
|
+
result.dependenciesAdded = true;
|
|
124
|
+
}
|
|
125
|
+
const sdkSection = ['dependencies', 'devDependencies'].find((section) => pkg[section]?.['@swell/apps-sdk']);
|
|
126
|
+
if (sdkSection) {
|
|
127
|
+
const range = pkg[sdkSection]['@swell/apps-sdk'];
|
|
128
|
+
const min = semver.validRange(range) ? semver.minVersion(range) : null;
|
|
129
|
+
if (min && semver.lt(min, COMPONENTS_SDK)) {
|
|
130
|
+
// `swell create tests` pinned an earlier 2.x; 1.x is another SDK the app may use
|
|
131
|
+
if (semver.major(min) === 2) {
|
|
132
|
+
pkg[sdkSection]['@swell/apps-sdk'] = APPS_SDK_VERSION;
|
|
133
|
+
result.dependenciesAdded = true;
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
result.warnings.push(`@swell/apps-sdk ${range} has no components: install @swell/apps-sdk@${APPS_SDK_VERSION} to type them.`);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
pkg.devDependencies = {
|
|
142
|
+
...pkg.devDependencies,
|
|
143
|
+
'@swell/apps-sdk': APPS_SDK_VERSION,
|
|
144
|
+
};
|
|
145
|
+
result.dependenciesAdded = true;
|
|
146
|
+
}
|
|
147
|
+
const typecheck = pkg.scripts?.typecheck;
|
|
148
|
+
const typecheckUpdated = typeof typecheck === 'string' && !typecheck.includes('components');
|
|
149
|
+
if (typecheckUpdated) {
|
|
150
|
+
pkg.scripts.typecheck = typecheck + TYPECHECK_COMPONENTS;
|
|
151
|
+
}
|
|
152
|
+
if (result.dependenciesAdded || typecheckUpdated) {
|
|
153
|
+
await writeJson(packagePath, pkg);
|
|
154
|
+
result.changed.push('package.json');
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
result.warnings.push(pkg === undefined
|
|
159
|
+
? 'package.json not found: run `npm init`, then `npm install preact` and `npm install -D @swell/apps-sdk`.'
|
|
160
|
+
: 'package.json could not be parsed: add preact to dependencies and @swell/apps-sdk to devDependencies.');
|
|
161
|
+
}
|
|
162
|
+
const tsconfigPath = path.join(appPath, 'tsconfig.json');
|
|
163
|
+
const tsconfig = await readJson(tsconfigPath);
|
|
164
|
+
if (tsconfig) {
|
|
165
|
+
const { exclude } = tsconfig;
|
|
166
|
+
if (!Array.isArray(exclude) || !exclude.includes('components')) {
|
|
167
|
+
// An explicit exclude replaces TypeScript's default one, so keep node_modules
|
|
168
|
+
tsconfig.exclude = [
|
|
169
|
+
...(Array.isArray(exclude) ? exclude : ['node_modules']),
|
|
170
|
+
'components',
|
|
171
|
+
];
|
|
172
|
+
await writeJson(tsconfigPath, tsconfig);
|
|
173
|
+
result.changed.push('tsconfig.json');
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
const componentsTsconfigPath = path.join(appPath, 'components', 'tsconfig.json');
|
|
177
|
+
await fs.mkdir(path.dirname(componentsTsconfigPath), { recursive: true });
|
|
178
|
+
const created = await fs
|
|
179
|
+
.writeFile(componentsTsconfigPath, `${JSON.stringify(getComponentsTsconfig(Boolean(tsconfig)), null, 2)}\n`, { flag: 'wx' })
|
|
180
|
+
.then(() => true, (error) => {
|
|
181
|
+
if (error.code !== 'EEXIST')
|
|
182
|
+
throw error;
|
|
183
|
+
return false;
|
|
184
|
+
});
|
|
185
|
+
if (created) {
|
|
186
|
+
result.changed.push('components/tsconfig.json');
|
|
187
|
+
}
|
|
188
|
+
if (tsconfig === null) {
|
|
189
|
+
result.warnings.push(created
|
|
190
|
+
? 'tsconfig.json could not be parsed: add "components" to its "exclude". components/tsconfig.json was created without "extends".'
|
|
191
|
+
: 'tsconfig.json could not be parsed: add "components" to its "exclude".');
|
|
192
|
+
}
|
|
193
|
+
return result;
|
|
194
|
+
}
|
|
@@ -13,7 +13,7 @@ interface ContentViewProps {
|
|
|
13
13
|
tabs: [];
|
|
14
14
|
}
|
|
15
15
|
type ContentView = 'edit' | 'list' | 'new';
|
|
16
|
-
type ContentType = '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';
|
|
16
|
+
type ContentType = 'asset' | 'basic_html' | 'boolean' | 'category_lookup' | 'checkbox' | 'checkboxes' | 'collection' | 'color' | 'color_scheme' | 'color_scheme_group' | 'component' | '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';
|
|
17
17
|
interface ContentFieldProps {
|
|
18
18
|
id: string;
|
|
19
19
|
label: string;
|
|
@@ -9,7 +9,7 @@ interface SettingFieldProps {
|
|
|
9
9
|
label: string;
|
|
10
10
|
type?: SettingFieldType;
|
|
11
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';
|
|
12
|
+
type SettingFieldType = 'asset' | 'basic_html' | 'boolean' | 'category_lookup' | 'checkbox' | 'checkboxes' | 'collection' | 'color' | 'color_scheme' | 'color_scheme_group' | 'component' | '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
13
|
export declare function parseFields(fields: string[]): SettingFieldProps[];
|
|
14
14
|
export declare const toSettingLabel: (value: string) => string;
|
|
15
15
|
export {};
|
|
@@ -1,8 +1,5 @@
|
|
|
1
|
-
export { envDtsTemplate } from './env-dts.js';
|
|
2
|
-
export { integrationTestTemplate } from './integration-test.js';
|
|
3
1
|
export { mockRequestTemplate } from './mock-request.js';
|
|
4
2
|
export { setupGlobalsTemplate } from './setup-globals.js';
|
|
5
|
-
export { swellClientTemplate } from './swell-client.js';
|
|
6
3
|
export { tsconfigTemplate } from './tsconfig.js';
|
|
7
4
|
export { unitTestTemplate } from './unit-test.js';
|
|
8
5
|
export { vitestConfigTemplate } from './vitest-config.js';
|
|
@@ -1,8 +1,5 @@
|
|
|
1
|
-
export { envDtsTemplate } from './env-dts.js';
|
|
2
|
-
export { integrationTestTemplate } from './integration-test.js';
|
|
3
1
|
export { mockRequestTemplate } from './mock-request.js';
|
|
4
2
|
export { setupGlobalsTemplate } from './setup-globals.js';
|
|
5
|
-
export { swellClientTemplate } from './swell-client.js';
|
|
6
3
|
export { tsconfigTemplate } from './tsconfig.js';
|
|
7
4
|
export { unitTestTemplate } from './unit-test.js';
|
|
8
5
|
export { vitestConfigTemplate } from './vitest-config.js';
|
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
export function mockRequestTemplate(options) {
|
|
2
2
|
return `\
|
|
3
|
-
import { env } from "cloudflare:test";
|
|
4
|
-
import { createSwellClient } from "./swell-client";
|
|
5
|
-
|
|
6
3
|
export interface MockRequestOptions {
|
|
7
4
|
data?: SwellData;
|
|
8
5
|
query?: { [key: string]: string };
|
|
@@ -13,7 +10,6 @@ export interface MockRequestOptions {
|
|
|
13
10
|
swell?: Partial<SwellAPI>;
|
|
14
11
|
store?: Partial<SwellStore>;
|
|
15
12
|
appId?: string;
|
|
16
|
-
useRealSwell?: boolean;
|
|
17
13
|
}
|
|
18
14
|
|
|
19
15
|
function createDefaultSwellMock(): Partial<SwellAPI> {
|
|
@@ -42,7 +38,6 @@ export function createMockRequest(options: MockRequestOptions = {}): SwellReques
|
|
|
42
38
|
swell,
|
|
43
39
|
store,
|
|
44
40
|
appId,
|
|
45
|
-
useRealSwell = false,
|
|
46
41
|
} = options;
|
|
47
42
|
|
|
48
43
|
const requestHeaders = new Headers(headers);
|
|
@@ -58,7 +53,7 @@ export function createMockRequest(options: MockRequestOptions = {}): SwellReques
|
|
|
58
53
|
body: method === "GET" ? undefined : JSON.stringify(data),
|
|
59
54
|
});
|
|
60
55
|
|
|
61
|
-
const storeId = store?.id ||
|
|
56
|
+
const storeId = store?.id || "test-store";
|
|
62
57
|
|
|
63
58
|
const resolvedStore = {
|
|
64
59
|
id: storeId,
|
|
@@ -72,10 +67,9 @@ export function createMockRequest(options: MockRequestOptions = {}): SwellReques
|
|
|
72
67
|
},
|
|
73
68
|
};
|
|
74
69
|
|
|
75
|
-
const swellClient =
|
|
76
|
-
swell || (useRealSwell ? createSwellClient() : createDefaultSwellMock());
|
|
70
|
+
const swellClient = swell || createDefaultSwellMock();
|
|
77
71
|
|
|
78
|
-
const resolvedAppId = appId ||
|
|
72
|
+
const resolvedAppId = appId || "${options.appId}";
|
|
79
73
|
|
|
80
74
|
const req: Partial<SwellRequest> & { appId: string; storeId: string } = {
|
|
81
75
|
originalRequest,
|
|
@@ -91,7 +85,7 @@ export function createMockRequest(options: MockRequestOptions = {}): SwellReques
|
|
|
91
85
|
publicKey: null,
|
|
92
86
|
store: resolvedStore,
|
|
93
87
|
session,
|
|
94
|
-
apiHost:
|
|
88
|
+
apiHost: "",
|
|
95
89
|
logParams: undefined,
|
|
96
90
|
swell: swellClient as SwellAPI,
|
|
97
91
|
rawBody: method === "GET" ? "" : JSON.stringify(data),
|
|
@@ -1,2 +1 @@
|
|
|
1
|
-
|
|
2
|
-
export declare function vitestConfigTemplate(options: TestTemplateOptions): string;
|
|
1
|
+
export declare function vitestConfigTemplate(): string;
|