@swell/cli 2.1.0 → 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 +9 -9
- package/dist/commands/app/dev.js +158 -136
- 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/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 +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 -15
- 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/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/package.json +8 -1
- package/oclif.manifest.json +0 -1936
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { HttpMethod } from '../../lib/api.js';
|
|
2
|
+
import { SwellApiCommand } from '../../swell-api-command.js';
|
|
3
|
+
export default class ApiDelete extends SwellApiCommand {
|
|
4
|
+
static summary: string;
|
|
5
|
+
static description: string;
|
|
6
|
+
static args: {
|
|
7
|
+
path: import("@oclif/core/lib/interfaces/parser.js").Arg<string, Record<string, unknown>>;
|
|
8
|
+
};
|
|
9
|
+
static flags: {
|
|
10
|
+
live: import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
|
|
11
|
+
};
|
|
12
|
+
static examples: string[];
|
|
13
|
+
get method(): HttpMethod;
|
|
14
|
+
run(): Promise<void>;
|
|
15
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Args, Flags } from '@oclif/core';
|
|
2
|
+
import { HttpMethod } from '../../lib/api.js';
|
|
3
|
+
import { SwellApiCommand } from '../../swell-api-command.js';
|
|
4
|
+
export default class ApiDelete extends SwellApiCommand {
|
|
5
|
+
static summary = 'Send a DELETE request to the Swell API.';
|
|
6
|
+
static description = 'Remove existing resources from the Swell Backend API.';
|
|
7
|
+
static args = {
|
|
8
|
+
path: Args.string({
|
|
9
|
+
description: 'API endpoint path (must start with /)',
|
|
10
|
+
required: true,
|
|
11
|
+
}),
|
|
12
|
+
};
|
|
13
|
+
static flags = {
|
|
14
|
+
live: Flags.boolean({
|
|
15
|
+
description: 'Use live environment instead of test',
|
|
16
|
+
default: false,
|
|
17
|
+
}),
|
|
18
|
+
};
|
|
19
|
+
static examples = ['swell api delete /products/abc123'];
|
|
20
|
+
get method() {
|
|
21
|
+
return HttpMethod.DELETE;
|
|
22
|
+
}
|
|
23
|
+
async run() {
|
|
24
|
+
await this.request(ApiDelete);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { HttpMethod } from '../../lib/api.js';
|
|
2
|
+
import { SwellApiCommand } from '../../swell-api-command.js';
|
|
3
|
+
export default class ApiGet extends SwellApiCommand {
|
|
4
|
+
static summary: string;
|
|
5
|
+
static description: string;
|
|
6
|
+
static args: {
|
|
7
|
+
path: import("@oclif/core/lib/interfaces/parser.js").Arg<string, Record<string, unknown>>;
|
|
8
|
+
};
|
|
9
|
+
static flags: {
|
|
10
|
+
live: import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
|
|
11
|
+
};
|
|
12
|
+
static examples: string[];
|
|
13
|
+
get method(): HttpMethod;
|
|
14
|
+
run(): Promise<void>;
|
|
15
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { Args, Flags } from '@oclif/core';
|
|
2
|
+
import { HttpMethod } from '../../lib/api.js';
|
|
3
|
+
import { SwellApiCommand } from '../../swell-api-command.js';
|
|
4
|
+
export default class ApiGet extends SwellApiCommand {
|
|
5
|
+
static summary = 'Send a GET request to the Swell API.';
|
|
6
|
+
static description = `Retrieve data from the Swell Backend API.
|
|
7
|
+
The command performs a GET request to the specified endpoint path.`;
|
|
8
|
+
static args = {
|
|
9
|
+
path: Args.string({
|
|
10
|
+
description: 'API endpoint path (must start with /)',
|
|
11
|
+
required: true,
|
|
12
|
+
}),
|
|
13
|
+
};
|
|
14
|
+
static flags = {
|
|
15
|
+
live: Flags.boolean({
|
|
16
|
+
description: 'Use live environment instead of test',
|
|
17
|
+
default: false,
|
|
18
|
+
}),
|
|
19
|
+
};
|
|
20
|
+
static examples = [
|
|
21
|
+
'swell api get /products',
|
|
22
|
+
'swell api get "/products?limit=10&category=shoes"',
|
|
23
|
+
'swell api get /products/abc123',
|
|
24
|
+
];
|
|
25
|
+
get method() {
|
|
26
|
+
return HttpMethod.GET;
|
|
27
|
+
}
|
|
28
|
+
async run() {
|
|
29
|
+
await this.request(ApiGet);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class Api extends Command {
|
|
3
|
+
static summary = 'Send requests directly to the Swell Backend API.';
|
|
4
|
+
static description = `Run Swell API requests directly from the command line.
|
|
5
|
+
Supports GET, POST, PUT, and DELETE methods with optional test/live environments.`;
|
|
6
|
+
static examples = [
|
|
7
|
+
'swell api get /products',
|
|
8
|
+
'swell api post /products --body \'{"name":"New product"}\'',
|
|
9
|
+
'swell api put /products/abc123 --body ./update.json',
|
|
10
|
+
'swell api delete /products/abc123',
|
|
11
|
+
];
|
|
12
|
+
async run() {
|
|
13
|
+
this.log('Use "swell api get|post|put|delete" to interact with the Swell API.');
|
|
14
|
+
this.log('Run "swell api --help" for more information.');
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { HttpMethod } from '../../lib/api.js';
|
|
2
|
+
import { SwellApiCommand } from '../../swell-api-command.js';
|
|
3
|
+
export default class ApiPost extends SwellApiCommand {
|
|
4
|
+
static summary: string;
|
|
5
|
+
static description: string;
|
|
6
|
+
static args: {
|
|
7
|
+
path: import("@oclif/core/lib/interfaces/parser.js").Arg<string, Record<string, unknown>>;
|
|
8
|
+
};
|
|
9
|
+
static flags: {
|
|
10
|
+
body: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
|
|
11
|
+
live: import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
|
|
12
|
+
};
|
|
13
|
+
static examples: string[];
|
|
14
|
+
get method(): HttpMethod;
|
|
15
|
+
run(): Promise<void>;
|
|
16
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { Args, Flags } from '@oclif/core';
|
|
2
|
+
import { HttpMethod } from '../../lib/api.js';
|
|
3
|
+
import { SwellApiCommand } from '../../swell-api-command.js';
|
|
4
|
+
export default class ApiPost extends SwellApiCommand {
|
|
5
|
+
static summary = 'Send a POST request to the Swell API.';
|
|
6
|
+
static description = `Create new resources in the Swell Backend API.
|
|
7
|
+
You can provide a JSON body directly or specify a file path containing JSON data.`;
|
|
8
|
+
static args = {
|
|
9
|
+
path: Args.string({
|
|
10
|
+
description: 'API endpoint path (must start with /)',
|
|
11
|
+
required: true,
|
|
12
|
+
}),
|
|
13
|
+
};
|
|
14
|
+
static flags = {
|
|
15
|
+
body: Flags.string({
|
|
16
|
+
description: 'Request body (inline JSON or file path)',
|
|
17
|
+
}),
|
|
18
|
+
live: Flags.boolean({
|
|
19
|
+
description: 'Use live environment instead of test',
|
|
20
|
+
default: false,
|
|
21
|
+
}),
|
|
22
|
+
};
|
|
23
|
+
static examples = [
|
|
24
|
+
'swell api post /products --body \'{"name": "Product", "price": 100}\'',
|
|
25
|
+
'swell api post /products --body ./product.json',
|
|
26
|
+
];
|
|
27
|
+
get method() {
|
|
28
|
+
return HttpMethod.POST;
|
|
29
|
+
}
|
|
30
|
+
async run() {
|
|
31
|
+
await this.request(ApiPost);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { HttpMethod } from '../../lib/api.js';
|
|
2
|
+
import { SwellApiCommand } from '../../swell-api-command.js';
|
|
3
|
+
export default class ApiPut extends SwellApiCommand {
|
|
4
|
+
static summary: string;
|
|
5
|
+
static description: string;
|
|
6
|
+
static args: {
|
|
7
|
+
path: import("@oclif/core/lib/interfaces/parser.js").Arg<string, Record<string, unknown>>;
|
|
8
|
+
};
|
|
9
|
+
static flags: {
|
|
10
|
+
body: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
|
|
11
|
+
live: import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
|
|
12
|
+
};
|
|
13
|
+
static examples: string[];
|
|
14
|
+
get method(): HttpMethod;
|
|
15
|
+
run(): Promise<void>;
|
|
16
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { Args, Flags } from '@oclif/core';
|
|
2
|
+
import { HttpMethod } from '../../lib/api.js';
|
|
3
|
+
import { SwellApiCommand } from '../../swell-api-command.js';
|
|
4
|
+
export default class ApiPut extends SwellApiCommand {
|
|
5
|
+
static summary = 'Send a PUT request to the Swell API.';
|
|
6
|
+
static description = `Update existing resources in the Swell Backend API.
|
|
7
|
+
You can provide a JSON body directly or specify a file path containing JSON data.`;
|
|
8
|
+
static args = {
|
|
9
|
+
path: Args.string({
|
|
10
|
+
description: 'API endpoint path (must start with /)',
|
|
11
|
+
required: true,
|
|
12
|
+
}),
|
|
13
|
+
};
|
|
14
|
+
static flags = {
|
|
15
|
+
body: Flags.string({
|
|
16
|
+
description: 'Request body (inline JSON or file path)',
|
|
17
|
+
}),
|
|
18
|
+
live: Flags.boolean({
|
|
19
|
+
description: 'Use live environment instead of test',
|
|
20
|
+
default: false,
|
|
21
|
+
}),
|
|
22
|
+
};
|
|
23
|
+
static examples = [
|
|
24
|
+
'swell api put /products/{id} --body \'{"id": "abc123", "name": "Updated"}\'',
|
|
25
|
+
'swell api put /products/abc123 --body ./product.json',
|
|
26
|
+
];
|
|
27
|
+
get method() {
|
|
28
|
+
return HttpMethod.PUT;
|
|
29
|
+
}
|
|
30
|
+
async run() {
|
|
31
|
+
await this.request(ApiPut);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { PushAppCommand } from '../../push-app-command.js';
|
|
2
2
|
export default class AppDev extends PushAppCommand {
|
|
3
|
+
static delayOrientation: boolean;
|
|
3
4
|
static examples: string[];
|
|
4
5
|
static flags: {
|
|
5
6
|
'no-push': import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
|
|
@@ -8,23 +9,22 @@ export default class AppDev extends PushAppCommand {
|
|
|
8
9
|
'storefront-id': import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
|
|
9
10
|
'storefront-select': import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
|
|
10
11
|
};
|
|
11
|
-
static summary: string;
|
|
12
|
-
static delayOrientation: boolean;
|
|
13
12
|
static orientation: {
|
|
14
13
|
env: string;
|
|
15
14
|
};
|
|
16
|
-
|
|
17
|
-
functionPorts: Map<string, number>;
|
|
15
|
+
static summary: string;
|
|
18
16
|
functionErrors: Map<string, string>;
|
|
17
|
+
functionPorts: Map<string, number>;
|
|
18
|
+
tmpDir: string;
|
|
19
19
|
run(): Promise<void>;
|
|
20
|
-
private startAppFunctionServer;
|
|
21
20
|
runAppFrontendDevIfApplicable(frontendPort?: number): Promise<number | void>;
|
|
21
|
+
private createFunctionRouter;
|
|
22
|
+
private createTmpDirectory;
|
|
23
|
+
private generateWranglerConfig;
|
|
22
24
|
private getAppFunctions;
|
|
23
25
|
private logAllFunctions;
|
|
24
26
|
private logFunction;
|
|
25
|
-
private createTmpDirectory;
|
|
26
|
-
private startFunctionServers;
|
|
27
|
-
private generateWranglerConfig;
|
|
28
|
-
private createFunctionRouter;
|
|
29
27
|
private onChangeFunctionWatcher;
|
|
28
|
+
private startAppFunctionServer;
|
|
29
|
+
private startFunctionServers;
|
|
30
30
|
}
|
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();
|
|
@@ -150,7 +195,8 @@ export default class AppDev extends PushAppCommand {
|
|
|
150
195
|
if (config.route.headers) {
|
|
151
196
|
const headers = Object.entries(config.route.headers).map(([key, value]) => `${key}: ${value}`);
|
|
152
197
|
this.log(` Headers:`);
|
|
153
|
-
|
|
198
|
+
for (const header of headers)
|
|
199
|
+
this.log(` ${style.dim(header)}`);
|
|
154
200
|
}
|
|
155
201
|
if (config.route.cache?.timeout) {
|
|
156
202
|
this.log(` Cache timeout: ${style.dim(config.route.cache.timeout)}`);
|
|
@@ -159,6 +205,9 @@ export default class AppDev extends PushAppCommand {
|
|
|
159
205
|
this.log(` Public: ${style.dim('true')}`);
|
|
160
206
|
}
|
|
161
207
|
}
|
|
208
|
+
if (config?.extension) {
|
|
209
|
+
this.log(` Extension: ${style.dim(config.extension)}`);
|
|
210
|
+
}
|
|
162
211
|
if (config?.description) {
|
|
163
212
|
this.log(` Description: ${style.dim(config.description)}`);
|
|
164
213
|
}
|
|
@@ -167,15 +216,68 @@ export default class AppDev extends PushAppCommand {
|
|
|
167
216
|
this.log(` ${style.error('Error loading config:', error.message)}`);
|
|
168
217
|
}
|
|
169
218
|
}
|
|
170
|
-
async
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
219
|
+
async onChangeFunctionWatcher(appConfig, action, result) {
|
|
220
|
+
// If no result, skip
|
|
221
|
+
if (!result) {
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
// If deleted result, skip and remove port
|
|
225
|
+
if (action === 'remove' || !result) {
|
|
226
|
+
this.functionPorts.delete(appConfig?.name);
|
|
227
|
+
this.log();
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
// Skip everything except functions
|
|
231
|
+
if (appConfig?.type !== ConfigType.FUNCTION) {
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
try {
|
|
235
|
+
const fullPath = path.join(this.appPath, appConfig.filePath);
|
|
236
|
+
// Re-bundle the function
|
|
237
|
+
const { code } = await bundleFunction(fullPath);
|
|
238
|
+
// Update the bundled file (wrangler dev will auto-reload)
|
|
239
|
+
const bundledPath = path.join(this.tmpDir, `${appConfig.name}.js`);
|
|
240
|
+
await fs.promises.writeFile(bundledPath, code);
|
|
241
|
+
if (this.functionPorts.has(appConfig.name)) {
|
|
242
|
+
this.log(`\nUpdating function ${appConfig.name}...`);
|
|
243
|
+
}
|
|
244
|
+
else {
|
|
245
|
+
this.log(`\nStarting function ${appConfig.name}...`);
|
|
246
|
+
await this.startFunctionServers([appConfig]);
|
|
247
|
+
}
|
|
248
|
+
await this.logFunction(appConfig);
|
|
249
|
+
}
|
|
250
|
+
catch (error) {
|
|
251
|
+
this.log(`${style.error('Error re-bundling function')} ${appConfig.name}: ${error.message}`);
|
|
252
|
+
}
|
|
253
|
+
this.log();
|
|
254
|
+
}
|
|
255
|
+
async startAppFunctionServer(spinner, serverPort) {
|
|
256
|
+
// Get all functions in this app
|
|
257
|
+
const functions = await this.getAppFunctions();
|
|
258
|
+
if (functions.length === 0) {
|
|
259
|
+
spinner.stop();
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
// Create TMP directory for wrangler configs and bundled functions
|
|
263
|
+
await this.createTmpDirectory();
|
|
264
|
+
// Bundle functions and start wrangler dev servers for each
|
|
265
|
+
await this.startFunctionServers(functions);
|
|
266
|
+
// Start watching for function file changes
|
|
267
|
+
this.watchForChanges({
|
|
268
|
+
onChange: this.onChangeFunctionWatcher.bind(this),
|
|
269
|
+
});
|
|
270
|
+
// Create a routing server that proxies requests to function servers
|
|
271
|
+
await this.createFunctionRouter(serverPort);
|
|
272
|
+
spinner.succeed(`App function server running on port ${serverPort}\n`);
|
|
273
|
+
this.log(`${style.appConfigName(`Functions:`)}`);
|
|
274
|
+
await this.logAllFunctions(functions);
|
|
275
|
+
this.log(`${style.success('→')} Call functions at: http://localhost:${serverPort}/<function-name>\n`);
|
|
176
276
|
}
|
|
177
277
|
async startFunctionServers(functions) {
|
|
178
278
|
const functionStatus = new Map();
|
|
279
|
+
const allocatedInspectorPorts = new Set();
|
|
280
|
+
/* eslint-disable no-await-in-loop */
|
|
179
281
|
for (const func of functions) {
|
|
180
282
|
const functionName = func.name;
|
|
181
283
|
if (this.functionPorts.has(functionName)) {
|
|
@@ -183,6 +285,11 @@ export default class AppDev extends PushAppCommand {
|
|
|
183
285
|
continue;
|
|
184
286
|
}
|
|
185
287
|
const functionPort = await getPort({ port: portNumbers(9000, 9100) });
|
|
288
|
+
const inspectorPort = await getPort({
|
|
289
|
+
port: portNumbers(9229, 9329),
|
|
290
|
+
exclude: [...allocatedInspectorPorts],
|
|
291
|
+
});
|
|
292
|
+
allocatedInspectorPorts.add(inspectorPort);
|
|
186
293
|
try {
|
|
187
294
|
// Bundle the function
|
|
188
295
|
const fullPath = path.join(this.appPath, func.filePath);
|
|
@@ -201,15 +308,16 @@ export default class AppDev extends PushAppCommand {
|
|
|
201
308
|
'dev',
|
|
202
309
|
`--config=${configPath}`,
|
|
203
310
|
`--port=${functionPort}`,
|
|
311
|
+
`--inspector-port=${inspectorPort}`,
|
|
204
312
|
], {
|
|
205
313
|
cwd: this.tmpDir,
|
|
206
|
-
//stdio: 'pipe', // Capture output for debugging
|
|
314
|
+
// stdio: 'pipe', // Capture output for debugging
|
|
207
315
|
detached: false,
|
|
208
316
|
});
|
|
209
317
|
const handleFunctionOutput = (data) => {
|
|
210
318
|
const output = data.toString();
|
|
211
319
|
// Remove ANSI escape sequences that cause line clearing
|
|
212
|
-
const cleanOutput = output.replace('\
|
|
320
|
+
const cleanOutput = output.replace('\u001B[2K\u001B[1A\u001B[2K\u001B[G', '');
|
|
213
321
|
const lines = cleanOutput
|
|
214
322
|
.split('\n')
|
|
215
323
|
.filter((line) => line.trim());
|
|
@@ -262,6 +370,12 @@ export default class AppDev extends PushAppCommand {
|
|
|
262
370
|
functionStatus.set(functionName, 'error');
|
|
263
371
|
this.functionErrors.set(functionName, error.message);
|
|
264
372
|
});
|
|
373
|
+
wranglerProcess.on('exit', (code, _signal) => {
|
|
374
|
+
if (functionStatus.get(functionName) === 'starting') {
|
|
375
|
+
functionStatus.set(functionName, 'error');
|
|
376
|
+
this.functionErrors.set(functionName, `Process exited with code ${code}`);
|
|
377
|
+
}
|
|
378
|
+
});
|
|
265
379
|
// Store the port for routing
|
|
266
380
|
this.functionPorts.set(functionName, functionPort);
|
|
267
381
|
}
|
|
@@ -270,13 +384,14 @@ export default class AppDev extends PushAppCommand {
|
|
|
270
384
|
this.functionErrors.set(functionName, error.message);
|
|
271
385
|
}
|
|
272
386
|
}
|
|
387
|
+
/* eslint-enable no-await-in-loop */
|
|
273
388
|
await new Promise((resolve) => {
|
|
274
|
-
if (
|
|
389
|
+
if (functions.length === 0) {
|
|
275
390
|
resolve();
|
|
276
391
|
return;
|
|
277
392
|
}
|
|
278
393
|
const checkAllRunning = () => {
|
|
279
|
-
const allRunning =
|
|
394
|
+
const allRunning = [...functionStatus.values()].every((status) => status !== 'starting');
|
|
280
395
|
if (allRunning) {
|
|
281
396
|
resolve();
|
|
282
397
|
}
|
|
@@ -288,97 +403,4 @@ export default class AppDev extends PushAppCommand {
|
|
|
288
403
|
});
|
|
289
404
|
return { functionStatus };
|
|
290
405
|
}
|
|
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
406
|
}
|