@swell/cli 2.0.20 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/api/delete.d.ts +15 -0
- package/dist/commands/api/delete.js +26 -0
- package/dist/commands/api/get.d.ts +15 -0
- package/dist/commands/api/get.js +31 -0
- package/dist/commands/api/index.d.ts +7 -0
- package/dist/commands/api/index.js +16 -0
- package/dist/commands/api/post.d.ts +16 -0
- package/dist/commands/api/post.js +33 -0
- package/dist/commands/api/put.d.ts +16 -0
- package/dist/commands/api/put.js +33 -0
- package/dist/commands/app/dev.d.ts +18 -4
- package/dist/commands/app/dev.js +383 -5
- package/dist/commands/app/frontend/dev.d.ts +6 -5
- package/dist/commands/app/frontend/dev.js +127 -58
- package/dist/commands/app/install.js +3 -3
- package/dist/commands/app/pull.d.ts +1 -1
- package/dist/commands/app/pull.js +6 -6
- package/dist/commands/app/push.d.ts +3 -3
- package/dist/commands/app/push.js +8 -6
- package/dist/commands/app/release.d.ts +1 -1
- package/dist/commands/app/release.js +1 -1
- package/dist/commands/app/version.d.ts +1 -1
- package/dist/commands/app/version.js +5 -3
- package/dist/commands/create/app.d.ts +10 -2
- package/dist/commands/create/app.js +226 -57
- package/dist/commands/create/content.d.ts +4 -0
- package/dist/commands/create/content.js +83 -15
- package/dist/commands/create/function.js +27 -5
- package/dist/commands/create/model.d.ts +4 -0
- package/dist/commands/create/model.js +120 -25
- package/dist/commands/inspect/content.d.ts +25 -0
- package/dist/commands/inspect/content.js +159 -0
- package/dist/commands/inspect/index.d.ts +7 -0
- package/dist/commands/inspect/index.js +16 -0
- package/dist/commands/inspect/models.d.ts +27 -0
- package/dist/commands/inspect/models.js +197 -0
- package/dist/commands/schema.d.ts +27 -0
- package/dist/commands/schema.js +231 -0
- package/dist/commands/theme/dev.d.ts +1 -1
- package/dist/commands/theme/dev.js +2 -2
- package/dist/commands/theme/init.d.ts +1 -1
- package/dist/commands/theme/init.js +1 -1
- package/dist/commands/theme/pull.d.ts +2 -2
- package/dist/commands/theme/pull.js +2 -2
- package/dist/commands/theme/push.d.ts +1 -1
- package/dist/commands/theme/push.js +1 -1
- package/dist/create-app-command.d.ts +14 -0
- package/dist/create-app-command.js +198 -15
- package/dist/create-collection-command.js +4 -1
- package/dist/create-config-command.js +4 -0
- package/dist/env/local.d.ts +2 -0
- package/dist/env/local.js +2 -0
- package/dist/env/production.d.ts +2 -0
- package/dist/env/production.js +2 -0
- package/dist/env/review.d.ts +2 -0
- package/dist/env/review.js +2 -0
- package/dist/env/staging.d.ts +2 -0
- package/dist/env/staging.js +2 -0
- package/dist/lib/api.d.ts +16 -5
- package/dist/lib/api.js +67 -25
- package/dist/lib/app-config.js +1 -0
- package/dist/lib/apps/app-config.d.ts +49 -0
- package/dist/lib/apps/app-config.js +21 -14
- package/dist/lib/apps/index.d.ts +5 -4
- package/dist/lib/apps/index.js +109 -21
- package/dist/lib/bundle.js +2 -1
- package/dist/lib/config.d.ts +1 -1
- package/dist/lib/constants.d.ts +2 -4
- package/dist/lib/constants.js +6 -4
- package/dist/lib/create/content.d.ts +5 -1
- package/dist/lib/create/content.js +8 -1
- package/dist/lib/create/function.d.ts +2 -1
- package/dist/lib/create/function.js +7 -3
- package/dist/lib/create/model.d.ts +1 -0
- package/dist/lib/create/schemas.d.ts +6 -0
- package/dist/lib/create/schemas.js +6 -0
- package/dist/lib/proxy.d.ts +1 -0
- package/dist/lib/proxy.js +29 -1
- package/dist/lib/sessions.js +4 -3
- package/dist/lib/style.js +0 -1
- package/dist/lib/swell-function-wrapper.d.ts +23 -4
- package/dist/lib/swell-function-wrapper.js +11 -4
- package/dist/lib/theme-sync.d.ts +6 -6
- package/dist/lib/theme-sync.js +49 -39
- package/dist/push-app-command.d.ts +12 -8
- package/dist/push-app-command.js +108 -29
- package/dist/remote-app-command.d.ts +1 -1
- package/dist/remote-app-command.js +4 -2
- package/dist/swell-api-command.d.ts +13 -0
- package/dist/swell-api-command.js +75 -0
- package/dist/swell-command.d.ts +1 -1
- package/dist/swell-command.js +9 -9
- package/package.json +10 -1
- package/oclif.manifest.json +0 -1895
package/dist/lib/theme-sync.js
CHANGED
|
@@ -1,29 +1,37 @@
|
|
|
1
|
-
import http from 'http';
|
|
2
1
|
import getPort, { portNumbers } from 'get-port';
|
|
2
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
3
|
+
// @ts-ignore - http-proxy has incomplete TypeScript definitions
|
|
4
|
+
import httpProxy from 'http-proxy';
|
|
5
|
+
import http from 'node:http';
|
|
3
6
|
import util from 'node:util';
|
|
4
7
|
import zlib from 'node:zlib';
|
|
5
|
-
// @ts-
|
|
6
|
-
|
|
7
|
-
//
|
|
8
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
9
|
+
// @ts-ignore - ws library uses named exports differently
|
|
10
|
+
// eslint-disable-next-line import/no-named-as-default
|
|
8
11
|
import WebSocket, { WebSocketServer } from 'ws';
|
|
9
12
|
const gunzipAsync = util.promisify(zlib.gunzip);
|
|
10
13
|
const inflateAsync = util.promisify(zlib.inflate);
|
|
11
14
|
const brotliDecompressAsync = util.promisify(zlib.brotliDecompress);
|
|
12
15
|
const FALLBACK_PORT = 3000;
|
|
13
16
|
export class ThemeSync {
|
|
14
|
-
|
|
17
|
+
options;
|
|
15
18
|
proxy;
|
|
19
|
+
server;
|
|
16
20
|
wss;
|
|
17
|
-
options;
|
|
18
|
-
async selectPort() {
|
|
19
|
-
return (await getPort({ port: portNumbers(3000, 3100) })) || FALLBACK_PORT;
|
|
20
|
-
}
|
|
21
21
|
constructor(options) {
|
|
22
22
|
this.options = options;
|
|
23
23
|
this.server = http.createServer();
|
|
24
24
|
this.proxy = httpProxy.createProxyServer({});
|
|
25
25
|
this.wss = new WebSocketServer({ server: this.server });
|
|
26
26
|
}
|
|
27
|
+
reload() {
|
|
28
|
+
// Get all connected WebSocket clients
|
|
29
|
+
const clients = [...this.wss.clients].filter((client) => client.readyState === WebSocket.OPEN);
|
|
30
|
+
// Notify clients to reload
|
|
31
|
+
for (const client of clients) {
|
|
32
|
+
client.send('reload');
|
|
33
|
+
}
|
|
34
|
+
}
|
|
27
35
|
async start() {
|
|
28
36
|
// Select port if not specified
|
|
29
37
|
if (!this.options.port) {
|
|
@@ -85,38 +93,52 @@ export class ThemeSync {
|
|
|
85
93
|
});
|
|
86
94
|
});
|
|
87
95
|
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
96
|
+
decodeRequestBody(body, encoding) {
|
|
97
|
+
switch (encoding) {
|
|
98
|
+
case 'gzip': {
|
|
99
|
+
return gunzipAsync(body);
|
|
100
|
+
}
|
|
101
|
+
case 'deflate': {
|
|
102
|
+
return inflateAsync(body);
|
|
103
|
+
}
|
|
104
|
+
case 'br': {
|
|
105
|
+
return brotliDecompressAsync(body);
|
|
106
|
+
}
|
|
107
|
+
default: {
|
|
108
|
+
return Promise.reject(new Error(`Unsupported content encoding: ${encoding}`));
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
// Inject WebSocket client script into proxied HTML
|
|
113
|
+
injectScript(html, host) {
|
|
114
|
+
const script = `
|
|
115
|
+
<script>
|
|
116
|
+
const ws = new WebSocket('ws://${host}/ws');
|
|
117
|
+
ws.onmessage = () => {
|
|
118
|
+
window.location.reload();
|
|
119
|
+
};
|
|
120
|
+
</script>
|
|
121
|
+
`;
|
|
122
|
+
return html.replace('</body>', `${script}</body>`);
|
|
95
123
|
}
|
|
96
124
|
async parseRequestBody(proxyRes, body) {
|
|
97
125
|
// May contain multiple encodings
|
|
98
126
|
const contentEncoding = String(proxyRes.headers['content-encoding'] ?? '')
|
|
99
127
|
.split(',')
|
|
100
128
|
.reverse();
|
|
129
|
+
// Sequential decoding of content-encoding layers is required
|
|
130
|
+
/* eslint-disable no-await-in-loop */
|
|
101
131
|
for (const value of contentEncoding) {
|
|
102
132
|
const encoding = value.trim().toLowerCase();
|
|
103
133
|
if (encoding) {
|
|
104
134
|
body = await this.decodeRequestBody(body, encoding);
|
|
105
135
|
}
|
|
106
136
|
}
|
|
137
|
+
/* eslint-enable no-await-in-loop */
|
|
107
138
|
return body.toString('utf8');
|
|
108
139
|
}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
case 'gzip':
|
|
112
|
-
return gunzipAsync(body);
|
|
113
|
-
case 'deflate':
|
|
114
|
-
return inflateAsync(body);
|
|
115
|
-
case 'br':
|
|
116
|
-
return brotliDecompressAsync(body);
|
|
117
|
-
default:
|
|
118
|
-
return Promise.reject(new Error(`Unsupported content encoding: ${encoding}`));
|
|
119
|
-
}
|
|
140
|
+
async selectPort() {
|
|
141
|
+
return (await getPort({ port: portNumbers(3000, 3100) })) || FALLBACK_PORT;
|
|
120
142
|
}
|
|
121
143
|
shouldInjectScript(proxyRes, html) {
|
|
122
144
|
// Check if this is an HTML response
|
|
@@ -129,16 +151,4 @@ export class ThemeSync {
|
|
|
129
151
|
}
|
|
130
152
|
return true;
|
|
131
153
|
}
|
|
132
|
-
// Inject WebSocket client script into proxied HTML
|
|
133
|
-
injectScript(html, host) {
|
|
134
|
-
const script = `
|
|
135
|
-
<script>
|
|
136
|
-
const ws = new WebSocket('ws://${host}/ws');
|
|
137
|
-
ws.onmessage = () => {
|
|
138
|
-
window.location.reload();
|
|
139
|
-
};
|
|
140
|
-
</script>
|
|
141
|
-
`;
|
|
142
|
-
return html.replace('</body>', `${script}</body>`);
|
|
143
|
-
}
|
|
144
154
|
}
|
|
@@ -12,20 +12,22 @@ interface WatchingChange {
|
|
|
12
12
|
export declare abstract class PushAppCommand extends RemoteAppCommand {
|
|
13
13
|
frontendPath: string;
|
|
14
14
|
logWatchChanges: boolean;
|
|
15
|
-
onWatchChange?: (appConfig?: AppConfig, result?: any) => void;
|
|
15
|
+
onWatchChange?: (appConfig?: AppConfig, action?: string, result?: any) => void;
|
|
16
16
|
watchingChangeQueue: Map<string, WatchingChange>;
|
|
17
17
|
watchingFiles: Set<string>;
|
|
18
18
|
watchingIgnoreFilter: GlobbyFilterFunction | null;
|
|
19
19
|
watchingTimer: NodeJS.Timeout | null;
|
|
20
20
|
watchListener: (eventType: fs.WatchEventType, filename: null | string) => Promise<void>;
|
|
21
|
-
|
|
21
|
+
protected showFrontendMigrationError(): never;
|
|
22
|
+
buildFrontend(projectType?: FrontendProjectType): Promise<void>;
|
|
22
23
|
chooseAppToPull(query?: any): Promise<App>;
|
|
23
24
|
createAppStorefront(hasOtherStorefronts?: boolean): Promise<any>;
|
|
24
25
|
deployAppFrontend(force?: boolean, log?: boolean): Promise<void>;
|
|
25
26
|
deployFrontend(projectType: FrontendProjectType): Promise<string>;
|
|
26
27
|
ensureAppExists(file?: string, shouldCreate?: boolean): Promise<boolean>;
|
|
27
28
|
ensureLoggedIn(): Promise<true | undefined>;
|
|
28
|
-
exec(command: string, onOutput?: (string: string) => any | false): Promise<void>;
|
|
29
|
+
exec(command: string, cwd?: string, onOutput?: (string: string) => any | false): Promise<void>;
|
|
30
|
+
execFrontend(command: string, onOutput?: (string: string) => any | false): Promise<void>;
|
|
29
31
|
getAllAppStorefronts(params?: {
|
|
30
32
|
type?: string;
|
|
31
33
|
}): Promise<any>;
|
|
@@ -49,19 +51,19 @@ export declare abstract class PushAppCommand extends RemoteAppCommand {
|
|
|
49
51
|
appId?: string;
|
|
50
52
|
targetPath?: string;
|
|
51
53
|
}, flags?: {
|
|
54
|
+
env?: string;
|
|
52
55
|
'storefront-id'?: string;
|
|
53
56
|
'storefront-select'?: boolean;
|
|
54
|
-
env?: string;
|
|
55
57
|
}): Promise<any>;
|
|
56
58
|
getStorefrontToPush(flags?: {
|
|
59
|
+
env?: string;
|
|
57
60
|
'storefront-id'?: string;
|
|
58
61
|
'storefront-select'?: boolean;
|
|
59
|
-
env?: string;
|
|
60
62
|
}): Promise<any>;
|
|
61
63
|
setStorefrontEnv(flags: {
|
|
64
|
+
env?: string;
|
|
62
65
|
'storefront-id'?: string;
|
|
63
66
|
'storefront-select'?: boolean;
|
|
64
|
-
env?: string;
|
|
65
67
|
}, defaultStorefront?: any): Promise<void>;
|
|
66
68
|
handleWatchFileChange(configFile: string, configType: ConfigType, event: WatchFileEvent): Promise<void>;
|
|
67
69
|
logAppFrontendUrl(): void;
|
|
@@ -76,13 +78,15 @@ export declare abstract class PushAppCommand extends RemoteAppCommand {
|
|
|
76
78
|
runWatchChangeQueue(): Promise<void>;
|
|
77
79
|
saveCurrentStorefront(): void;
|
|
78
80
|
setWatchingFiles(): Promise<void>;
|
|
81
|
+
startProxyServer(port?: number): Promise<number>;
|
|
79
82
|
updateFrontendDeployment(currentStore: string, projectType: FrontendProjectType, deploymentUrl: string, deploymentHash: string): Promise<void>;
|
|
83
|
+
updateLocalProxy(proxyUrl: string, storefrontId?: string): Promise<void>;
|
|
80
84
|
watchForChanges({ logChanges, onChange, syncAll, }?: {
|
|
81
85
|
logChanges?: boolean;
|
|
82
|
-
onChange?: () => void;
|
|
86
|
+
onChange?: (appConfig?: AppConfig, result?: any) => void;
|
|
83
87
|
syncAll?: boolean;
|
|
84
88
|
}): Promise<void>;
|
|
85
|
-
wranglerDeployFrontend(
|
|
89
|
+
wranglerDeployFrontend(_projectType: FrontendProjectType): Promise<string>;
|
|
86
90
|
private confirmRemoveInstalledApp;
|
|
87
91
|
}
|
|
88
92
|
export {};
|
package/dist/push-app-command.js
CHANGED
|
@@ -1,19 +1,22 @@
|
|
|
1
1
|
import { confirm, input, select } from '@inquirer/prompts';
|
|
2
2
|
import { $ } from 'execa';
|
|
3
|
+
import getPort, { portNumbers } from 'get-port';
|
|
3
4
|
import * as fs from 'node:fs';
|
|
4
5
|
import * as path from 'node:path';
|
|
5
6
|
import Stream from 'node:stream';
|
|
6
7
|
import ora from 'ora';
|
|
7
8
|
import { default as swellConfig } from './lib/app-config.js';
|
|
8
|
-
import { ConfigType,
|
|
9
|
-
import { selectEnvironmentId } from './lib/stores.js';
|
|
9
|
+
import { ConfigType, getFrontendProjectValidValues, allConfigFilesInDir, appConfigFromFile, filePathExists, filePathExistsAsync, findAppConfig, getAppSlugId, getConfigTypeFromPath, getConfigTypeKeyFromValue, getFrontendProjectType, globAllFilesByPath, hashString, isPathDirectory, } from './lib/apps/index.js';
|
|
10
10
|
import { getGlobIgnorePathsChecker } from './lib/apps/paths.js';
|
|
11
11
|
import { default as localConfig } from './lib/config.js';
|
|
12
12
|
import { toAppId } from './lib/create/index.js';
|
|
13
|
+
import { getProxyUrl } from './lib/proxy.js';
|
|
14
|
+
import { selectEnvironmentId } from './lib/stores.js';
|
|
13
15
|
import style from './lib/style.js';
|
|
14
16
|
import { RemoteAppCommand } from './remote-app-command.js';
|
|
15
17
|
const PUSH_CONCURRENCY = 3;
|
|
16
18
|
const WATCH_WINDOW_MS = 100;
|
|
19
|
+
const DEV_SERVER_FALLBACK_PORT = 3000;
|
|
17
20
|
export class PushAppCommand extends RemoteAppCommand {
|
|
18
21
|
frontendPath = '';
|
|
19
22
|
logWatchChanges = true;
|
|
@@ -45,6 +48,7 @@ export class PushAppCommand extends RemoteAppCommand {
|
|
|
45
48
|
if (!configType) {
|
|
46
49
|
return;
|
|
47
50
|
}
|
|
51
|
+
await this.setWatchingFiles();
|
|
48
52
|
const isWatching = this.watchingFiles.has(configFile);
|
|
49
53
|
let watchFileEvent;
|
|
50
54
|
// per node docs:
|
|
@@ -75,6 +79,7 @@ export class PushAppCommand extends RemoteAppCommand {
|
|
|
75
79
|
await this.handleWatchFileChange(configFile, configType, watchFileEvent);
|
|
76
80
|
}
|
|
77
81
|
catch (error) {
|
|
82
|
+
console.error(error);
|
|
78
83
|
// we don't want to break the watcher if an error is thrown while
|
|
79
84
|
// handling a file change
|
|
80
85
|
// for tests though, we want to break the watcher
|
|
@@ -87,10 +92,29 @@ export class PushAppCommand extends RemoteAppCommand {
|
|
|
87
92
|
}
|
|
88
93
|
}
|
|
89
94
|
};
|
|
95
|
+
showFrontendMigrationError() {
|
|
96
|
+
return this.error(style.funcWarn(`⚠️ Your frontend directory exists but appears to use an older structure.\n` +
|
|
97
|
+
`Swell CLI v2.1.0+ requires a Workers-based frontend with package.json.\n\n` +
|
|
98
|
+
`${style.basicHighlight('Migration options:')}\n\n` +
|
|
99
|
+
`1. ${style.basicHighlight('Migrate to Workers')} (recommended)\n` +
|
|
100
|
+
` If you are using a Swell official application (e.g. Proxima), update to the latest version.\n` +
|
|
101
|
+
` Otherwise, initialize your frontend as a Workers project.\n` +
|
|
102
|
+
` Follow Cloudflare's official guide: ${style.link('https://developers.cloudflare.com/workers/static-assets/migration-guides/migrate-from-pages/')}\n\n` +
|
|
103
|
+
`2. ${style.basicHighlight('Use older CLI version')}\n` +
|
|
104
|
+
` Continue with Pages deployment:\n` +
|
|
105
|
+
` ${style.dim('$ npm install -g @swell/cli@2.0.20')}\n`));
|
|
106
|
+
}
|
|
90
107
|
async buildFrontend(projectType) {
|
|
108
|
+
if (!projectType) {
|
|
109
|
+
this.showFrontendMigrationError();
|
|
110
|
+
return; // unreachable but helps TypeScript narrow the type
|
|
111
|
+
}
|
|
112
|
+
if (!projectType.buildCommand) {
|
|
113
|
+
// No build command needed for this framework
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
91
116
|
this.log(`Building ${projectType.name} frontend...\n`);
|
|
92
|
-
|
|
93
|
-
await this.exec(projectType.buildCommand);
|
|
117
|
+
await this.execFrontend(projectType.buildCommand);
|
|
94
118
|
}
|
|
95
119
|
async chooseAppToPull(query) {
|
|
96
120
|
const typeLabelPlural = this.appType === 'theme' ? 'themes' : 'apps';
|
|
@@ -241,7 +265,7 @@ export class PushAppCommand extends RemoteAppCommand {
|
|
|
241
265
|
null);
|
|
242
266
|
const isTheme = app?.type === 'theme';
|
|
243
267
|
if (app && !isTheme && this.appType === 'theme') {
|
|
244
|
-
this.error(`App ${style.appConfigValue(app?.name)} is not a theme.`);
|
|
268
|
+
this.error(`App ${style.appConfigValue(app?.name)} is not a theme. Use 'swell app' commands instead.`);
|
|
245
269
|
}
|
|
246
270
|
// Confirm development app unless theme without app syncing
|
|
247
271
|
if (!isTheme || this.themeSyncApp) {
|
|
@@ -283,9 +307,9 @@ export class PushAppCommand extends RemoteAppCommand {
|
|
|
283
307
|
return true;
|
|
284
308
|
}
|
|
285
309
|
}
|
|
286
|
-
async exec(command, onOutput) {
|
|
310
|
+
async exec(command, cwd, onOutput) {
|
|
287
311
|
const $$ = $({
|
|
288
|
-
cwd: this.
|
|
312
|
+
cwd: cwd || this.appPath,
|
|
289
313
|
shell: true,
|
|
290
314
|
stderr: onOutput ? 'pipe' : 'inherit',
|
|
291
315
|
stdin: 'inherit',
|
|
@@ -307,6 +331,9 @@ export class PushAppCommand extends RemoteAppCommand {
|
|
|
307
331
|
await $$ `${command}`;
|
|
308
332
|
}
|
|
309
333
|
}
|
|
334
|
+
async execFrontend(command, onOutput) {
|
|
335
|
+
return this.exec(command, this.frontendPath, onOutput);
|
|
336
|
+
}
|
|
310
337
|
async getAllAppStorefronts(params) {
|
|
311
338
|
const query = Object.entries(params || {})
|
|
312
339
|
.map(([key, value]) => `${key}=${value}`)
|
|
@@ -392,15 +419,23 @@ export class PushAppCommand extends RemoteAppCommand {
|
|
|
392
419
|
}
|
|
393
420
|
getFrontendProjectType(required = true) {
|
|
394
421
|
this.frontendPath = path.join(this.appPath, 'frontend');
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
return null;
|
|
399
|
-
}
|
|
422
|
+
const frontendExists = filePathExists(this.frontendPath);
|
|
423
|
+
if (!required && !frontendExists) {
|
|
424
|
+
return null;
|
|
400
425
|
}
|
|
401
426
|
const projectType = getFrontendProjectType(this.appPath);
|
|
402
427
|
if (!projectType) {
|
|
403
|
-
|
|
428
|
+
// If frontend not required, just return null (don't error)
|
|
429
|
+
if (!required) {
|
|
430
|
+
return null;
|
|
431
|
+
}
|
|
432
|
+
// Frontend IS required but not found
|
|
433
|
+
// Check if frontend directory exists but lacks package.json (old structure)
|
|
434
|
+
if (frontendExists) {
|
|
435
|
+
this.showFrontendMigrationError();
|
|
436
|
+
}
|
|
437
|
+
// Frontend directory doesn't exist at all
|
|
438
|
+
this.error(`No valid frontend app found in ${this.frontendPath}. Supported frameworks include: ${getFrontendProjectValidValues(false)}.`);
|
|
404
439
|
}
|
|
405
440
|
return projectType;
|
|
406
441
|
}
|
|
@@ -526,8 +561,8 @@ export class PushAppCommand extends RemoteAppCommand {
|
|
|
526
561
|
await this.api.setStoreEnv(currentStore, defaultStorefront?.env);
|
|
527
562
|
}
|
|
528
563
|
}
|
|
529
|
-
else if (flags
|
|
530
|
-
await this.api.setStoreEnv(currentStore, flags
|
|
564
|
+
else if (flags.env) {
|
|
565
|
+
await this.api.setStoreEnv(currentStore, flags.env);
|
|
531
566
|
}
|
|
532
567
|
else {
|
|
533
568
|
const hasTestEnv = await this.api.isTestEnvEnabled();
|
|
@@ -668,15 +703,24 @@ export class PushAppCommand extends RemoteAppCommand {
|
|
|
668
703
|
}
|
|
669
704
|
const queue = [...this.watchingChangeQueue.values()];
|
|
670
705
|
this.watchingChangeQueue.clear();
|
|
706
|
+
// Process queue in batches with concurrency control
|
|
707
|
+
/* eslint-disable no-await-in-loop */
|
|
671
708
|
while (queue.length > 0) {
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
709
|
+
try {
|
|
710
|
+
await Promise.all(queue
|
|
711
|
+
.splice(0, PUSH_CONCURRENCY)
|
|
712
|
+
.map(async ({ action, appConfig }) => {
|
|
713
|
+
const result = await (action === 'remove'
|
|
714
|
+
? this.removeRemoteFile(appConfig, this.logWatchChanges)
|
|
715
|
+
: this.pushRemoteFile(appConfig, this.logWatchChanges));
|
|
716
|
+
this.onWatchChange?.(appConfig, action, result);
|
|
717
|
+
}));
|
|
718
|
+
}
|
|
719
|
+
catch {
|
|
720
|
+
// noop
|
|
721
|
+
}
|
|
679
722
|
}
|
|
723
|
+
/* eslint-enable no-await-in-loop */
|
|
680
724
|
// refetch app to get updated configs
|
|
681
725
|
this.app = await this.getAppWithConfig(this.app.id);
|
|
682
726
|
// Process next accumulated changes
|
|
@@ -691,6 +735,16 @@ export class PushAppCommand extends RemoteAppCommand {
|
|
|
691
735
|
async setWatchingFiles() {
|
|
692
736
|
this.watchingFiles = new Set(await globAllFilesByPath(this.appPath));
|
|
693
737
|
}
|
|
738
|
+
async startProxyServer(port) {
|
|
739
|
+
// Find an open port starting at 3000
|
|
740
|
+
const freePort = port ||
|
|
741
|
+
(await getPort({ port: portNumbers(3000, 3100) })) ||
|
|
742
|
+
DEV_SERVER_FALLBACK_PORT;
|
|
743
|
+
// Start proxy
|
|
744
|
+
const proxyUrl = await getProxyUrl(freePort);
|
|
745
|
+
await this.updateLocalProxy(proxyUrl, this.storefront?.id);
|
|
746
|
+
return freePort;
|
|
747
|
+
}
|
|
694
748
|
async updateFrontendDeployment(currentStore, projectType, deploymentUrl, deploymentHash) {
|
|
695
749
|
const spinner = ora();
|
|
696
750
|
spinner.start('Updating frontend deployment...');
|
|
@@ -708,6 +762,17 @@ export class PushAppCommand extends RemoteAppCommand {
|
|
|
708
762
|
}), () => spinner.fail('Error updating app deployment'));
|
|
709
763
|
spinner.succeed('Updated frontend deployment.\n');
|
|
710
764
|
}
|
|
765
|
+
async updateLocalProxy(proxyUrl, storefrontId) {
|
|
766
|
+
const storefront = this.app.type === 'storefront' &&
|
|
767
|
+
(await this.getAppStorefront({ storefront_id: storefrontId }));
|
|
768
|
+
await this.handleRequestErrors(async () => this.api.put({ adminPath: `/client/apps/${this.app.id}/local-proxy` }, {
|
|
769
|
+
body: {
|
|
770
|
+
proxy_url: proxyUrl,
|
|
771
|
+
storefront_id: storefront?.id || null,
|
|
772
|
+
storefront_slug: storefront?.slug || null,
|
|
773
|
+
},
|
|
774
|
+
}));
|
|
775
|
+
}
|
|
711
776
|
async watchForChanges({ logChanges, onChange, syncAll, } = {}) {
|
|
712
777
|
this.watchingIgnoreFilter = await getGlobIgnorePathsChecker(this.appPath);
|
|
713
778
|
this.logWatchChanges = logChanges ?? true;
|
|
@@ -723,19 +788,27 @@ export class PushAppCommand extends RemoteAppCommand {
|
|
|
723
788
|
}
|
|
724
789
|
fs.watch(this.appPath, { recursive: true }, this.watchListener);
|
|
725
790
|
}
|
|
726
|
-
async wranglerDeployFrontend(
|
|
791
|
+
async wranglerDeployFrontend(_projectType) {
|
|
727
792
|
let deploymentUrl;
|
|
728
793
|
let interactiveError = false;
|
|
794
|
+
let pagesProjectError = false;
|
|
729
795
|
this.log(`\nDeploying to Cloudflare...\n`);
|
|
730
796
|
try {
|
|
731
|
-
await this.
|
|
797
|
+
await this.execFrontend(`npx wrangler deploy`, (string) => {
|
|
732
798
|
// Dependent on wrangler output
|
|
733
799
|
// Parse the deployment URL from the wrangler output.
|
|
734
|
-
const match = string.match(
|
|
800
|
+
const match = string.match(/^\s*(https:\/\/\S+\.workers\.dev)\s*[\S\s]*?Current Version ID: [\w-]+/m);
|
|
735
801
|
if (match && match.length > 0) {
|
|
736
802
|
deploymentUrl = match[1];
|
|
737
803
|
return false;
|
|
738
804
|
}
|
|
805
|
+
// Check for Pages project error
|
|
806
|
+
if (string.includes("It looks like you've run a Workers-specific command in a Pages project") ||
|
|
807
|
+
string.includes('please run `wrangler pages deploy` instead') ||
|
|
808
|
+
string.includes('Missing entry-point')) {
|
|
809
|
+
pagesProjectError = true;
|
|
810
|
+
return false;
|
|
811
|
+
}
|
|
739
812
|
if (interactiveError ||
|
|
740
813
|
string.includes('non-interactive mode') ||
|
|
741
814
|
string.includes('non-interactive environment')) {
|
|
@@ -746,10 +819,16 @@ export class PushAppCommand extends RemoteAppCommand {
|
|
|
746
819
|
}
|
|
747
820
|
catch (error) {
|
|
748
821
|
// noop
|
|
749
|
-
if (
|
|
750
|
-
this.
|
|
751
|
-
|
|
752
|
-
|
|
822
|
+
if (pagesProjectError) {
|
|
823
|
+
this.error(style.funcWarn(`⚠️ Version incompatibility!\n` +
|
|
824
|
+
`Your project is configured for Cloudflare Pages, but Swell CLI v2.1.0+ uses Cloudflare Workers.\n\n` +
|
|
825
|
+
`To fix this, you can either:\n` +
|
|
826
|
+
`1. Update your app project (for Swell official apps) or reconfigure it for workers\n` +
|
|
827
|
+
` (https://developers.cloudflare.com/workers/static-assets/migration-guides/migrate-from-pages/)\n` +
|
|
828
|
+
`2. Use an older Swell CLI version: npm install -g @swell/cli@2.0.20\n`));
|
|
829
|
+
}
|
|
830
|
+
else if (interactiveError) {
|
|
831
|
+
this.error(style.funcWarn(`Your Cloudflare environment must be initialized by logging in with \`wrangler login\`, and exporting the \`CLOUDFLARE_ACCOUNT_ID\` environment variable. Refer to https://developers.cloudflare.com/workers/wrangler/configuration/ for details, and re-run this command to connect the deployment with your app.`));
|
|
753
832
|
}
|
|
754
833
|
else {
|
|
755
834
|
throw error;
|
|
@@ -5,11 +5,11 @@ import { App, AppConfig, AppVersion } from './lib/apps/index.js';
|
|
|
5
5
|
* configurations.
|
|
6
6
|
*/
|
|
7
7
|
export declare abstract class RemoteAppCommand extends AppCommand {
|
|
8
|
-
appType: string;
|
|
9
8
|
static delayOrientation: boolean;
|
|
10
9
|
static orientation: AppCommandOrientation;
|
|
11
10
|
app: App;
|
|
12
11
|
appCreated: boolean;
|
|
12
|
+
appType: string;
|
|
13
13
|
storefront?: any;
|
|
14
14
|
storefrontLocalUrl?: string;
|
|
15
15
|
themeSyncApp?: boolean;
|
|
@@ -15,7 +15,6 @@ import style from './lib/style.js';
|
|
|
15
15
|
* configurations.
|
|
16
16
|
*/
|
|
17
17
|
export class RemoteAppCommand extends AppCommand {
|
|
18
|
-
appType = 'any';
|
|
19
18
|
// allows child commands to set orientation
|
|
20
19
|
static delayOrientation = false;
|
|
21
20
|
// most of the times this will be the target environment
|
|
@@ -24,6 +23,7 @@ export class RemoteAppCommand extends AppCommand {
|
|
|
24
23
|
app = {};
|
|
25
24
|
// Indicates the app was created during the command execution
|
|
26
25
|
appCreated = false;
|
|
26
|
+
appType = 'any';
|
|
27
27
|
// API instance of the target storefront, if applicable
|
|
28
28
|
storefront = null;
|
|
29
29
|
// Indicates we are syncing with a local app frontend via proxy
|
|
@@ -638,7 +638,9 @@ export class RemoteAppCommand extends AppCommand {
|
|
|
638
638
|
const { env, store } = flags;
|
|
639
639
|
if (this.app.name) {
|
|
640
640
|
// Show theme version if not syncing app
|
|
641
|
-
const version = !this.themeSyncApp &&
|
|
641
|
+
const version = !this.themeSyncApp &&
|
|
642
|
+
this.app.type === 'theme' &&
|
|
643
|
+
this.storefront?.theme_version
|
|
642
644
|
? this.storefront?.theme_version
|
|
643
645
|
: this.app.version;
|
|
644
646
|
orientationOutput.push(`[${this.app.type === 'theme' ? 'theme' : 'app'}] ${style.appName(this.app.name)}${version ? ` v${version}` : ''}`);
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { HttpMethod } from './lib/api.js';
|
|
2
|
+
import { SwellCommand } from './swell-command.js';
|
|
3
|
+
export declare abstract class SwellApiCommand extends SwellCommand {
|
|
4
|
+
protected abstract get method(): HttpMethod;
|
|
5
|
+
protected request(command: typeof SwellApiCommand, requestOptions?: Api.RequestOptions): Promise<void>;
|
|
6
|
+
protected catch(error: Error): Promise<any>;
|
|
7
|
+
private parseCommand;
|
|
8
|
+
private processBody;
|
|
9
|
+
private isFilePath;
|
|
10
|
+
private handleResponse;
|
|
11
|
+
private onSuccess;
|
|
12
|
+
private onError;
|
|
13
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { FetchError } from 'node-fetch';
|
|
4
|
+
import { SwellCommand } from './swell-command.js';
|
|
5
|
+
export class SwellApiCommand extends SwellCommand {
|
|
6
|
+
async request(command, requestOptions = {}) {
|
|
7
|
+
const { paths, options } = await this.parseCommand(command);
|
|
8
|
+
const response = await this.api[this.method](paths, {
|
|
9
|
+
rawResponse: true,
|
|
10
|
+
...options,
|
|
11
|
+
...requestOptions,
|
|
12
|
+
});
|
|
13
|
+
await this.handleResponse(response);
|
|
14
|
+
}
|
|
15
|
+
async catch(error) {
|
|
16
|
+
if (error instanceof FetchError) {
|
|
17
|
+
const message = `Could not connect to Swell API. Please try again later: ${error.message}`;
|
|
18
|
+
return this.onError(message, { exit: 2, code: error.code });
|
|
19
|
+
}
|
|
20
|
+
return this.onError(error.message, { exit: 1 });
|
|
21
|
+
}
|
|
22
|
+
async parseCommand(options, argv) {
|
|
23
|
+
const parsedInput = await super.parse(options, argv);
|
|
24
|
+
const { args, flags } = parsedInput;
|
|
25
|
+
const { path } = args;
|
|
26
|
+
const { live, body } = flags;
|
|
27
|
+
if (!live) {
|
|
28
|
+
await this.api.setEnv('test');
|
|
29
|
+
}
|
|
30
|
+
if (!path.startsWith('/')) {
|
|
31
|
+
throw new Error('Path must start with a forward slash (/)');
|
|
32
|
+
}
|
|
33
|
+
return {
|
|
34
|
+
...parsedInput,
|
|
35
|
+
paths: { adminPath: `/data${path}` },
|
|
36
|
+
options: {
|
|
37
|
+
body: await this.processBody(body),
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
async processBody(body) {
|
|
42
|
+
if (!body) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
const bodyData = this.isFilePath(body)
|
|
46
|
+
? await fs.promises.readFile(path.resolve(body), 'utf8')
|
|
47
|
+
: body;
|
|
48
|
+
return JSON.parse(bodyData);
|
|
49
|
+
}
|
|
50
|
+
isFilePath(filePath) {
|
|
51
|
+
return (path.isAbsolute(filePath) ||
|
|
52
|
+
filePath.startsWith('./') ||
|
|
53
|
+
filePath.startsWith('../'));
|
|
54
|
+
}
|
|
55
|
+
async handleResponse(response) {
|
|
56
|
+
const responseText = await response.text();
|
|
57
|
+
if (responseText.length <= 2) {
|
|
58
|
+
throw new Error('Not found');
|
|
59
|
+
}
|
|
60
|
+
const result = JSON.parse(responseText);
|
|
61
|
+
if (result.error || result.errors || !response.ok) {
|
|
62
|
+
throw new Error(responseText);
|
|
63
|
+
}
|
|
64
|
+
this.onSuccess(result);
|
|
65
|
+
}
|
|
66
|
+
onSuccess(result) {
|
|
67
|
+
this.log(JSON.stringify(result, null, 2));
|
|
68
|
+
}
|
|
69
|
+
onError(message, { code, exit }) {
|
|
70
|
+
this.error(message, {
|
|
71
|
+
code,
|
|
72
|
+
exit,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
}
|
package/dist/swell-command.d.ts
CHANGED
|
@@ -14,8 +14,8 @@ export declare abstract class SwellCommand extends Command {
|
|
|
14
14
|
api: Api;
|
|
15
15
|
requiresAuth: boolean;
|
|
16
16
|
constructor(argv: string[], config: Config);
|
|
17
|
-
catch(error: any): Promise<void>;
|
|
18
17
|
debugJson(...args: any[]): void;
|
|
19
18
|
logError(message: string): void;
|
|
20
19
|
logSuccess(message: string): void;
|
|
20
|
+
protected catch(error: any): Promise<void>;
|
|
21
21
|
}
|
package/dist/swell-command.js
CHANGED
|
@@ -21,15 +21,6 @@ export class SwellCommand extends Command {
|
|
|
21
21
|
super(argv, config);
|
|
22
22
|
this.api = new Api();
|
|
23
23
|
}
|
|
24
|
-
async catch(error) {
|
|
25
|
-
if (error instanceof GitUnknownError) {
|
|
26
|
-
this.error(style.error(`git: ${error.stderr || error.stdout || error.message}`));
|
|
27
|
-
}
|
|
28
|
-
else if (error instanceof FetchError) {
|
|
29
|
-
this.error(`Could not connect to Swell API. Please try again later: ${error.message}`);
|
|
30
|
-
}
|
|
31
|
-
throw error;
|
|
32
|
-
}
|
|
33
24
|
debugJson(...args) {
|
|
34
25
|
const stringified = args.map((arg) => typeof arg === 'string' ? arg : JSON.stringify(arg, null, 4));
|
|
35
26
|
this.debug(...stringified);
|
|
@@ -40,4 +31,13 @@ export class SwellCommand extends Command {
|
|
|
40
31
|
logSuccess(message) {
|
|
41
32
|
this.log(`${style.success(message)}`);
|
|
42
33
|
}
|
|
34
|
+
async catch(error) {
|
|
35
|
+
if (error instanceof GitUnknownError) {
|
|
36
|
+
this.error(style.error(`git: ${error.stderr || error.stdout || error.message}`));
|
|
37
|
+
}
|
|
38
|
+
else if (error instanceof FetchError) {
|
|
39
|
+
this.error(`Could not connect to Swell API. Please try again later: ${error.message}`);
|
|
40
|
+
}
|
|
41
|
+
throw error;
|
|
42
|
+
}
|
|
43
43
|
}
|