@swell/cli 2.9.17 → 2.9.19
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/README.md +90 -0
- package/dist/commands/app/dev.js +21 -34
- package/dist/commands/app/frontend/deploy.js +3 -3
- package/dist/commands/app/frontend/dev.js +5 -2
- package/dist/commands/app/init.js +12 -2
- package/dist/commands/create/app.d.ts +10 -1
- package/dist/commands/create/app.js +48 -25
- package/dist/commands/create/frontend.d.ts +3 -1
- package/dist/commands/create/frontend.js +21 -13
- package/dist/create-app-command.d.ts +10 -51
- package/dist/create-app-command.js +99 -526
- package/dist/help/custom-help.js +28 -7
- package/dist/help/types.d.ts +2 -0
- package/dist/lib/app-config.js +6 -0
- package/dist/lib/apps/frontend-package.d.ts +36 -0
- package/dist/lib/apps/frontend-package.js +375 -0
- package/dist/lib/apps/frontend-routes.d.ts +1 -0
- package/dist/lib/apps/frontend-routes.js +40 -0
- package/dist/lib/apps/frontend-scaffold.d.ts +35 -0
- package/dist/lib/apps/frontend-scaffold.js +502 -0
- package/dist/lib/apps/frontend-templates.d.ts +32 -0
- package/dist/lib/apps/frontend-templates.js +137 -0
- package/dist/lib/apps/index.d.ts +7 -7
- package/dist/lib/apps/index.js +10 -33
- package/dist/lib/apps/storefront-resources.d.ts +3 -2
- package/dist/lib/apps/storefront-resources.js +3 -2
- package/dist/push-app-command.js +22 -0
- package/dist/remote-app-command.js +6 -0
- package/dist/types/index.d.ts +5 -0
- package/oclif.manifest.json +63 -94
- package/package.json +5 -3
package/README.md
CHANGED
|
@@ -1,3 +1,93 @@
|
|
|
1
1
|
# @swell/cli 2 alpha
|
|
2
2
|
|
|
3
3
|
Find the alpha docs at https://www.notion.so/swellcorp/CLI-reference-c3553ebb3de24d94becbedf95a5a25d8?pvs=4
|
|
4
|
+
|
|
5
|
+
## Frontend scaffolding
|
|
6
|
+
|
|
7
|
+
Run `swell create app` for interactive setup. The frontend picker groups templates
|
|
8
|
+
by hosting mode. App creation without a frontend remains supported.
|
|
9
|
+
|
|
10
|
+
| `--frontend` | Starter | Hosting |
|
|
11
|
+
| --- | --- | --- |
|
|
12
|
+
| `swell-react` | React + Vite, with server endpoints | Swell managed |
|
|
13
|
+
| `swell-vinext` | Vinext, with server rendering and server components | Swell managed |
|
|
14
|
+
| `swell-spa` | Client-only React storefront with commerce hooks and UI components | Swell managed; storefront apps only |
|
|
15
|
+
| `nextjs`, `astro`, `nuxt`, `react`, `hono`, `angular` | Framework starter | Self-hosted; requires your own Cloudflare account |
|
|
16
|
+
| `none` | No frontend | Default for `create app -y` |
|
|
17
|
+
|
|
18
|
+
```sh
|
|
19
|
+
swell create app my-app -t admin --frontend swell-react -y
|
|
20
|
+
swell create app my-app -t admin --frontend swell-vinext -y
|
|
21
|
+
swell create app my-store -t storefront --frontend swell-spa -p bun -y
|
|
22
|
+
swell create app my-app -t admin --frontend astro -y
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Managed starters come from
|
|
26
|
+
[`swell-app-templates`](https://github.com/swellstores/swell-app-templates).
|
|
27
|
+
Use Node.js 22.22.2 or newer and select a package manager with
|
|
28
|
+
`--pkg npm|yarn|pnpm|bun`. pnpm must allow the `esbuild` and `workerd` install
|
|
29
|
+
scripts. Hosting is recorded in `swell.json`; no developer Cloudflare account is
|
|
30
|
+
needed for managed hosting.
|
|
31
|
+
|
|
32
|
+
From the generated app directory, run `swell app frontend dev` for local
|
|
33
|
+
development and `swell app push` to deploy through Swell. Managed hosting requires
|
|
34
|
+
a compatible Swell platform deployment.
|
|
35
|
+
The SPA starter includes commerce components but starts with an empty route tree;
|
|
36
|
+
add storefront pages before expecting a visible storefront. SPA creation also
|
|
37
|
+
creates the development app and storefront in your configured test store.
|
|
38
|
+
|
|
39
|
+
To add a frontend to an existing app:
|
|
40
|
+
|
|
41
|
+
```sh
|
|
42
|
+
swell create frontend
|
|
43
|
+
swell create frontend --frontend swell-react -y
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
`create frontend -y` requires an explicit template. Without `--pkg` it uses the
|
|
47
|
+
package manager of the workspace lockfile, as `app frontend dev` and `app push`
|
|
48
|
+
do. It preserves existing frontend files and does not provision remote
|
|
49
|
+
storefront resources. `swell app init` only creates `swell.json`; use
|
|
50
|
+
`create frontend` afterward to scaffold a frontend.
|
|
51
|
+
|
|
52
|
+
Existing generic framework values retain self-hosted behavior. The former
|
|
53
|
+
internal `react-storefront` value is accepted as a hidden alias for managed
|
|
54
|
+
`swell-spa`; existing app deployments keep their configured hosting. The managed
|
|
55
|
+
SPA has no supported self-hosted scaffold at this time.
|
|
56
|
+
|
|
57
|
+
## Managed deployment failures
|
|
58
|
+
|
|
59
|
+
If managed deployment fails, Swell retains the uploaded package and keeps the
|
|
60
|
+
working frontend active. Correct any reported errors and repeat `swell app push`
|
|
61
|
+
or `swell app frontend deploy`. These commands build and upload the frontend
|
|
62
|
+
again; no separate retry command is needed.
|
|
63
|
+
|
|
64
|
+
If another operation is updating the app, Swell returns 409 without waiting.
|
|
65
|
+
Wait briefly and repeat the install, upgrade or deployment. An activation-write
|
|
66
|
+
failure also returns 409; provider outages remain server errors. A merchant who
|
|
67
|
+
cannot install an app because its frontend is invalid should contact the app
|
|
68
|
+
developer.
|
|
69
|
+
|
|
70
|
+
Managed frontend runtime logs are not available in this release. `swell logs`
|
|
71
|
+
does not include managed frontend Worker console output or runtime exceptions.
|
|
72
|
+
Use local frontend development logs to investigate application behavior and the
|
|
73
|
+
deployment error returned by the CLI for build/upload failures. For deployment
|
|
74
|
+
support, provide the app ID, selected version, store/environment, operation time
|
|
75
|
+
and error code/message; do not include credentials or session cookies.
|
|
76
|
+
|
|
77
|
+
## Frontend routes in development and deployment
|
|
78
|
+
|
|
79
|
+
Swell owns `/api` and its existing platform endpoints on Swell-routed domains.
|
|
80
|
+
Use native paths such as `/app-api/quote` for frontend handlers. Calls to
|
|
81
|
+
`fetch('/api/products')` still reach Swell. The React and Vinext starters use
|
|
82
|
+
`/app-api`; the SPA starter remains client-only.
|
|
83
|
+
|
|
84
|
+
Development and deployment warn about conventional Next/Vinext App Router and
|
|
85
|
+
Pages Router API handlers under `/api`, including `src/` layouts. This check
|
|
86
|
+
does not analyze arbitrary Worker routing, rewrites, custom route extensions,
|
|
87
|
+
or every platform endpoint. It does not move source files or change a base path.
|
|
88
|
+
|
|
89
|
+
Existing handlers can use `/<app_private_id>/api/...` as a compatibility mount
|
|
90
|
+
for the app selected by the domain. The prefix is removed before forwarding,
|
|
91
|
+
with the query retained. A different app's prefix cannot select that app.
|
|
92
|
+
Redirects, callback URLs and cookie paths may need explicit configuration for
|
|
93
|
+
this mount. Storefront password protection still applies.
|
package/dist/commands/app/dev.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Flags } from '@oclif/core';
|
|
2
2
|
import getPort, { portNumbers } from 'get-port';
|
|
3
|
+
import httpProxy from 'http-proxy';
|
|
3
4
|
import { spawn } from 'node:child_process';
|
|
4
5
|
import * as fs from 'node:fs/promises';
|
|
5
6
|
import * as http from 'node:http';
|
|
@@ -148,29 +149,17 @@ export default class AppDev extends PushAppCommand {
|
|
|
148
149
|
}
|
|
149
150
|
}
|
|
150
151
|
async createFunctionRouter(serverPort) {
|
|
152
|
+
const proxy = httpProxy.createProxyServer();
|
|
151
153
|
const server = http.createServer((req, res) => {
|
|
152
154
|
const url = new URL(req.url, `http://localhost:${serverPort}`);
|
|
153
155
|
const functionName = url.pathname.slice(1); // Remove leading slash
|
|
156
|
+
const fail = (status, message) => {
|
|
157
|
+
if (!res.headersSent)
|
|
158
|
+
res.writeHead(status);
|
|
159
|
+
res.end(message);
|
|
160
|
+
};
|
|
154
161
|
if (this.functionPorts.has(functionName)) {
|
|
155
162
|
const targetPort = this.functionPorts.get(functionName);
|
|
156
|
-
// Proxy the request to the function server
|
|
157
|
-
const proxyReq = http.request({
|
|
158
|
-
hostname: 'localhost',
|
|
159
|
-
port: targetPort,
|
|
160
|
-
path: req.url,
|
|
161
|
-
method: req.method,
|
|
162
|
-
headers: {
|
|
163
|
-
...req.headers,
|
|
164
|
-
'Swell-Local-Dev': 'true',
|
|
165
|
-
},
|
|
166
|
-
}, (proxyRes) => {
|
|
167
|
-
res.writeHead(proxyRes.statusCode, proxyRes.headers);
|
|
168
|
-
proxyRes.pipe(res);
|
|
169
|
-
});
|
|
170
|
-
proxyReq.on('error', (error) => {
|
|
171
|
-
res.writeHead(500);
|
|
172
|
-
res.end(`Error proxying to function ${functionName}: ${error.message}`);
|
|
173
|
-
});
|
|
174
163
|
// Log when response is finished, method, status, and time to execute by proxy
|
|
175
164
|
const startTime = Date.now();
|
|
176
165
|
res.on('finish', async () => {
|
|
@@ -181,25 +170,15 @@ export default class AppDev extends PushAppCommand {
|
|
|
181
170
|
});
|
|
182
171
|
this.log(`\n${style.appConfigValue(`→ ${functionName}`)} ${this.timestampStyled()} [${res.statusCode}] ${req.method} ${req.url} (${duration}ms)\n`);
|
|
183
172
|
});
|
|
184
|
-
|
|
173
|
+
// Proxy the request to the function server
|
|
174
|
+
proxy.web(req, res, {
|
|
175
|
+
target: `http://localhost:${targetPort}`,
|
|
176
|
+
headers: { 'Swell-Local-Dev': 'true' },
|
|
177
|
+
}, (error) => fail(500, `Error proxying to function ${functionName}: ${error.message}`));
|
|
185
178
|
}
|
|
186
179
|
else if (this.frontendPort) {
|
|
187
180
|
// Proxy non-function requests to frontend dev server
|
|
188
|
-
|
|
189
|
-
hostname: 'localhost',
|
|
190
|
-
port: this.frontendPort,
|
|
191
|
-
path: req.url,
|
|
192
|
-
method: req.method,
|
|
193
|
-
headers: req.headers,
|
|
194
|
-
}, (proxyRes) => {
|
|
195
|
-
res.writeHead(proxyRes.statusCode, proxyRes.headers);
|
|
196
|
-
proxyRes.pipe(res);
|
|
197
|
-
});
|
|
198
|
-
proxyReq.on('error', (error) => {
|
|
199
|
-
res.writeHead(502);
|
|
200
|
-
res.end(`Frontend dev server error: ${error.message}`);
|
|
201
|
-
});
|
|
202
|
-
req.pipe(proxyReq);
|
|
181
|
+
proxy.web(req, res, { target: `http://localhost:${this.frontendPort}` }, (error) => fail(502, `Frontend dev server error: ${error.message}`));
|
|
203
182
|
}
|
|
204
183
|
else {
|
|
205
184
|
res.writeHead(404);
|
|
@@ -212,6 +191,13 @@ export default class AppDev extends PushAppCommand {
|
|
|
212
191
|
: ''}`);
|
|
213
192
|
}
|
|
214
193
|
});
|
|
194
|
+
server.on('upgrade', (req, socket, head) => {
|
|
195
|
+
if (!this.frontendPort) {
|
|
196
|
+
socket.destroy();
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
proxy.ws(req, socket, head, { target: `http://localhost:${this.frontendPort}` }, () => socket.destroy());
|
|
200
|
+
});
|
|
215
201
|
await new Promise((resolve, reject) => {
|
|
216
202
|
server.once('error', reject);
|
|
217
203
|
server.listen(serverPort, () => {
|
|
@@ -219,6 +205,7 @@ export default class AppDev extends PushAppCommand {
|
|
|
219
205
|
resolve();
|
|
220
206
|
});
|
|
221
207
|
});
|
|
208
|
+
return server;
|
|
222
209
|
}
|
|
223
210
|
emitLifecycleEvent(type, data) {
|
|
224
211
|
if (!this.jsonEvents)
|
|
@@ -2,9 +2,9 @@ import { Flags } from '@oclif/core';
|
|
|
2
2
|
import { PushAppCommand } from '../../../push-app-command.js';
|
|
3
3
|
export default class AppFrontendDeploy extends PushAppCommand {
|
|
4
4
|
static description = `Creates a new build using the relevant command depending on your frontend framework.
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
Managed frontends are uploaded to Swell and deployed using Swell's Cloudflare account.
|
|
6
|
+
Self-hosted frontends deploy through Wrangler using your own Cloudflare account.
|
|
7
|
+
If deployment fails, correct any reported errors and run this command again.`;
|
|
8
8
|
static examples = [
|
|
9
9
|
'swell app frontend deploy',
|
|
10
10
|
'swell app frontend deploy --force',
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Flags } from '@oclif/core';
|
|
2
|
+
import { warnFrontendRouteCollisions } from '../../../lib/apps/frontend-routes.js';
|
|
2
3
|
import { CUSTOM_FRAMEWORK_SLUG, getManualDevHint, getProjectCommands, } from '../../../lib/apps/index.js';
|
|
3
4
|
import { PushAppCommand } from '../../../push-app-command.js';
|
|
4
5
|
import AppDev from '../dev.js';
|
|
@@ -76,6 +77,7 @@ export default class AppFrontendDev extends PushAppCommand {
|
|
|
76
77
|
if (!projectType) {
|
|
77
78
|
return;
|
|
78
79
|
}
|
|
80
|
+
await warnFrontendRouteCollisions(this.appPath, projectType.slug, (message) => this.warn(message));
|
|
79
81
|
// Manual mode for unrecognized frameworks
|
|
80
82
|
if (projectType.slug === CUSTOM_FRAMEWORK_SLUG) {
|
|
81
83
|
const hintCommand = await getManualDevHint(this.appPath, serverPort);
|
|
@@ -189,9 +191,10 @@ export default class AppFrontendDev extends PushAppCommand {
|
|
|
189
191
|
break;
|
|
190
192
|
}
|
|
191
193
|
case 'react':
|
|
192
|
-
case 'react-storefront':
|
|
194
|
+
case 'react-storefront':
|
|
195
|
+
case 'vinext': {
|
|
193
196
|
// Vite emits the Local URL as its ready signal — no separate
|
|
194
|
-
// detector needed.
|
|
197
|
+
// detector needed. Vinext dev delegates URL printing to Vite.
|
|
195
198
|
if (/➜\s+local:\s+http:\/\//i.test(string) ||
|
|
196
199
|
/➜\s+network:/i.test(string)) {
|
|
197
200
|
if (!serverReadyDetected) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Args } from '@oclif/core';
|
|
1
|
+
import { Args, Flags } from '@oclif/core';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import CreateApp, { appHelpMetaBase } from '../create/app.js';
|
|
4
4
|
export default class InitApp extends CreateApp {
|
|
@@ -16,22 +16,32 @@ export default class InitApp extends CreateApp {
|
|
|
16
16
|
'$ swell app init my-app',
|
|
17
17
|
'$ swell app init -t admin -y',
|
|
18
18
|
'$ swell app init my-app -t admin -y',
|
|
19
|
-
'$ swell app init my-app -t admin --frontend astro -y',
|
|
20
19
|
'$ swell app init my_theme -t theme --storefront-app proxima -y',
|
|
21
20
|
];
|
|
22
21
|
static helpMeta = {
|
|
23
22
|
...appHelpMetaBase,
|
|
23
|
+
typeSection: {
|
|
24
|
+
...appHelpMetaBase.typeSection,
|
|
25
|
+
types: appHelpMetaBase.typeSection.types.map((type) => ({
|
|
26
|
+
...type,
|
|
27
|
+
optional: [],
|
|
28
|
+
})),
|
|
29
|
+
},
|
|
24
30
|
usageDirect: '[id] -t <type> [...] -y',
|
|
25
31
|
};
|
|
26
32
|
// Inherit all flags from CreateApp (includes -p, -t, -n, -v, -d)
|
|
27
33
|
// baseFlags from CreateAppCommand (-y, --frontend, etc.) are merged automatically by oclif
|
|
28
34
|
static flags = {
|
|
29
35
|
...CreateApp.flags,
|
|
36
|
+
frontend: Flags.string({ hidden: true }),
|
|
30
37
|
};
|
|
31
38
|
async run() {
|
|
32
39
|
const { args, flags } = await this.parse(InitApp);
|
|
33
40
|
const { id: argId } = args;
|
|
34
41
|
const { yes: confirmYes, type, name, version, description, frontend, } = flags;
|
|
42
|
+
if (frontend !== undefined) {
|
|
43
|
+
this.error('app init only creates swell.json. Run swell create frontend --frontend <template> afterward.');
|
|
44
|
+
}
|
|
35
45
|
// Use provided id argument, or fall back to current directory name
|
|
36
46
|
const id = argId || process.cwd().split(path.sep).pop() || 'Swell App';
|
|
37
47
|
await this.createSwellConfig({
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { FrontendTemplate } from '../../lib/apps/frontend-templates.js';
|
|
1
2
|
import { CreateAppCommand } from '../../create-app-command.js';
|
|
2
3
|
import { HelpMeta } from '../../help/types.js';
|
|
3
4
|
/**
|
|
@@ -16,7 +17,7 @@ export default class CreateApp extends CreateAppCommand {
|
|
|
16
17
|
appType: string;
|
|
17
18
|
private jsonOutput;
|
|
18
19
|
log(message?: string, ...args: any[]): void;
|
|
19
|
-
createSwellConfig({ allowOverwrite, inputId, inputStorefrontApp, inputType, inputName, inputVersion, inputDescription, inputFrontend, inputIntegrationType, inputIntegrationId, inputYes, nestedPath, }: {
|
|
20
|
+
createSwellConfig({ allowOverwrite, inputId, inputStorefrontApp, inputType, inputName, inputVersion, inputDescription, inputFrontend, inputIntegrationType, inputIntegrationId, inputYes, nestedPath, scaffoldFrontend, }: {
|
|
20
21
|
allowOverwrite?: boolean;
|
|
21
22
|
inputDescription?: string;
|
|
22
23
|
inputFrontend?: string;
|
|
@@ -29,11 +30,15 @@ export default class CreateApp extends CreateAppCommand {
|
|
|
29
30
|
inputVersion?: string;
|
|
30
31
|
inputYes?: boolean;
|
|
31
32
|
nestedPath?: boolean;
|
|
33
|
+
scaffoldFrontend?: boolean;
|
|
32
34
|
}): Promise<{
|
|
33
35
|
appId: string;
|
|
34
36
|
config: import("conf").default<Record<string, unknown>>;
|
|
35
37
|
installedStorefrontApp: any;
|
|
36
38
|
swellConfigJson: {
|
|
39
|
+
frontend?: {
|
|
40
|
+
hosting: "managed" | "self-hosted";
|
|
41
|
+
} | undefined;
|
|
37
42
|
extensions?: any[] | undefined;
|
|
38
43
|
theme: {
|
|
39
44
|
storefront: {
|
|
@@ -46,6 +51,9 @@ export default class CreateApp extends CreateAppCommand {
|
|
|
46
51
|
type: string;
|
|
47
52
|
version: string;
|
|
48
53
|
} | {
|
|
54
|
+
frontend?: {
|
|
55
|
+
hosting: "managed" | "self-hosted";
|
|
56
|
+
} | undefined;
|
|
49
57
|
extensions?: any[] | undefined;
|
|
50
58
|
permissions: never[];
|
|
51
59
|
description: string;
|
|
@@ -54,6 +62,7 @@ export default class CreateApp extends CreateAppCommand {
|
|
|
54
62
|
type: string;
|
|
55
63
|
version: string;
|
|
56
64
|
};
|
|
65
|
+
template: FrontendTemplate | undefined;
|
|
57
66
|
} | undefined>;
|
|
58
67
|
run(): Promise<void>;
|
|
59
68
|
}
|
|
@@ -5,6 +5,7 @@ import ora from 'ora';
|
|
|
5
5
|
import { CreateAppCommand } from '../../create-app-command.js';
|
|
6
6
|
import { newConfig, swellConfigFileExists } from '../../lib/app-config.js';
|
|
7
7
|
import { buildCreateAppResult } from '../../lib/apps/create-app-result.js';
|
|
8
|
+
import { setupPackage } from '../../lib/apps/frontend-scaffold.js';
|
|
8
9
|
import { toAppId, toAppName } from '../../lib/create/index.js';
|
|
9
10
|
import style from '../../lib/style.js';
|
|
10
11
|
/**
|
|
@@ -33,6 +34,7 @@ export const appHelpMetaBase = {
|
|
|
33
34
|
{
|
|
34
35
|
name: 'integration',
|
|
35
36
|
description: 'Third-party service',
|
|
37
|
+
optional: ['--frontend'],
|
|
36
38
|
requires: [
|
|
37
39
|
'--integration-type',
|
|
38
40
|
'--integration-id (for payment | shipping | tax)',
|
|
@@ -58,16 +60,25 @@ export default class CreateApp extends CreateAppCommand {
|
|
|
58
60
|
static examples = [
|
|
59
61
|
'$ swell create app',
|
|
60
62
|
'$ swell create app my_app -t admin -y',
|
|
61
|
-
'$ swell create app my_app -t admin --frontend
|
|
63
|
+
'$ swell create app my_app -t admin --frontend swell-react -y',
|
|
64
|
+
'$ swell create app my_shop -t storefront --frontend swell-spa -y',
|
|
62
65
|
'$ swell create app my_theme -t theme --storefront-app proxima -y',
|
|
63
66
|
'$ swell create app my_payment -t integration --integration-type payment --integration-id card -y',
|
|
64
|
-
'$ swell create app my_shipping -t integration --integration-type shipping --integration-id fedex -y',
|
|
65
67
|
];
|
|
66
68
|
static helpMeta = {
|
|
67
69
|
...appHelpMetaBase,
|
|
70
|
+
frontendTemplates: 'optional',
|
|
68
71
|
usageDirect: '<id> -t <type> [...] -y',
|
|
69
72
|
};
|
|
70
73
|
static flags = {
|
|
74
|
+
'storefront-app': Flags.string({ description: 'Target storefront app ID' }),
|
|
75
|
+
'integration-type': Flags.string({
|
|
76
|
+
description: 'Integration: generic | payment | shipping | tax',
|
|
77
|
+
options: ['generic', 'payment', 'shipping', 'tax'],
|
|
78
|
+
}),
|
|
79
|
+
'integration-id': Flags.string({
|
|
80
|
+
description: 'Service ID (e.g., card, fedex)',
|
|
81
|
+
}),
|
|
71
82
|
pkg: Flags.string({
|
|
72
83
|
char: 'p',
|
|
73
84
|
default: 'npm',
|
|
@@ -101,7 +112,7 @@ export default class CreateApp extends CreateAppCommand {
|
|
|
101
112
|
if (!this.jsonOutput)
|
|
102
113
|
super.log(message, ...args);
|
|
103
114
|
}
|
|
104
|
-
async createSwellConfig({ allowOverwrite = true, inputId = '', inputStorefrontApp, inputType, inputName, inputVersion, inputDescription, inputFrontend, inputIntegrationType, inputIntegrationId, inputYes, nestedPath = true, }) {
|
|
115
|
+
async createSwellConfig({ allowOverwrite = true, inputId = '', inputStorefrontApp, inputType, inputName, inputVersion, inputDescription, inputFrontend, inputIntegrationType, inputIntegrationId, inputYes, nestedPath = true, scaffoldFrontend = false, }) {
|
|
105
116
|
const confirmYes = inputYes;
|
|
106
117
|
let appId = toAppId(inputId || '');
|
|
107
118
|
let storefrontInstalledApps;
|
|
@@ -292,16 +303,15 @@ export default class CreateApp extends CreateAppCommand {
|
|
|
292
303
|
});
|
|
293
304
|
}
|
|
294
305
|
}
|
|
295
|
-
|
|
296
|
-
if (
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
}
|
|
306
|
+
let template;
|
|
307
|
+
if (scaffoldFrontend && type !== 'theme') {
|
|
308
|
+
template = await this.selectFrontend(type, {
|
|
309
|
+
frontend: inputFrontend,
|
|
310
|
+
yes: confirmYes,
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
else if (inputFrontend && inputFrontend !== 'none') {
|
|
314
|
+
this.error('A theme cannot have a frontend.');
|
|
305
315
|
}
|
|
306
316
|
const swellConfigJson = {
|
|
307
317
|
description,
|
|
@@ -321,6 +331,7 @@ export default class CreateApp extends CreateAppCommand {
|
|
|
321
331
|
permissions: [],
|
|
322
332
|
}),
|
|
323
333
|
...(extensions && { extensions }),
|
|
334
|
+
...(template && { frontend: { hosting: template.hosting } }),
|
|
324
335
|
};
|
|
325
336
|
const appPath = nestedPath
|
|
326
337
|
? path.join(process.cwd(), inputId || appId)
|
|
@@ -345,6 +356,12 @@ export default class CreateApp extends CreateAppCommand {
|
|
|
345
356
|
}
|
|
346
357
|
this.log(`\nCreating ${this.appType === 'theme' ? 'theme' : 'app'} ${style.appConfigValue(name)} in ${appPath}/swell.json`);
|
|
347
358
|
this.log(`\n${JSON.stringify(swellConfigJson, null, 2)}\n`);
|
|
359
|
+
if (template) {
|
|
360
|
+
this.log(`Frontend: ${template.name} (${template.hosting})`);
|
|
361
|
+
if (template.provisionsStorefront) {
|
|
362
|
+
this.log('A development app and storefront will also be created in your test store.');
|
|
363
|
+
}
|
|
364
|
+
}
|
|
348
365
|
const confirmCreateApp = confirmYes
|
|
349
366
|
? true
|
|
350
367
|
: await confirm({
|
|
@@ -356,7 +373,13 @@ export default class CreateApp extends CreateAppCommand {
|
|
|
356
373
|
const config = newConfig(nestedPath ? inputId || appId : '');
|
|
357
374
|
config.clear();
|
|
358
375
|
config.set(swellConfigJson);
|
|
359
|
-
return {
|
|
376
|
+
return {
|
|
377
|
+
appId,
|
|
378
|
+
config,
|
|
379
|
+
installedStorefrontApp,
|
|
380
|
+
swellConfigJson,
|
|
381
|
+
template,
|
|
382
|
+
};
|
|
360
383
|
}
|
|
361
384
|
async run() {
|
|
362
385
|
const { args, flags } = await this.parse(CreateApp);
|
|
@@ -375,16 +398,17 @@ export default class CreateApp extends CreateAppCommand {
|
|
|
375
398
|
inputFrontend: frontend,
|
|
376
399
|
inputIntegrationType: flags['integration-type'],
|
|
377
400
|
inputIntegrationId: flags['integration-id'],
|
|
401
|
+
scaffoldFrontend: true,
|
|
378
402
|
});
|
|
379
403
|
if (!createParams) {
|
|
380
404
|
return;
|
|
381
405
|
}
|
|
382
|
-
const { appId, config, installedStorefrontApp, swellConfigJson } = createParams;
|
|
406
|
+
const { appId, config, installedStorefrontApp, swellConfigJson, template } = createParams;
|
|
383
407
|
!confirmYes && this.log();
|
|
384
408
|
const spinner = ora();
|
|
385
409
|
spinner.start('Creating app...');
|
|
386
410
|
if (pkg !== 'none') {
|
|
387
|
-
await
|
|
411
|
+
await setupPackage(appId, config, pkg);
|
|
388
412
|
}
|
|
389
413
|
await this.createAppConfigFolders(config);
|
|
390
414
|
spinner.succeed(`${style.appConfigValue(config.get('name'))} app created.\n`);
|
|
@@ -394,15 +418,11 @@ export default class CreateApp extends CreateAppCommand {
|
|
|
394
418
|
if (installedStorefrontApp) {
|
|
395
419
|
createdTheme = await this.createThemeApp(config, flags, installedStorefrontApp);
|
|
396
420
|
}
|
|
397
|
-
else if (
|
|
398
|
-
|
|
399
|
-
provisionResources:
|
|
421
|
+
else if (template) {
|
|
422
|
+
frontendResult = await this.createFrontend(config, template, pkg, {
|
|
423
|
+
provisionResources: config.get('type') === 'storefront',
|
|
400
424
|
});
|
|
401
|
-
|
|
402
|
-
resources = storefrontResult.resources;
|
|
403
|
-
}
|
|
404
|
-
else {
|
|
405
|
-
frontendResult = await this.createFrontendApp(config, flags);
|
|
425
|
+
resources = frontendResult.resources;
|
|
406
426
|
}
|
|
407
427
|
if (json) {
|
|
408
428
|
super.log(JSON.stringify(buildCreateAppResult({
|
|
@@ -410,7 +430,10 @@ export default class CreateApp extends CreateAppCommand {
|
|
|
410
430
|
resources,
|
|
411
431
|
name: swellConfigJson.name,
|
|
412
432
|
type: swellConfigJson.type,
|
|
413
|
-
|
|
433
|
+
// Echo the requested value, including the hidden alias.
|
|
434
|
+
frontend: frontendResult
|
|
435
|
+
? frontend || frontendResult.template
|
|
436
|
+
: null,
|
|
414
437
|
path: path.dirname(config.path),
|
|
415
438
|
})));
|
|
416
439
|
return;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { HelpMeta } from '../../help/types.js';
|
|
1
2
|
import { CreateAppCommand } from '../../create-app-command.js';
|
|
2
3
|
export default class CreateFrontend extends CreateAppCommand {
|
|
3
4
|
static description: string;
|
|
@@ -5,8 +6,9 @@ export default class CreateFrontend extends CreateAppCommand {
|
|
|
5
6
|
command: string;
|
|
6
7
|
description: string;
|
|
7
8
|
}[];
|
|
9
|
+
static helpMeta: HelpMeta;
|
|
8
10
|
static flags: {
|
|
9
|
-
pkg: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
|
|
11
|
+
pkg: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
|
|
10
12
|
};
|
|
11
13
|
run(): Promise<void>;
|
|
12
14
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { Flags } from '@oclif/core';
|
|
2
|
-
import
|
|
2
|
+
import path from 'node:path';
|
|
3
3
|
import { CreateAppCommand } from '../../create-app-command.js';
|
|
4
4
|
import swellConfig from '../../lib/app-config.js';
|
|
5
|
+
import { detectPackageManager } from '../../lib/package-manager.js';
|
|
5
6
|
export default class CreateFrontend extends CreateAppCommand {
|
|
6
7
|
static description = 'Initialize a frontend app framework and folder structure.';
|
|
7
8
|
static examples = [
|
|
@@ -10,15 +11,22 @@ export default class CreateFrontend extends CreateAppCommand {
|
|
|
10
11
|
description: 'Create frontend app following command prompts.',
|
|
11
12
|
},
|
|
12
13
|
{
|
|
13
|
-
command: 'swell create frontend -y',
|
|
14
|
-
description: '
|
|
14
|
+
command: 'swell create frontend --frontend swell-react -y',
|
|
15
|
+
description: 'Add a React frontend hosted by Swell.',
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
command: 'swell create frontend --frontend astro -y',
|
|
19
|
+
description: 'Add a self-hosted Astro frontend.',
|
|
15
20
|
},
|
|
16
21
|
];
|
|
22
|
+
static helpMeta = {
|
|
23
|
+
frontendTemplates: 'required',
|
|
24
|
+
usageDirect: '--frontend <template> [...] -y',
|
|
25
|
+
};
|
|
17
26
|
static flags = {
|
|
18
27
|
pkg: Flags.string({
|
|
19
28
|
char: 'p',
|
|
20
|
-
|
|
21
|
-
description: 'Package manager: npm | yarn | pnpm | bun | none',
|
|
29
|
+
description: 'Package manager: npm | yarn | pnpm | bun | none (default: the workspace lockfile, else npm)',
|
|
22
30
|
options: ['npm', 'yarn', 'pnpm', 'bun', 'none'],
|
|
23
31
|
}),
|
|
24
32
|
};
|
|
@@ -26,15 +34,15 @@ export default class CreateFrontend extends CreateAppCommand {
|
|
|
26
34
|
const { flags } = await this.parse(CreateFrontend);
|
|
27
35
|
const newSwellConfig = await swellConfig();
|
|
28
36
|
const appType = newSwellConfig?.get('type');
|
|
29
|
-
const spinner = ora();
|
|
30
37
|
if (appType === 'theme') {
|
|
31
|
-
|
|
32
|
-
|
|
38
|
+
this.error("You can't create app frontend in a theme.");
|
|
39
|
+
}
|
|
40
|
+
const template = await this.selectFrontend(appType, flags, true);
|
|
41
|
+
if (template) {
|
|
42
|
+
// Match the manager that app dev and push will detect for this workspace.
|
|
43
|
+
const pkg = flags.pkg ??
|
|
44
|
+
(await detectPackageManager(path.dirname(newSwellConfig.path)));
|
|
45
|
+
await this.scaffoldFrontend(newSwellConfig, template, pkg, true);
|
|
33
46
|
}
|
|
34
|
-
await (appType === 'storefront'
|
|
35
|
-
? this.createStorefrontApp(newSwellConfig, flags, {
|
|
36
|
-
directCreate: true,
|
|
37
|
-
})
|
|
38
|
-
: this.createFrontendApp(newSwellConfig, flags, true));
|
|
39
47
|
}
|
|
40
48
|
}
|
|
@@ -1,71 +1,30 @@
|
|
|
1
|
+
import { type FrontendTemplate } from './lib/apps/frontend-templates.js';
|
|
1
2
|
import { type StorefrontResourceResult } from './lib/apps/storefront-resources.js';
|
|
2
|
-
import { PackageManager } from './lib/package-manager.js';
|
|
3
3
|
import { SwellCommand } from './swell-command.js';
|
|
4
4
|
export interface FrontendCreationResult {
|
|
5
5
|
created: boolean;
|
|
6
|
-
framework: string | null;
|
|
7
|
-
provisionsStorefront: boolean;
|
|
8
|
-
}
|
|
9
|
-
export interface StorefrontCreationResult extends FrontendCreationResult {
|
|
10
6
|
resources?: StorefrontResourceResult;
|
|
7
|
+
/** Resolved template slug; the JSON result echoes the requested value. */
|
|
8
|
+
template: string;
|
|
11
9
|
}
|
|
12
|
-
export interface
|
|
13
|
-
directCreate?: boolean;
|
|
10
|
+
export interface FrontendCreationOptions {
|
|
14
11
|
provisionResources?: boolean;
|
|
15
12
|
}
|
|
16
13
|
export declare abstract class CreateAppCommand extends SwellCommand {
|
|
17
14
|
protected commandExample: string;
|
|
18
15
|
static baseFlags: {
|
|
19
16
|
frontend: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
|
|
20
|
-
'storefront-app': import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
|
|
21
|
-
'integration-type': import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
|
|
22
|
-
'integration-id': import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
|
|
23
17
|
yes: import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
|
|
24
18
|
};
|
|
25
19
|
private devApi;
|
|
26
20
|
constructor(argv: string[], config: any);
|
|
27
|
-
addAllowedHostsToAngular(configPath: string): Promise<void>;
|
|
28
|
-
addAllowedHostsToAstro(configPath: string): Promise<void>;
|
|
29
|
-
addAllowedHostsToNuxt(configPath: string): Promise<void>;
|
|
30
|
-
/**
|
|
31
|
-
* For frameworks whose dev server is Vite directly. Astro/Nuxt nest the
|
|
32
|
-
* vite block inside their own defineConfig — see addViteAllowedHosts.
|
|
33
|
-
*/
|
|
34
|
-
addAllowedHostsToVite(configPath: string): Promise<void>;
|
|
35
|
-
/**
|
|
36
|
-
* Add vite allowedHosts configuration to a framework config file.
|
|
37
|
-
* Supports config files that use a defineX({}) pattern (Astro, Nuxt, etc.)
|
|
38
|
-
*/
|
|
39
|
-
private addViteAllowedHosts;
|
|
40
21
|
createAppConfigFolders(swellConfig: any): Promise<void>;
|
|
41
|
-
|
|
42
|
-
|
|
22
|
+
selectFrontend(appType: string, flags: any, direct?: boolean): Promise<FrontendTemplate | undefined>;
|
|
23
|
+
/** Scaffold the local frontend workspace without touching remote resources. */
|
|
24
|
+
scaffoldFrontend(swellConfig: any, template: FrontendTemplate, packageManager?: string, directCreate?: boolean): Promise<FrontendCreationResult>;
|
|
25
|
+
/** Scaffold the frontend and, when asked, its remote storefront resources. */
|
|
26
|
+
createFrontend(swellConfig: any, template: FrontendTemplate, packageManager: string | undefined, options?: FrontendCreationOptions): Promise<FrontendCreationResult>;
|
|
43
27
|
createThemeApp(config: any, flags: any, installedStorefrontApp: any): Promise<boolean>;
|
|
44
|
-
doesPackageManagerExist(packageManager: string): Promise<boolean>;
|
|
45
|
-
execWithStdio(cwd: string, command: string, onOutput?: (string: string) => any | false): Promise<void>;
|
|
46
|
-
findPackageManager(pkg?: string): Promise<string | undefined>;
|
|
47
28
|
getInstalledStorefrontApps(): Promise<any[] | boolean>;
|
|
48
|
-
|
|
49
|
-
installCommand: string;
|
|
50
|
-
appTypes?: string[] | undefined;
|
|
51
|
-
buildCommand?: string | undefined;
|
|
52
|
-
devCommand: string;
|
|
53
|
-
displayOrder?: number | undefined;
|
|
54
|
-
mainPackage: string;
|
|
55
|
-
name: string;
|
|
56
|
-
provisionsStorefront?: boolean | undefined;
|
|
57
|
-
slug: string;
|
|
58
|
-
};
|
|
59
|
-
setupPackage(name: string, config: any, pkg: PackageManager): Promise<void>;
|
|
60
|
-
tryPackageSetup(name: string, config: any, pkg: PackageManager): Promise<void>;
|
|
61
|
-
/**
|
|
62
|
-
* Fix wrangler.jsonc `main` field when it points to a build artifact that
|
|
63
|
-
* doesn't exist at dev time. The @cloudflare/vite-plugin validates this path
|
|
64
|
-
* at startup and crashes during `astro dev` if it can't resolve it.
|
|
65
|
-
*
|
|
66
|
-
* Only replaces the exact legacy value that C3 currently scaffolds.
|
|
67
|
-
* Once C3 updates its template, this becomes a no-op.
|
|
68
|
-
*/
|
|
69
|
-
private fixAstroWranglerEntrypoint;
|
|
70
|
-
private addFrontendAllowedHosts;
|
|
29
|
+
resolveFrontendTemplate(value: string): FrontendTemplate;
|
|
71
30
|
}
|