@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.
Files changed (44) hide show
  1. package/dist/commands/app/dev.d.ts +1 -0
  2. package/dist/commands/app/dev.js +26 -2
  3. package/dist/commands/create/component.d.ts +22 -0
  4. package/dist/commands/create/component.js +129 -0
  5. package/dist/commands/create/index.js +5 -1
  6. package/dist/commands/create/tests.js +11 -2
  7. package/dist/commands/inspect/extensions.js +3 -3
  8. package/dist/lib/apps/app-config.d.ts +15 -0
  9. package/dist/lib/apps/app-config.js +99 -30
  10. package/dist/lib/apps/component-extension.d.ts +23 -0
  11. package/dist/lib/apps/component-extension.js +60 -0
  12. package/dist/lib/apps/components.d.ts +32 -0
  13. package/dist/lib/apps/components.js +81 -0
  14. package/dist/lib/apps/frontend-scaffold.js +1 -1
  15. package/dist/lib/apps/index.d.ts +1 -0
  16. package/dist/lib/apps/index.js +5 -3
  17. package/dist/lib/bundle.d.ts +21 -1
  18. package/dist/lib/bundle.js +88 -5
  19. package/dist/lib/create/component.d.ts +22 -0
  20. package/dist/lib/create/component.js +194 -0
  21. package/dist/lib/create/content.d.ts +1 -1
  22. package/dist/lib/create/setting.d.ts +1 -1
  23. package/dist/lib/create/tests/templates/index.d.ts +0 -3
  24. package/dist/lib/create/tests/templates/index.js +0 -3
  25. package/dist/lib/create/tests/templates/mock-request.js +4 -10
  26. package/dist/lib/create/tests/templates/tsconfig.js +1 -1
  27. package/dist/lib/create/tests/templates/vitest-config.d.ts +1 -2
  28. package/dist/lib/create/tests/templates/vitest-config.js +17 -113
  29. package/dist/lib/create/tests/types.d.ts +2 -0
  30. package/dist/lib/create/tests.js +64 -12
  31. package/dist/lib/function-source-analysis.d.ts +11 -0
  32. package/dist/lib/function-source-analysis.js +33 -0
  33. package/dist/lib/inspect/extensions.d.ts +8 -1
  34. package/dist/lib/inspect/extensions.js +12 -2
  35. package/dist/lib/swell-function-wrapper.js +31 -0
  36. package/dist/push-app-command.js +11 -0
  37. package/oclif.manifest.json +82 -3
  38. package/package.json +3 -1
  39. package/dist/lib/create/tests/templates/env-dts.d.ts +0 -1
  40. package/dist/lib/create/tests/templates/env-dts.js +0 -15
  41. package/dist/lib/create/tests/templates/integration-test.d.ts +0 -1
  42. package/dist/lib/create/tests/templates/integration-test.js +0 -19
  43. package/dist/lib/create/tests/templates/swell-client.d.ts +0 -1
  44. 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.ts'],
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.
@@ -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 {
@@ -52,9 +52,11 @@ export var ConfigType;
52
52
  ConfigType["THEME"] = "theme";
53
53
  ConfigType["WEBHOOK"] = "webhook";
54
54
  })(ConfigType || (ConfigType = {}));
55
- // Models and content should come first in order
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) {
@@ -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
- export declare function bundleComponent(filePath: string): Promise<any>;
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>;
@@ -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
- const { outputFiles: [{ text: code }], } = buildResult;
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 || env.SWELL_STORE_ID || "test-store";
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 || env.SWELL_APP_ID || "${options.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: env.SWELL_API_BASE_URL || "",
88
+ apiHost: "",
95
89
  logParams: undefined,
96
90
  swell: swellClient as SwellAPI,
97
91
  rawBody: method === "GET" ? "" : JSON.stringify(data),
@@ -5,7 +5,7 @@ export function tsconfigTemplate() {
5
5
  moduleResolution: 'bundler',
6
6
  skipLibCheck: true,
7
7
  types: [
8
- '@cloudflare/vitest-pool-workers',
8
+ '@cloudflare/vitest-plugin/types',
9
9
  '@swell/app-types',
10
10
  'vitest/globals',
11
11
  ],
@@ -1,2 +1 @@
1
- import type { TestTemplateOptions } from '../types.js';
2
- export declare function vitestConfigTemplate(options: TestTemplateOptions): string;
1
+ export declare function vitestConfigTemplate(): string;