@swell/cli 2.1.0 → 2.2.1
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 +9 -9
- package/dist/commands/app/dev.js +163 -139
- package/dist/commands/app/frontend/dev.d.ts +1 -1
- package/dist/commands/app/frontend/dev.js +84 -78
- 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/index.js +5 -1
- package/dist/commands/create/model.d.ts +4 -0
- package/dist/commands/create/model.js +120 -25
- package/dist/commands/create/tests.d.ts +15 -0
- package/dist/commands/create/tests.js +79 -0
- 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 +12 -2
- package/dist/create-app-command.js +103 -80
- package/dist/create-collection-command.js +4 -1
- package/dist/create-config-command.js +4 -0
- package/dist/env/local.d.ts +1 -0
- package/dist/env/local.js +1 -0
- package/dist/env/production.d.ts +1 -0
- package/dist/env/production.js +1 -0
- package/dist/env/review.d.ts +1 -0
- package/dist/env/review.js +1 -0
- package/dist/env/staging.d.ts +1 -0
- package/dist/env/staging.js +1 -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/index.d.ts +2 -0
- package/dist/lib/apps/index.js +20 -14
- package/dist/lib/config.d.ts +1 -1
- package/dist/lib/constants.d.ts +1 -0
- package/dist/lib/constants.js +1 -0
- 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/create/tests/templates/env-dts.d.ts +1 -0
- package/dist/lib/create/tests/templates/env-dts.js +14 -0
- package/dist/lib/create/tests/templates/index.d.ts +8 -0
- package/dist/lib/create/tests/templates/index.js +8 -0
- package/dist/lib/create/tests/templates/integration-test.d.ts +1 -0
- package/dist/lib/create/tests/templates/integration-test.js +19 -0
- package/dist/lib/create/tests/templates/mock-request.d.ts +2 -0
- package/dist/lib/create/tests/templates/mock-request.js +112 -0
- package/dist/lib/create/tests/templates/setup-globals.d.ts +1 -0
- package/dist/lib/create/tests/templates/setup-globals.js +23 -0
- package/dist/lib/create/tests/templates/swell-client.d.ts +1 -0
- package/dist/lib/create/tests/templates/swell-client.js +126 -0
- package/dist/lib/create/tests/templates/tsconfig.d.ts +1 -0
- package/dist/lib/create/tests/templates/tsconfig.js +15 -0
- package/dist/lib/create/tests/templates/unit-test.d.ts +1 -0
- package/dist/lib/create/tests/templates/unit-test.js +42 -0
- package/dist/lib/create/tests/templates/vitest-config.d.ts +2 -0
- package/dist/lib/create/tests/templates/vitest-config.js +121 -0
- package/dist/lib/create/tests/types.d.ts +21 -0
- package/dist/lib/create/tests/types.js +1 -0
- package/dist/lib/create/tests.d.ts +4 -0
- package/dist/lib/create/tests.js +113 -0
- package/dist/lib/sessions.js +4 -3
- package/dist/lib/style.js +0 -1
- package/dist/lib/theme-sync.d.ts +6 -6
- package/dist/lib/theme-sync.js +49 -39
- package/dist/push-app-command.d.ts +7 -7
- package/dist/push-app-command.js +38 -36
- 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/oclif.manifest.json +639 -46
- package/package.json +8 -1
package/dist/commands/app/dev.js
CHANGED
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
import { Flags } from '@oclif/core';
|
|
2
|
-
import ora from 'ora';
|
|
3
2
|
import getPort, { portNumbers } from 'get-port';
|
|
3
|
+
import { spawn } from 'node:child_process';
|
|
4
4
|
import * as fs from 'node:fs';
|
|
5
|
-
import * as path from 'node:path';
|
|
6
|
-
import * as os from 'node:os';
|
|
7
5
|
import * as http from 'node:http';
|
|
8
|
-
import
|
|
6
|
+
import * as os from 'node:os';
|
|
7
|
+
import * as path from 'node:path';
|
|
8
|
+
import ora from 'ora';
|
|
9
9
|
import { ConfigType, allConfigFilesInDir, appConfigFromFile, } from '../../lib/apps/index.js';
|
|
10
10
|
import { bundleFunction } from '../../lib/bundle.js';
|
|
11
11
|
import style from '../../lib/style.js';
|
|
12
12
|
import { PushAppCommand } from '../../push-app-command.js';
|
|
13
13
|
export default class AppDev extends PushAppCommand {
|
|
14
|
+
static delayOrientation = true;
|
|
14
15
|
static examples = [
|
|
15
16
|
'swell app dev',
|
|
16
17
|
'swell app dev --storefront-id <id>',
|
|
@@ -36,16 +37,15 @@ export default class AppDev extends PushAppCommand {
|
|
|
36
37
|
description: 'for storefront apps, prompt to select a storefront to preview',
|
|
37
38
|
}),
|
|
38
39
|
};
|
|
39
|
-
static summary = `Run an app in dev mode from your local machine.`;
|
|
40
|
-
static delayOrientation = true;
|
|
41
40
|
static orientation = {
|
|
42
41
|
env: 'test',
|
|
43
42
|
};
|
|
44
|
-
|
|
45
|
-
|
|
43
|
+
static summary = `Run an app in dev mode from your local machine.`;
|
|
44
|
+
functionErrors = new Map();
|
|
46
45
|
// All available functions
|
|
47
46
|
functionPorts = new Map();
|
|
48
|
-
|
|
47
|
+
// Directory for compiled function files and wrangler context
|
|
48
|
+
tmpDir = '';
|
|
49
49
|
async run() {
|
|
50
50
|
const { flags } = await this.parse(AppDev);
|
|
51
51
|
const { port, 'frontend-port': frontendPort } = flags;
|
|
@@ -63,28 +63,6 @@ export default class AppDev extends PushAppCommand {
|
|
|
63
63
|
await this.startAppFunctionServer(spinner, serverPort);
|
|
64
64
|
await this.runAppFrontendDevIfApplicable(frontendPort);
|
|
65
65
|
}
|
|
66
|
-
async startAppFunctionServer(spinner, serverPort) {
|
|
67
|
-
// Get all functions in this app
|
|
68
|
-
const functions = await this.getAppFunctions();
|
|
69
|
-
if (functions.length === 0) {
|
|
70
|
-
spinner.stop();
|
|
71
|
-
return;
|
|
72
|
-
}
|
|
73
|
-
// Create TMP directory for wrangler configs and bundled functions
|
|
74
|
-
await this.createTmpDirectory();
|
|
75
|
-
// Bundle functions and start wrangler dev servers for each
|
|
76
|
-
await this.startFunctionServers(functions);
|
|
77
|
-
// Start watching for function file changes
|
|
78
|
-
this.watchForChanges({
|
|
79
|
-
onChange: this.onChangeFunctionWatcher.bind(this),
|
|
80
|
-
});
|
|
81
|
-
// Create a routing server that proxies requests to function servers
|
|
82
|
-
await this.createFunctionRouter(serverPort);
|
|
83
|
-
spinner.succeed(`App function server running on port ${serverPort}\n`);
|
|
84
|
-
this.log(`${style.appConfigName(`Functions:`)}`);
|
|
85
|
-
await this.logAllFunctions(functions);
|
|
86
|
-
this.log(`${style.success('→')} Call functions at: http://localhost:${serverPort}/<function-name>\n`);
|
|
87
|
-
}
|
|
88
66
|
async runAppFrontendDevIfApplicable(frontendPort) {
|
|
89
67
|
const projectType = this.getFrontendProjectType(false);
|
|
90
68
|
if (projectType) {
|
|
@@ -97,6 +75,72 @@ export default class AppDev extends PushAppCommand {
|
|
|
97
75
|
return proxyPort;
|
|
98
76
|
}
|
|
99
77
|
}
|
|
78
|
+
async createFunctionRouter(serverPort) {
|
|
79
|
+
const server = http.createServer((req, res) => {
|
|
80
|
+
const url = new URL(req.url, `http://localhost:${serverPort}`);
|
|
81
|
+
const functionName = url.pathname.slice(1); // Remove leading slash
|
|
82
|
+
if (this.functionPorts.has(functionName)) {
|
|
83
|
+
const targetPort = this.functionPorts.get(functionName);
|
|
84
|
+
// Proxy the request to the function server
|
|
85
|
+
const proxyReq = http.request({
|
|
86
|
+
hostname: 'localhost',
|
|
87
|
+
port: targetPort,
|
|
88
|
+
path: req.url,
|
|
89
|
+
method: req.method,
|
|
90
|
+
headers: {
|
|
91
|
+
...req.headers,
|
|
92
|
+
'Swell-Local-Dev': 'true',
|
|
93
|
+
},
|
|
94
|
+
}, (proxyRes) => {
|
|
95
|
+
res.writeHead(proxyRes.statusCode, proxyRes.headers);
|
|
96
|
+
proxyRes.pipe(res);
|
|
97
|
+
});
|
|
98
|
+
proxyReq.on('error', (error) => {
|
|
99
|
+
res.writeHead(500);
|
|
100
|
+
res.end(`Error proxying to function ${functionName}: ${error.message}`);
|
|
101
|
+
});
|
|
102
|
+
// Log when response is finished, method, status, and time to execute by proxy
|
|
103
|
+
const startTime = Date.now();
|
|
104
|
+
res.on('finish', async () => {
|
|
105
|
+
const duration = Date.now() - startTime;
|
|
106
|
+
// Log after a short delay to ensure output order
|
|
107
|
+
await new Promise((r) => {
|
|
108
|
+
setTimeout(r, 100);
|
|
109
|
+
});
|
|
110
|
+
this.log(`\n${style.appConfigValue(`→ ${functionName}`)} ${this.timestampStyled()} [${res.statusCode}] ${req.method} ${req.url} (${duration}ms)\n`);
|
|
111
|
+
});
|
|
112
|
+
req.pipe(proxyReq);
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
res.writeHead(404);
|
|
116
|
+
const functionError = this.functionErrors.get(functionName);
|
|
117
|
+
const functionsAvailable = [...this.functionPorts.keys()];
|
|
118
|
+
res.end(functionError
|
|
119
|
+
? `Function Error: ${functionError}`
|
|
120
|
+
: `Function '${functionName}' not found. ${functionsAvailable.length > 0
|
|
121
|
+
? `Available functions: ${[...this.functionPorts.keys()].join(', ')}`
|
|
122
|
+
: ''}`);
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
server.listen(serverPort);
|
|
126
|
+
}
|
|
127
|
+
async createTmpDirectory() {
|
|
128
|
+
const tmpBase = path.join(os.tmpdir(), 'swell-cli');
|
|
129
|
+
const appTmpDir = path.join(tmpBase, this.app.id || 'unknown-app', 'functions');
|
|
130
|
+
// Create the directory structure
|
|
131
|
+
await fs.promises.mkdir(appTmpDir, { recursive: true });
|
|
132
|
+
this.tmpDir = appTmpDir;
|
|
133
|
+
}
|
|
134
|
+
generateWranglerConfig(functionName, bundledPath) {
|
|
135
|
+
return `
|
|
136
|
+
name = "${functionName}"
|
|
137
|
+
main = "${bundledPath}"
|
|
138
|
+
compatibility_date = "2023-05-18"
|
|
139
|
+
|
|
140
|
+
[vars]
|
|
141
|
+
ENVIRONMENT = "development"
|
|
142
|
+
`.trim();
|
|
143
|
+
}
|
|
100
144
|
async getAppFunctions() {
|
|
101
145
|
const functions = [];
|
|
102
146
|
// Get all function files from the functions directory
|
|
@@ -109,13 +153,14 @@ export default class AppDev extends PushAppCommand {
|
|
|
109
153
|
functions.push(config);
|
|
110
154
|
}
|
|
111
155
|
}
|
|
112
|
-
catch
|
|
156
|
+
catch {
|
|
113
157
|
// functions directory doesn't exist
|
|
114
158
|
}
|
|
115
159
|
return functions;
|
|
116
160
|
}
|
|
117
161
|
async logAllFunctions(functions) {
|
|
118
162
|
for (const func of functions) {
|
|
163
|
+
// eslint-disable-next-line no-await-in-loop
|
|
119
164
|
await this.logFunction(func);
|
|
120
165
|
}
|
|
121
166
|
this.log();
|
|
@@ -144,13 +189,16 @@ export default class AppDev extends PushAppCommand {
|
|
|
144
189
|
}
|
|
145
190
|
else if (config?.route) {
|
|
146
191
|
this.log(` Trigger: Route`);
|
|
147
|
-
|
|
148
|
-
.
|
|
149
|
-
|
|
192
|
+
if (config.route.methods?.length) {
|
|
193
|
+
this.log(` Methods: ${style.dim(config.route.methods
|
|
194
|
+
.map((m) => String(m).toUpperCase())
|
|
195
|
+
.join(', '))}`);
|
|
196
|
+
}
|
|
150
197
|
if (config.route.headers) {
|
|
151
198
|
const headers = Object.entries(config.route.headers).map(([key, value]) => `${key}: ${value}`);
|
|
152
199
|
this.log(` Headers:`);
|
|
153
|
-
|
|
200
|
+
for (const header of headers)
|
|
201
|
+
this.log(` ${style.dim(header)}`);
|
|
154
202
|
}
|
|
155
203
|
if (config.route.cache?.timeout) {
|
|
156
204
|
this.log(` Cache timeout: ${style.dim(config.route.cache.timeout)}`);
|
|
@@ -159,6 +207,9 @@ export default class AppDev extends PushAppCommand {
|
|
|
159
207
|
this.log(` Public: ${style.dim('true')}`);
|
|
160
208
|
}
|
|
161
209
|
}
|
|
210
|
+
if (config?.extension) {
|
|
211
|
+
this.log(` Extension: ${style.dim(config.extension)}`);
|
|
212
|
+
}
|
|
162
213
|
if (config?.description) {
|
|
163
214
|
this.log(` Description: ${style.dim(config.description)}`);
|
|
164
215
|
}
|
|
@@ -167,15 +218,68 @@ export default class AppDev extends PushAppCommand {
|
|
|
167
218
|
this.log(` ${style.error('Error loading config:', error.message)}`);
|
|
168
219
|
}
|
|
169
220
|
}
|
|
170
|
-
async
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
221
|
+
async onChangeFunctionWatcher(appConfig, action, result) {
|
|
222
|
+
// If no result, skip
|
|
223
|
+
if (!result) {
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
// If deleted result, skip and remove port
|
|
227
|
+
if (action === 'remove' || !result) {
|
|
228
|
+
this.functionPorts.delete(appConfig?.name);
|
|
229
|
+
this.log();
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
// Skip everything except functions
|
|
233
|
+
if (appConfig?.type !== ConfigType.FUNCTION) {
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
try {
|
|
237
|
+
const fullPath = path.join(this.appPath, appConfig.filePath);
|
|
238
|
+
// Re-bundle the function
|
|
239
|
+
const { code } = await bundleFunction(fullPath);
|
|
240
|
+
// Update the bundled file (wrangler dev will auto-reload)
|
|
241
|
+
const bundledPath = path.join(this.tmpDir, `${appConfig.name}.js`);
|
|
242
|
+
await fs.promises.writeFile(bundledPath, code);
|
|
243
|
+
if (this.functionPorts.has(appConfig.name)) {
|
|
244
|
+
this.log(`\nUpdating function ${appConfig.name}...`);
|
|
245
|
+
}
|
|
246
|
+
else {
|
|
247
|
+
this.log(`\nStarting function ${appConfig.name}...`);
|
|
248
|
+
await this.startFunctionServers([appConfig]);
|
|
249
|
+
}
|
|
250
|
+
await this.logFunction(appConfig);
|
|
251
|
+
}
|
|
252
|
+
catch (error) {
|
|
253
|
+
this.log(`${style.error('Error re-bundling function')} ${appConfig.name}: ${error.message}`);
|
|
254
|
+
}
|
|
255
|
+
this.log();
|
|
256
|
+
}
|
|
257
|
+
async startAppFunctionServer(spinner, serverPort) {
|
|
258
|
+
// Get all functions in this app
|
|
259
|
+
const functions = await this.getAppFunctions();
|
|
260
|
+
if (functions.length === 0) {
|
|
261
|
+
spinner.stop();
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
// Create TMP directory for wrangler configs and bundled functions
|
|
265
|
+
await this.createTmpDirectory();
|
|
266
|
+
// Bundle functions and start wrangler dev servers for each
|
|
267
|
+
await this.startFunctionServers(functions);
|
|
268
|
+
// Start watching for function file changes
|
|
269
|
+
this.watchForChanges({
|
|
270
|
+
onChange: this.onChangeFunctionWatcher.bind(this),
|
|
271
|
+
});
|
|
272
|
+
// Create a routing server that proxies requests to function servers
|
|
273
|
+
await this.createFunctionRouter(serverPort);
|
|
274
|
+
spinner.succeed(`App function server running on port ${serverPort}\n`);
|
|
275
|
+
this.log(`${style.appConfigName(`Functions:`)}`);
|
|
276
|
+
await this.logAllFunctions(functions);
|
|
277
|
+
this.log(`${style.success('→')} Call functions at: http://localhost:${serverPort}/<function-name>\n`);
|
|
176
278
|
}
|
|
177
279
|
async startFunctionServers(functions) {
|
|
178
280
|
const functionStatus = new Map();
|
|
281
|
+
const allocatedInspectorPorts = new Set();
|
|
282
|
+
/* eslint-disable no-await-in-loop */
|
|
179
283
|
for (const func of functions) {
|
|
180
284
|
const functionName = func.name;
|
|
181
285
|
if (this.functionPorts.has(functionName)) {
|
|
@@ -183,6 +287,11 @@ export default class AppDev extends PushAppCommand {
|
|
|
183
287
|
continue;
|
|
184
288
|
}
|
|
185
289
|
const functionPort = await getPort({ port: portNumbers(9000, 9100) });
|
|
290
|
+
const inspectorPort = await getPort({
|
|
291
|
+
port: portNumbers(9229, 9329),
|
|
292
|
+
exclude: [...allocatedInspectorPorts],
|
|
293
|
+
});
|
|
294
|
+
allocatedInspectorPorts.add(inspectorPort);
|
|
186
295
|
try {
|
|
187
296
|
// Bundle the function
|
|
188
297
|
const fullPath = path.join(this.appPath, func.filePath);
|
|
@@ -201,15 +310,16 @@ export default class AppDev extends PushAppCommand {
|
|
|
201
310
|
'dev',
|
|
202
311
|
`--config=${configPath}`,
|
|
203
312
|
`--port=${functionPort}`,
|
|
313
|
+
`--inspector-port=${inspectorPort}`,
|
|
204
314
|
], {
|
|
205
315
|
cwd: this.tmpDir,
|
|
206
|
-
//stdio: 'pipe', // Capture output for debugging
|
|
316
|
+
// stdio: 'pipe', // Capture output for debugging
|
|
207
317
|
detached: false,
|
|
208
318
|
});
|
|
209
319
|
const handleFunctionOutput = (data) => {
|
|
210
320
|
const output = data.toString();
|
|
211
321
|
// Remove ANSI escape sequences that cause line clearing
|
|
212
|
-
const cleanOutput = output.replace('\
|
|
322
|
+
const cleanOutput = output.replace('\u001B[2K\u001B[1A\u001B[2K\u001B[G', '');
|
|
213
323
|
const lines = cleanOutput
|
|
214
324
|
.split('\n')
|
|
215
325
|
.filter((line) => line.trim());
|
|
@@ -262,6 +372,12 @@ export default class AppDev extends PushAppCommand {
|
|
|
262
372
|
functionStatus.set(functionName, 'error');
|
|
263
373
|
this.functionErrors.set(functionName, error.message);
|
|
264
374
|
});
|
|
375
|
+
wranglerProcess.on('exit', (code, _signal) => {
|
|
376
|
+
if (functionStatus.get(functionName) === 'starting') {
|
|
377
|
+
functionStatus.set(functionName, 'error');
|
|
378
|
+
this.functionErrors.set(functionName, `Process exited with code ${code}`);
|
|
379
|
+
}
|
|
380
|
+
});
|
|
265
381
|
// Store the port for routing
|
|
266
382
|
this.functionPorts.set(functionName, functionPort);
|
|
267
383
|
}
|
|
@@ -270,13 +386,14 @@ export default class AppDev extends PushAppCommand {
|
|
|
270
386
|
this.functionErrors.set(functionName, error.message);
|
|
271
387
|
}
|
|
272
388
|
}
|
|
389
|
+
/* eslint-enable no-await-in-loop */
|
|
273
390
|
await new Promise((resolve) => {
|
|
274
|
-
if (
|
|
391
|
+
if (functions.length === 0) {
|
|
275
392
|
resolve();
|
|
276
393
|
return;
|
|
277
394
|
}
|
|
278
395
|
const checkAllRunning = () => {
|
|
279
|
-
const allRunning =
|
|
396
|
+
const allRunning = [...functionStatus.values()].every((status) => status !== 'starting');
|
|
280
397
|
if (allRunning) {
|
|
281
398
|
resolve();
|
|
282
399
|
}
|
|
@@ -288,97 +405,4 @@ export default class AppDev extends PushAppCommand {
|
|
|
288
405
|
});
|
|
289
406
|
return { functionStatus };
|
|
290
407
|
}
|
|
291
|
-
generateWranglerConfig(functionName, bundledPath) {
|
|
292
|
-
return `
|
|
293
|
-
name = "${functionName}"
|
|
294
|
-
main = "${bundledPath}"
|
|
295
|
-
compatibility_date = "2023-05-18"
|
|
296
|
-
|
|
297
|
-
[vars]
|
|
298
|
-
ENVIRONMENT = "development"
|
|
299
|
-
`.trim();
|
|
300
|
-
}
|
|
301
|
-
async createFunctionRouter(serverPort) {
|
|
302
|
-
const server = http.createServer((req, res) => {
|
|
303
|
-
const url = new URL(req.url, `http://localhost:${serverPort}`);
|
|
304
|
-
const functionName = url.pathname.slice(1); // Remove leading slash
|
|
305
|
-
if (this.functionPorts.has(functionName)) {
|
|
306
|
-
const targetPort = this.functionPorts.get(functionName);
|
|
307
|
-
// Proxy the request to the function server
|
|
308
|
-
const proxyReq = http.request({
|
|
309
|
-
hostname: 'localhost',
|
|
310
|
-
port: targetPort,
|
|
311
|
-
path: req.url,
|
|
312
|
-
method: req.method,
|
|
313
|
-
headers: {
|
|
314
|
-
...req.headers,
|
|
315
|
-
'Swell-Local-Dev': 'true',
|
|
316
|
-
},
|
|
317
|
-
}, (proxyRes) => {
|
|
318
|
-
res.writeHead(proxyRes.statusCode, proxyRes.headers);
|
|
319
|
-
proxyRes.pipe(res);
|
|
320
|
-
});
|
|
321
|
-
proxyReq.on('error', (error) => {
|
|
322
|
-
res.writeHead(500);
|
|
323
|
-
res.end(`Error proxying to function ${functionName}: ${error.message}`);
|
|
324
|
-
});
|
|
325
|
-
// Log when response is finished, method, status, and time to execute by proxy
|
|
326
|
-
const startTime = Date.now();
|
|
327
|
-
res.on('finish', async () => {
|
|
328
|
-
const duration = Date.now() - startTime;
|
|
329
|
-
// Log after a short delay to ensure output order
|
|
330
|
-
await new Promise((r) => setTimeout(r, 100));
|
|
331
|
-
this.log(`\n${style.appConfigValue(`→ ${functionName}`)} ${this.timestampStyled()} [${res.statusCode}] ${req.method} ${req.url} (${duration}ms)\n`);
|
|
332
|
-
});
|
|
333
|
-
req.pipe(proxyReq);
|
|
334
|
-
}
|
|
335
|
-
else {
|
|
336
|
-
res.writeHead(404);
|
|
337
|
-
const functionError = this.functionErrors.get(functionName);
|
|
338
|
-
const functionsAvailable = Array.from(this.functionPorts.keys());
|
|
339
|
-
res.end(functionError
|
|
340
|
-
? `Function Error: ${functionError}`
|
|
341
|
-
: `Function '${functionName}' not found. ${functionsAvailable.length > 0
|
|
342
|
-
? `Available functions: ${Array.from(this.functionPorts.keys()).join(', ')}`
|
|
343
|
-
: ''}`);
|
|
344
|
-
}
|
|
345
|
-
});
|
|
346
|
-
server.listen(serverPort);
|
|
347
|
-
}
|
|
348
|
-
async onChangeFunctionWatcher(appConfig, action, result) {
|
|
349
|
-
// If no result, skip
|
|
350
|
-
if (!result) {
|
|
351
|
-
return;
|
|
352
|
-
}
|
|
353
|
-
// If deleted result, skip and remove port
|
|
354
|
-
if (action === 'remove' || !result) {
|
|
355
|
-
this.functionPorts.delete(appConfig?.name);
|
|
356
|
-
this.log();
|
|
357
|
-
return;
|
|
358
|
-
}
|
|
359
|
-
// Skip everything except functions
|
|
360
|
-
if (appConfig?.type !== ConfigType.FUNCTION) {
|
|
361
|
-
return;
|
|
362
|
-
}
|
|
363
|
-
try {
|
|
364
|
-
const fullPath = path.join(this.appPath, appConfig.filePath);
|
|
365
|
-
// Re-bundle the function
|
|
366
|
-
const { code } = await bundleFunction(fullPath);
|
|
367
|
-
// Update the bundled file (wrangler dev will auto-reload)
|
|
368
|
-
const bundledPath = path.join(this.tmpDir, `${appConfig.name}.js`);
|
|
369
|
-
await fs.promises.writeFile(bundledPath, code);
|
|
370
|
-
if (!this.functionPorts.has(appConfig.name)) {
|
|
371
|
-
this.log(`\nStarting function ${appConfig.name}...`);
|
|
372
|
-
await this.startFunctionServers([appConfig]);
|
|
373
|
-
}
|
|
374
|
-
else {
|
|
375
|
-
this.log(`\nUpdating function ${appConfig.name}...`);
|
|
376
|
-
}
|
|
377
|
-
await this.logFunction(appConfig);
|
|
378
|
-
}
|
|
379
|
-
catch (error) {
|
|
380
|
-
this.log(`${style.error('Error re-bundling function')} ${appConfig.name}: ${error.message}`);
|
|
381
|
-
}
|
|
382
|
-
this.log();
|
|
383
|
-
}
|
|
384
408
|
}
|
|
@@ -14,7 +14,7 @@ export default class AppFrontendDev extends PushAppCommand {
|
|
|
14
14
|
env: string;
|
|
15
15
|
};
|
|
16
16
|
static summary: string;
|
|
17
|
+
logAppLocalUrl(storeId: string, sessionId: string): void;
|
|
17
18
|
run(): Promise<void>;
|
|
18
19
|
private execFrontendProject;
|
|
19
|
-
logAppLocalUrl(storeId: string, sessionId: string): void;
|
|
20
20
|
}
|
|
@@ -33,6 +33,12 @@ export default class AppFrontendDev extends PushAppCommand {
|
|
|
33
33
|
env: 'test',
|
|
34
34
|
};
|
|
35
35
|
static summary = `Run a frontend app in dev mode from your local machine.`;
|
|
36
|
+
logAppLocalUrl(storeId, sessionId) {
|
|
37
|
+
const frontendAppRoute = this.app.type === 'storefront' && this.app.storefront?.external
|
|
38
|
+
? `/${this.app.private_id}`
|
|
39
|
+
: '';
|
|
40
|
+
this.log(`View it at ${style.link(`${this.localFrontendUrl(storeId, sessionId)}${frontendAppRoute}`)}\n`);
|
|
41
|
+
}
|
|
36
42
|
async run() {
|
|
37
43
|
const { flags } = await this.parse(AppFrontendDev);
|
|
38
44
|
const { port, 'proxy-port': proxyPort, 'app-dev': isAppDev } = flags;
|
|
@@ -63,21 +69,21 @@ export default class AppFrontendDev extends PushAppCommand {
|
|
|
63
69
|
let serverReadyDetected = false;
|
|
64
70
|
await this.execFrontend(projectType.devCommand.replace('${PORT}', String(serverPort)), (string) => {
|
|
65
71
|
// Framework-specific output handling and ready detection
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
/-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
if (
|
|
80
|
-
/✓
|
|
72
|
+
switch (projectType.slug) {
|
|
73
|
+
case 'nextjs': {
|
|
74
|
+
// 1. NOISE FILTER: Hide unwanted output
|
|
75
|
+
if (string.includes('Using vars defined in .dev.vars')) {
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
// 2. URL FILTER: Hide localhost/network URLs (confusing - users should use Swell tunnel)
|
|
79
|
+
if (/- local:\s+http:\/\//i.test(string) ||
|
|
80
|
+
/- network:\s+http:\/\//i.test(string) ||
|
|
81
|
+
/ready on http:\/\/localhost/i.test(string)) {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
// 3. READY DETECTION: Detect server ready, show our message
|
|
85
|
+
if (!serverReadyDetected &&
|
|
86
|
+
(/✓ ready in \d+/.test(string) || /✓ starting/.test(string))) {
|
|
81
87
|
serverReadyDetected = true;
|
|
82
88
|
this.log(`Started ${projectType.name} dev server on port ${serverPort}.\n`);
|
|
83
89
|
const sessionId = localConfig.getSessionId(currentStore);
|
|
@@ -86,38 +92,16 @@ export default class AppFrontendDev extends PushAppCommand {
|
|
|
86
92
|
this.watchForChanges();
|
|
87
93
|
}
|
|
88
94
|
}
|
|
95
|
+
break;
|
|
89
96
|
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
string.includes('┃ Network')) {
|
|
95
|
-
return false;
|
|
96
|
-
}
|
|
97
|
-
// 3. READY DETECTION: Detect server ready, show our message
|
|
98
|
-
if (!serverReadyDetected && /watching for file changes/i.test(string)) {
|
|
99
|
-
serverReadyDetected = true;
|
|
100
|
-
this.log(`Started ${projectType.name} dev server on port ${serverPort}.\n`);
|
|
101
|
-
const sessionId = localConfig.getSessionId(currentStore);
|
|
102
|
-
setTimeout(() => this.logAppLocalUrl(currentStore, sessionId), 100);
|
|
103
|
-
if (!isAppDev) {
|
|
104
|
-
this.watchForChanges();
|
|
97
|
+
case 'astro': {
|
|
98
|
+
// 2. URL FILTER: Hide localhost/network URLs (confusing - users should use Swell tunnel)
|
|
99
|
+
if (string.includes('┃ Local') || string.includes('┃ Network')) {
|
|
100
|
+
return false;
|
|
105
101
|
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
// 1. NOISE FILTER: Hide unwanted output
|
|
110
|
-
if (string.includes('press h + enter to show help')) {
|
|
111
|
-
return false;
|
|
112
|
-
}
|
|
113
|
-
// 2. URL FILTER: Hide localhost/network URLs (confusing - users should use Swell tunnel)
|
|
114
|
-
if (/➜\s+Local:\s+http:\/\//i.test(string)) {
|
|
115
|
-
return false;
|
|
116
|
-
}
|
|
117
|
-
// 3. READY DETECTION: Detect server ready, show our message
|
|
118
|
-
if (!serverReadyDetected) {
|
|
119
|
-
if (/Application bundle generation complete/i.test(string) ||
|
|
120
|
-
/Watch mode enabled/i.test(string)) {
|
|
102
|
+
// 3. READY DETECTION: Detect server ready, show our message
|
|
103
|
+
if (!serverReadyDetected &&
|
|
104
|
+
/watching for file changes/i.test(string)) {
|
|
121
105
|
serverReadyDetected = true;
|
|
122
106
|
this.log(`Started ${projectType.name} dev server on port ${serverPort}.\n`);
|
|
123
107
|
const sessionId = localConfig.getSessionId(currentStore);
|
|
@@ -126,46 +110,68 @@ export default class AppFrontendDev extends PushAppCommand {
|
|
|
126
110
|
this.watchForChanges();
|
|
127
111
|
}
|
|
128
112
|
}
|
|
113
|
+
break;
|
|
129
114
|
}
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
const sessionId = localConfig.getSessionId(currentStore);
|
|
139
|
-
setTimeout(() => this.logAppLocalUrl(currentStore, sessionId), 100);
|
|
140
|
-
if (!isAppDev) {
|
|
141
|
-
this.watchForChanges();
|
|
115
|
+
case 'angular': {
|
|
116
|
+
// 1. NOISE FILTER: Hide unwanted output
|
|
117
|
+
if (string.includes('press h + enter to show help')) {
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
// 2. URL FILTER: Hide localhost/network URLs (confusing - users should use Swell tunnel)
|
|
121
|
+
if (/➜\s+local:\s+http:\/\//i.test(string)) {
|
|
122
|
+
return false;
|
|
142
123
|
}
|
|
143
|
-
|
|
124
|
+
// 3. READY DETECTION: Detect server ready, show our message
|
|
125
|
+
if (!serverReadyDetected &&
|
|
126
|
+
(/application bundle generation complete/.test(string) ||
|
|
127
|
+
/watch mode enabled/.test(string))) {
|
|
128
|
+
serverReadyDetected = true;
|
|
129
|
+
this.log(`Started ${projectType.name} dev server on port ${serverPort}.\n`);
|
|
130
|
+
const sessionId = localConfig.getSessionId(currentStore);
|
|
131
|
+
setTimeout(() => this.logAppLocalUrl(currentStore, sessionId), 100);
|
|
132
|
+
if (!isAppDev) {
|
|
133
|
+
this.watchForChanges();
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
break;
|
|
144
137
|
}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
138
|
+
case 'hono': {
|
|
139
|
+
// 2. URL FILTER: Hide localhost URLs (confusing - users should use Swell tunnel)
|
|
140
|
+
// Note: For Hono, the ready signal IS the URL line, so we handle both in section 3
|
|
141
|
+
// 3. READY DETECTION: Detect server ready, show our message, hide the URL
|
|
142
|
+
if (!serverReadyDetected &&
|
|
143
|
+
/ready on http:\/\/localhost:\d+/i.test(string)) {
|
|
144
|
+
serverReadyDetected = true;
|
|
145
|
+
this.log(`Started ${projectType.name} dev server on port ${serverPort}.\n`);
|
|
146
|
+
const sessionId = localConfig.getSessionId(currentStore);
|
|
147
|
+
setTimeout(() => this.logAppLocalUrl(currentStore, sessionId), 100);
|
|
148
|
+
if (!isAppDev) {
|
|
149
|
+
this.watchForChanges();
|
|
150
|
+
}
|
|
151
|
+
return false; // Hide the "Ready on localhost" line
|
|
152
|
+
}
|
|
153
|
+
break;
|
|
151
154
|
}
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
155
|
+
case 'nuxt': {
|
|
156
|
+
// 2. URL FILTER: Hide localhost/network URLs (confusing - users should use Swell tunnel)
|
|
157
|
+
if (/➜ local:\s+http:\/\//i.test(string) ||
|
|
158
|
+
/➜ network:/i.test(string)) {
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
161
|
+
// 3. READY DETECTION: Detect server ready, show our message
|
|
162
|
+
if (!serverReadyDetected && /nuxt \d+\.\d+\.\d+/i.test(string)) {
|
|
163
|
+
serverReadyDetected = true;
|
|
164
|
+
this.log(`Started ${projectType.name} dev server on port ${serverPort}.\n`);
|
|
165
|
+
const sessionId = localConfig.getSessionId(currentStore);
|
|
166
|
+
setTimeout(() => this.logAppLocalUrl(currentStore, sessionId), 100);
|
|
167
|
+
if (!isAppDev) {
|
|
168
|
+
this.watchForChanges();
|
|
169
|
+
}
|
|
160
170
|
}
|
|
171
|
+
break;
|
|
161
172
|
}
|
|
173
|
+
// No default
|
|
162
174
|
}
|
|
163
175
|
});
|
|
164
176
|
}
|
|
165
|
-
logAppLocalUrl(storeId, sessionId) {
|
|
166
|
-
const frontendAppRoute = this.app.type === 'storefront' && this.app.storefront?.external
|
|
167
|
-
? `/${this.app.private_id}`
|
|
168
|
-
: '';
|
|
169
|
-
this.log(`View it at ${style.link(`${this.localFrontendUrl(storeId, sessionId)}${frontendAppRoute}`)}\n`);
|
|
170
|
-
}
|
|
171
177
|
}
|
|
@@ -2,8 +2,8 @@ import { confirm } from '@inquirer/prompts';
|
|
|
2
2
|
import { Flags } from '@oclif/core';
|
|
3
3
|
import ora from 'ora';
|
|
4
4
|
import * as semver from 'semver';
|
|
5
|
-
import config from '../../lib/config.js';
|
|
6
5
|
import { readRcFile } from '../../lib/app-config.js';
|
|
6
|
+
import config from '../../lib/config.js';
|
|
7
7
|
import { selectEnvironmentId, selectLoggedInStoreId, } from '../../lib/stores.js';
|
|
8
8
|
import style from '../../lib/style.js';
|
|
9
9
|
import { RemoteAppCommand } from '../../remote-app-command.js';
|
|
@@ -48,7 +48,7 @@ Without a store specified, install the app in the Live environment of the curren
|
|
|
48
48
|
if (versions.length === 0) {
|
|
49
49
|
this.error(`${version ? `Version ${version} not found` : 'No versions found'} for ${style.appConfigValue(this.app.name)} app. ${version
|
|
50
50
|
? ''
|
|
51
|
-
: `Create a version with the ${style.command('swell version')} command first.`}`);
|
|
51
|
+
: `Create a version with the ${style.command('swell app version')} command first.`}`);
|
|
52
52
|
}
|
|
53
53
|
const versionToInstall = version
|
|
54
54
|
? versions.find((ver) => ver.version === version)?.version
|
|
@@ -129,7 +129,7 @@ Without a store specified, install the app in the Live environment of the curren
|
|
|
129
129
|
const sourceApp = await readRcFile(this.swellRcPath());
|
|
130
130
|
sourceAppId = sourceApp?.id;
|
|
131
131
|
}
|
|
132
|
-
catch
|
|
132
|
+
catch {
|
|
133
133
|
// noop
|
|
134
134
|
}
|
|
135
135
|
const existingSourcedApps = sourceAppId
|