@swell/cli 2.9.21 → 2.9.23
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/post.js +1 -1
- package/dist/commands/app/dev.d.ts +1 -0
- package/dist/commands/app/dev.js +40 -2
- package/dist/commands/app/frontend/dev.d.ts +1 -0
- package/dist/commands/app/frontend/dev.js +11 -8
- package/dist/commands/app/push.d.ts +1 -0
- package/dist/commands/app/push.js +8 -0
- package/dist/commands/inspect/functions.d.ts +1 -0
- package/dist/commands/inspect/functions.js +4 -0
- package/dist/commands/schema.js +1 -1
- package/dist/lib/apps/app-config.d.ts +11 -0
- package/dist/lib/apps/app-config.js +38 -3
- package/dist/lib/apps/frontend-package.js +17 -8
- package/dist/lib/apps/frontend-templates.d.ts +1 -1
- package/dist/lib/apps/frontend-templates.js +1 -1
- package/dist/lib/bundle.d.ts +8 -0
- package/dist/lib/bundle.js +21 -0
- package/dist/lib/create/tests/templates/mock-request.js +9 -28
- package/dist/lib/create/tests/templates/setup-globals.js +2 -17
- package/dist/lib/create/tests/templates/unit-test.js +1 -1
- package/dist/lib/create/tests.js +4 -0
- package/dist/lib/function-source-analysis.js +1 -1
- package/dist/lib/swell-function-wrapper.js +3 -2
- package/dist/lib/workflows/operations.d.ts +3 -0
- package/dist/lib/workflows/operations.js +3 -0
- package/dist/push-app-command.d.ts +9 -0
- package/dist/push-app-command.js +73 -4
- package/dist/remote-app-command.d.ts +1 -0
- package/dist/remote-app-command.js +10 -6
- package/oclif.manifest.json +14 -2
- package/package.json +1 -1
|
@@ -31,7 +31,7 @@ export default class ApiPost extends SwellApiCommand {
|
|
|
31
31
|
'swell api post /products --body \'{"name": "Product", "price": 100}\'',
|
|
32
32
|
'swell api post /products --body ./product.json',
|
|
33
33
|
'swell api post /functions/my-app/create-order --body \'{"items":["sku-1"]}\'',
|
|
34
|
-
`swell api post /functions/my-app/webhook --body
|
|
34
|
+
`swell api post /functions/my-app/webhook --body ./payload.json -H 'X-Webhook-Event: order.created'`,
|
|
35
35
|
`swell api post /products --body '{"name": "Product"}' --api frontend`,
|
|
36
36
|
];
|
|
37
37
|
get method() {
|
|
@@ -6,6 +6,7 @@ export default class AppDev extends PushAppCommand {
|
|
|
6
6
|
static examples: string[];
|
|
7
7
|
static flags: {
|
|
8
8
|
'no-push': import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
|
|
9
|
+
'store-user': import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
|
|
9
10
|
port: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<number | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
|
|
10
11
|
'frontend-port': import("@oclif/core/lib/interfaces/parser.js").OptionFlag<number | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
|
|
11
12
|
function: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
|
package/dist/commands/app/dev.js
CHANGED
|
@@ -9,10 +9,30 @@ import * as path from 'node:path';
|
|
|
9
9
|
import ora from 'ora';
|
|
10
10
|
import { ConfigType, allConfigFilesInDir, appConfigFromFile, } from '../../lib/apps/index.js';
|
|
11
11
|
import { bundleFunction } from '../../lib/bundle.js';
|
|
12
|
+
import { getLocalFrontendHost } from '../../lib/constants.js';
|
|
12
13
|
import { analyzeFunctionSource, hasKindDiagnostic, } from '../../lib/function-source-analysis.js';
|
|
13
14
|
import { detectPackageManager, getExecSpawnArgs, } from '../../lib/package-manager.js';
|
|
14
15
|
import style from '../../lib/style.js';
|
|
15
16
|
import { PushAppCommand } from '../../push-app-command.js';
|
|
17
|
+
/**
|
|
18
|
+
* Headers that show the frontend dev server the public host and scheme, as
|
|
19
|
+
* managed hosting does. The tunnel replaces both with its own, so without them
|
|
20
|
+
* same-origin checks in the dev server reject the browser's requests.
|
|
21
|
+
*
|
|
22
|
+
* @param req - Request received from the tunnel
|
|
23
|
+
* @returns Header overrides, empty when the request did not come through Swell
|
|
24
|
+
*/
|
|
25
|
+
function publicRequestHeaders(req) {
|
|
26
|
+
const host = String(req.headers['x-forwarded-host'] ?? '')
|
|
27
|
+
.split(',')[0]
|
|
28
|
+
.trim();
|
|
29
|
+
if (!host) {
|
|
30
|
+
return {};
|
|
31
|
+
}
|
|
32
|
+
// The tunnel always reports https; the preview URL has the browser's scheme.
|
|
33
|
+
const { protocol } = new URL(getLocalFrontendHost('store', 'local'));
|
|
34
|
+
return { host, 'x-forwarded-proto': protocol.slice(0, -1) };
|
|
35
|
+
}
|
|
16
36
|
export default class AppDev extends PushAppCommand {
|
|
17
37
|
static delayOrientation = true;
|
|
18
38
|
static examples = [
|
|
@@ -26,6 +46,10 @@ export default class AppDev extends PushAppCommand {
|
|
|
26
46
|
'no-push': Flags.boolean({
|
|
27
47
|
description: 'skip pushing app files initially',
|
|
28
48
|
}),
|
|
49
|
+
'store-user': Flags.boolean({
|
|
50
|
+
default: false,
|
|
51
|
+
description: 'for dashboard apps, preview as your store user instead of a visitor',
|
|
52
|
+
}),
|
|
29
53
|
port: Flags.integer({
|
|
30
54
|
char: 'p',
|
|
31
55
|
description: 'override the default port to run the app locally',
|
|
@@ -76,6 +100,7 @@ export default class AppDev extends PushAppCommand {
|
|
|
76
100
|
const { port, 'frontend-port': frontendPort } = flags;
|
|
77
101
|
const noPush = flags['no-push'];
|
|
78
102
|
this.jsonEvents = flags['json-events'];
|
|
103
|
+
this.setStoreUser(flags['store-user']);
|
|
79
104
|
this.functionFilter = flags.function ?? null;
|
|
80
105
|
if (!(await this.ensureAppExists(undefined, false))) {
|
|
81
106
|
return;
|
|
@@ -98,6 +123,7 @@ export default class AppDev extends PushAppCommand {
|
|
|
98
123
|
this.emitLifecycleEvent('preview:proxy-ready', {
|
|
99
124
|
proxyPort: serverPort,
|
|
100
125
|
storefrontId: this.storefront?.id ?? null,
|
|
126
|
+
storeUser: this.storeUser,
|
|
101
127
|
});
|
|
102
128
|
await this.runAppFrontendDevIfApplicable(frontendPort);
|
|
103
129
|
}
|
|
@@ -178,7 +204,10 @@ export default class AppDev extends PushAppCommand {
|
|
|
178
204
|
}
|
|
179
205
|
else if (this.frontendPort) {
|
|
180
206
|
// Proxy non-function requests to frontend dev server
|
|
181
|
-
proxy.web(req, res, {
|
|
207
|
+
proxy.web(req, res, {
|
|
208
|
+
target: `http://localhost:${this.frontendPort}`,
|
|
209
|
+
headers: publicRequestHeaders(req),
|
|
210
|
+
}, (error) => fail(502, `Frontend dev server error: ${error.message}`));
|
|
182
211
|
}
|
|
183
212
|
else {
|
|
184
213
|
res.writeHead(404);
|
|
@@ -196,7 +225,10 @@ export default class AppDev extends PushAppCommand {
|
|
|
196
225
|
socket.destroy();
|
|
197
226
|
return;
|
|
198
227
|
}
|
|
199
|
-
proxy.ws(req, socket, head, {
|
|
228
|
+
proxy.ws(req, socket, head, {
|
|
229
|
+
target: `http://localhost:${this.frontendPort}`,
|
|
230
|
+
headers: publicRequestHeaders(req),
|
|
231
|
+
}, () => socket.destroy());
|
|
200
232
|
});
|
|
201
233
|
await new Promise((resolve, reject) => {
|
|
202
234
|
server.once('error', reject);
|
|
@@ -298,6 +330,9 @@ ENVIRONMENT = "development"
|
|
|
298
330
|
}
|
|
299
331
|
if (analysis.kind === 'workflow') {
|
|
300
332
|
this.log(` Workflow: deploys through ${style.command('swell app push')}`);
|
|
333
|
+
if (analysis.config?.action) {
|
|
334
|
+
this.log(` Trigger: Action (runs the pushed workflow)`);
|
|
335
|
+
}
|
|
301
336
|
return;
|
|
302
337
|
}
|
|
303
338
|
const { config } = await bundleFunction(fullPath);
|
|
@@ -312,6 +347,9 @@ ENVIRONMENT = "development"
|
|
|
312
347
|
this.log(` Trigger: Cron`);
|
|
313
348
|
this.log(` Schedule: ${style.dim(config.cron.schedule)}`);
|
|
314
349
|
}
|
|
350
|
+
else if (config?.action) {
|
|
351
|
+
this.log(` Trigger: Action`);
|
|
352
|
+
}
|
|
315
353
|
else if (config?.route) {
|
|
316
354
|
this.log(` Trigger: Route`);
|
|
317
355
|
if (config.route.methods?.length) {
|
|
@@ -8,6 +8,7 @@ export default class AppFrontendDev extends PushAppCommand {
|
|
|
8
8
|
'app-dev': import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
|
|
9
9
|
yes: import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
|
|
10
10
|
'no-push': import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
|
|
11
|
+
'store-user': import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
|
|
11
12
|
port: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<number | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
|
|
12
13
|
'frontend-port': import("@oclif/core/lib/interfaces/parser.js").OptionFlag<number | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
|
|
13
14
|
function: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Flags } from '@oclif/core';
|
|
2
|
+
import { stripVTControlCharacters } from 'node:util';
|
|
2
3
|
import { warnFrontendRouteCollisions } from '../../../lib/apps/frontend-routes.js';
|
|
3
4
|
import { CUSTOM_FRAMEWORK_SLUG, getManualDevHint, getProjectCommands, } from '../../../lib/apps/index.js';
|
|
4
5
|
import { PushAppCommand } from '../../../push-app-command.js';
|
|
@@ -40,6 +41,7 @@ export default class AppFrontendDev extends PushAppCommand {
|
|
|
40
41
|
const { flags } = await this.parse(AppFrontendDev);
|
|
41
42
|
const { port, 'proxy-port': proxyPort, 'app-dev': isAppDev } = flags;
|
|
42
43
|
const noPush = flags['no-push'];
|
|
44
|
+
this.setStoreUser(flags['store-user']);
|
|
43
45
|
if (!(await this.ensureAppExists(undefined, false))) {
|
|
44
46
|
return;
|
|
45
47
|
}
|
|
@@ -97,10 +99,7 @@ export default class AppFrontendDev extends PushAppCommand {
|
|
|
97
99
|
const { devCommand } = await getProjectCommands(this.appPath, projectType);
|
|
98
100
|
let serverReadyDetected = false;
|
|
99
101
|
await this.execFrontend(devCommand.replace('${PORT}', String(serverPort)), (string) => {
|
|
100
|
-
|
|
101
|
-
if (/press h \+ enter to show help/i.test(string)) {
|
|
102
|
-
return false;
|
|
103
|
-
}
|
|
102
|
+
const output = stripVTControlCharacters(string);
|
|
104
103
|
// Framework-specific output handling and ready detection
|
|
105
104
|
switch (projectType.slug) {
|
|
106
105
|
case 'nextjs': {
|
|
@@ -193,10 +192,11 @@ export default class AppFrontendDev extends PushAppCommand {
|
|
|
193
192
|
case 'react':
|
|
194
193
|
case 'react-storefront':
|
|
195
194
|
case 'vinext': {
|
|
196
|
-
//
|
|
197
|
-
//
|
|
198
|
-
if (
|
|
199
|
-
/➜\s+
|
|
195
|
+
// The ready banner can arrive without a URL. Detect readiness
|
|
196
|
+
// before filtering help text, which may share the same chunk.
|
|
197
|
+
if (/\bvite\s+v\S+\s+ready in\b/i.test(output) ||
|
|
198
|
+
/➜\s+local:\s+http:\/\//i.test(output) ||
|
|
199
|
+
/➜\s+network:/i.test(output)) {
|
|
200
200
|
if (!serverReadyDetected) {
|
|
201
201
|
serverReadyDetected = true;
|
|
202
202
|
this.log(`Started ${projectType.name} dev server on port ${serverPort}.\n`);
|
|
@@ -228,6 +228,9 @@ export default class AppFrontendDev extends PushAppCommand {
|
|
|
228
228
|
}
|
|
229
229
|
// No default
|
|
230
230
|
}
|
|
231
|
+
if (/press h \+ enter to show help/i.test(output)) {
|
|
232
|
+
return false;
|
|
233
|
+
}
|
|
231
234
|
});
|
|
232
235
|
}
|
|
233
236
|
}
|
|
@@ -153,6 +153,7 @@ ${Object.values(AllConfigPaths)
|
|
|
153
153
|
}
|
|
154
154
|
this.log();
|
|
155
155
|
this.logDashboardUrl(flags);
|
|
156
|
+
this.exitIfFilesFailed();
|
|
156
157
|
return;
|
|
157
158
|
}
|
|
158
159
|
}
|
|
@@ -162,5 +163,12 @@ ${Object.values(AllConfigPaths)
|
|
|
162
163
|
this.log();
|
|
163
164
|
}
|
|
164
165
|
this.logDashboardUrl(flags);
|
|
166
|
+
this.exitIfFilesFailed();
|
|
167
|
+
}
|
|
168
|
+
// A skipped file is a failed push, for scripts and CI too
|
|
169
|
+
exitIfFilesFailed() {
|
|
170
|
+
if (this.failedFiles.length > 0) {
|
|
171
|
+
this.error(`${this.failedFiles.length} file(s) failed to push: ${this.failedFiles.join(', ')}`);
|
|
172
|
+
}
|
|
165
173
|
}
|
|
166
174
|
}
|
|
@@ -7,6 +7,10 @@ function deriveTrigger(r) {
|
|
|
7
7
|
const n = r.model.events.length;
|
|
8
8
|
return `model (${n} event${n === 1 ? '' : 's'})`;
|
|
9
9
|
}
|
|
10
|
+
// Checked before route: an installed function can also carry `route: {}`.
|
|
11
|
+
if (r.action) {
|
|
12
|
+
return 'action';
|
|
13
|
+
}
|
|
10
14
|
if (r.route) {
|
|
11
15
|
const { methods } = r.route;
|
|
12
16
|
if (!methods?.length)
|
package/dist/commands/schema.js
CHANGED
|
@@ -262,7 +262,7 @@ export default class Schema extends SwellCommand {
|
|
|
262
262
|
// Validate trigger exclusivity
|
|
263
263
|
const triggers = getFunctionTriggers(config);
|
|
264
264
|
if (triggers.length === 0) {
|
|
265
|
-
throw new Error('Invalid function config:\n• Config must specify one of: route, model, cron');
|
|
265
|
+
throw new Error('Invalid function config:\n• Config must specify one of: route, model, cron, action');
|
|
266
266
|
}
|
|
267
267
|
if (triggers.length > 1) {
|
|
268
268
|
throw new Error(`Invalid function config:\n• Multiple triggers specified: ${triggers.join(', ')}. Use exactly one.`);
|
|
@@ -45,6 +45,16 @@ export declare abstract class AppConfig {
|
|
|
45
45
|
version?: string;
|
|
46
46
|
constructor(attrs?: Partial<AppConfig>);
|
|
47
47
|
load(): Promise<void>;
|
|
48
|
+
/**
|
|
49
|
+
* The hash push compares with the pushed config to skip unchanged files
|
|
50
|
+
* @returns {Promise<string | undefined>} the hash
|
|
51
|
+
*/
|
|
52
|
+
protected contentHash(): Promise<string | undefined>;
|
|
53
|
+
/**
|
|
54
|
+
* Full path of the app directory this config belongs to
|
|
55
|
+
* @returns {string} the path
|
|
56
|
+
*/
|
|
57
|
+
protected appRoot(): string;
|
|
48
58
|
static create(attrs?: Partial<AppConfig>): Promise<AppConfig>;
|
|
49
59
|
isRootConfig(configDir: string): boolean;
|
|
50
60
|
postData(): Promise<any>;
|
|
@@ -88,6 +98,7 @@ export declare class AppConfigFunction extends AppConfig {
|
|
|
88
98
|
hasValues: boolean;
|
|
89
99
|
type: ConfigType;
|
|
90
100
|
isRootFunction(): boolean;
|
|
101
|
+
protected contentHash(): Promise<string | undefined>;
|
|
91
102
|
preparePostData(postData: any): Promise<any>;
|
|
92
103
|
}
|
|
93
104
|
export declare class AppConfigComponent extends AppConfig {
|
|
@@ -3,9 +3,9 @@ import isEmpty from 'lodash/isEmpty.js';
|
|
|
3
3
|
import { detectFilenameMime } from 'mime-detect';
|
|
4
4
|
import * as fs from 'node:fs/promises';
|
|
5
5
|
import * as path from 'node:path';
|
|
6
|
-
import { bundleFunction, bundleComponent, bundleWorkflow } from '../bundle.js';
|
|
6
|
+
import { bundleFunction, bundleComponent, bundleWorkflow, getLocalInputs, } from '../bundle.js';
|
|
7
7
|
import { analyzeFunctionSource, hasKindDiagnostic, workflowStaticConfigError, } from '../function-source-analysis.js';
|
|
8
|
-
import { AllConfigPaths, ConfigType, filePathExistsAsync, hashFile, } from './index.js';
|
|
8
|
+
import { AllConfigPaths, ConfigType, filePathExistsAsync, hashFile, hashString, } from './index.js';
|
|
9
9
|
export class IgnoringFileError extends Error {
|
|
10
10
|
constructor(message) {
|
|
11
11
|
super(message);
|
|
@@ -83,11 +83,25 @@ export class AppConfig {
|
|
|
83
83
|
if (await filePathExistsAsync(this.appPath)) {
|
|
84
84
|
this.fileData = await fs.readFile(this.appPath);
|
|
85
85
|
this.fileHash = hashFile(this.appPath, this.fileData, this.filePath);
|
|
86
|
-
this.hash = this.hash || this.
|
|
86
|
+
this.hash = this.hash || (await this.contentHash());
|
|
87
87
|
this.byteSize = this.fileData.length;
|
|
88
88
|
this.mbSize = this.byteSize / 1024 / 1024;
|
|
89
89
|
}
|
|
90
90
|
}
|
|
91
|
+
/**
|
|
92
|
+
* The hash push compares with the pushed config to skip unchanged files
|
|
93
|
+
* @returns {Promise<string | undefined>} the hash
|
|
94
|
+
*/
|
|
95
|
+
async contentHash() {
|
|
96
|
+
return this.fileHash;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Full path of the app directory this config belongs to
|
|
100
|
+
* @returns {string} the path
|
|
101
|
+
*/
|
|
102
|
+
appRoot() {
|
|
103
|
+
return path.resolve(this.appPath, ...this.filePath.split('/').map(() => '..'));
|
|
104
|
+
}
|
|
91
105
|
static async create(attrs = {}) {
|
|
92
106
|
let instance;
|
|
93
107
|
switch (attrs.type) {
|
|
@@ -263,6 +277,27 @@ export class AppConfigFunction extends AppConfig {
|
|
|
263
277
|
return (this.isRootConfig('functions') &&
|
|
264
278
|
(this.filePath.endsWith('.js') || this.filePath.endsWith('.ts')));
|
|
265
279
|
}
|
|
280
|
+
// A function deploys as a bundle, so the hash includes the local files it
|
|
281
|
+
// imports: a change in shared code under functions/lib redeploys it
|
|
282
|
+
async contentHash() {
|
|
283
|
+
if (!this.isRootFunction() || !this.fileHash) {
|
|
284
|
+
return this.fileHash;
|
|
285
|
+
}
|
|
286
|
+
const appRoot = this.appRoot();
|
|
287
|
+
let inputs;
|
|
288
|
+
try {
|
|
289
|
+
inputs = await getLocalInputs(this.appPath, appRoot);
|
|
290
|
+
}
|
|
291
|
+
catch {
|
|
292
|
+
// Unknown until it compiles: push tries it and reports the error
|
|
293
|
+
return undefined;
|
|
294
|
+
}
|
|
295
|
+
const imports = inputs
|
|
296
|
+
.filter((input) => input !== this.filePath)
|
|
297
|
+
.sort()
|
|
298
|
+
.map((input) => hashFile(path.join(appRoot, input), undefined, input));
|
|
299
|
+
return hashString(this.fileHash, ...imports);
|
|
300
|
+
}
|
|
266
301
|
async preparePostData(postData) {
|
|
267
302
|
if (this.isRootFunction()) {
|
|
268
303
|
try {
|
|
@@ -3,7 +3,7 @@ import { execa } from 'execa';
|
|
|
3
3
|
import { isIgnoredByIgnoreFiles } from 'globby';
|
|
4
4
|
import { createHash } from 'node:crypto';
|
|
5
5
|
import * as fs from 'node:fs/promises';
|
|
6
|
-
import { createRequire } from 'node:module';
|
|
6
|
+
import { createRequire, isBuiltin } from 'node:module';
|
|
7
7
|
import * as os from 'node:os';
|
|
8
8
|
import * as path from 'node:path';
|
|
9
9
|
const sha256 = (bytes) => createHash('sha256').update(bytes).digest('hex');
|
|
@@ -155,13 +155,25 @@ export async function checkModuleGraph(pkg) {
|
|
|
155
155
|
name: 'retained-frontend-modules',
|
|
156
156
|
setup(builder) {
|
|
157
157
|
builder.onResolve({ filter: /.*/ }, (args) => {
|
|
158
|
-
|
|
158
|
+
// The provider validates runtime module names at upload, so a
|
|
159
|
+
// scheme is never checked against the Node version running here.
|
|
160
|
+
if (args.kind !== 'entry-point' &&
|
|
161
|
+
(/^(?:node|cloudflare):/.test(args.path) || isBuiltin(args.path)))
|
|
159
162
|
return { path: args.path, external: true };
|
|
163
|
+
// Like the runtime, resolve any other name, bare or relative,
|
|
164
|
+
// against the importer's directory.
|
|
160
165
|
const name = args.kind === 'entry-point'
|
|
161
166
|
? args.path
|
|
162
167
|
: path.posix.join(path.posix.dirname(args.importer), args.path);
|
|
163
|
-
if (!modules.has(name))
|
|
164
|
-
|
|
168
|
+
if (!modules.has(name)) {
|
|
169
|
+
const from = args.importer
|
|
170
|
+
? ` (imported as "${args.path}" from "${args.importer}")`
|
|
171
|
+
: '';
|
|
172
|
+
const hint = args.importer && /^[\w@]/.test(args.path)
|
|
173
|
+
? '. npm dependencies must be bundled.'
|
|
174
|
+
: '';
|
|
175
|
+
throw new Error(`Missing frontend Worker module: ${name}${from}${hint}`);
|
|
176
|
+
}
|
|
165
177
|
return { path: name, namespace: 'frontend' };
|
|
166
178
|
});
|
|
167
179
|
builder.onLoad({ filter: /.*/, namespace: 'frontend' }, (args) => {
|
|
@@ -289,10 +301,7 @@ export async function captureFrontendPackage(frontend, framework) {
|
|
|
289
301
|
Buffer.from(await part.arrayBuffer())));
|
|
290
302
|
}
|
|
291
303
|
const bindings = (metadata.bindings ?? []).map((binding) => {
|
|
292
|
-
if (
|
|
293
|
-
(framework === 'vinext' &&
|
|
294
|
-
binding.type === 'version_metadata' &&
|
|
295
|
-
binding.name === 'CF_VERSION_METADATA')) {
|
|
304
|
+
if (binding.type === 'assets' && binding.name === 'ASSETS') {
|
|
296
305
|
return binding.name;
|
|
297
306
|
}
|
|
298
307
|
throw new Error(`Unsupported managed frontend binding: ${binding.name}`);
|
|
@@ -4,7 +4,7 @@ export declare const FRONTEND_HOSTING_LABELS: {
|
|
|
4
4
|
'self-hosted': string;
|
|
5
5
|
};
|
|
6
6
|
export declare const C3_VERSION = "2.72.7";
|
|
7
|
-
export declare const FRONTEND_TEMPLATE_REF = "v0.
|
|
7
|
+
export declare const FRONTEND_TEMPLATE_REF = "v0.2";
|
|
8
8
|
export interface FrontendTemplate {
|
|
9
9
|
/** `--frontend` value and picker value. */
|
|
10
10
|
slug: string;
|
|
@@ -9,7 +9,7 @@ export const C3_VERSION = '2.72.7';
|
|
|
9
9
|
// template hotfixes need no CLI release; a breaking template change gets a new
|
|
10
10
|
// minor tag and a CLI release that points at it. degit resolves only branch
|
|
11
11
|
// and tag tips, never arbitrary commits.
|
|
12
|
-
export const FRONTEND_TEMPLATE_REF = 'v0.
|
|
12
|
+
export const FRONTEND_TEMPLATE_REF = 'v0.2';
|
|
13
13
|
// npx pins the generator even on Yarn Classic. The caller passes the selected
|
|
14
14
|
// manager's user agent for C3's dependency edits.
|
|
15
15
|
const managedInstallCommand = (directory) => `npx --yes create-cloudflare@${C3_VERSION} frontend --template=swellstores/swell-app-templates/${directory}#${FRONTEND_TEMPLATE_REF} --deploy=false --git=false --no-agents --no-auto-update --lang=ts`;
|
package/dist/lib/bundle.d.ts
CHANGED
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
import type { FunctionSourceAnalysis } from './function-source-analysis.js';
|
|
2
|
+
/**
|
|
3
|
+
* Local source files an entry point imports, itself included, relative to
|
|
4
|
+
* rootDir. Packages are not followed: only the app's own files are listed.
|
|
5
|
+
* @param {string} entryPoint path of the entry point, relative to rootDir
|
|
6
|
+
* @param {string} rootDir directory the paths are relative to
|
|
7
|
+
* @returns {Promise<string[]>} paths of the imported files
|
|
8
|
+
*/
|
|
9
|
+
export declare function getLocalInputs(entryPoint: string, rootDir: string): Promise<string[]>;
|
|
2
10
|
export declare function bundleFunction(filePath: string): Promise<any>;
|
|
3
11
|
export declare function bundleWorkflow(filePath: string, analysis: FunctionSourceAnalysis): Promise<any>;
|
|
4
12
|
export declare function bundleComponent(filePath: string): Promise<any>;
|
package/dist/lib/bundle.js
CHANGED
|
@@ -2,6 +2,27 @@ import * as esbuild from 'esbuild';
|
|
|
2
2
|
import * as fs from 'node:fs/promises';
|
|
3
3
|
import * as path from 'node:path';
|
|
4
4
|
import { fileURLToPath } from 'node:url';
|
|
5
|
+
/**
|
|
6
|
+
* Local source files an entry point imports, itself included, relative to
|
|
7
|
+
* rootDir. Packages are not followed: only the app's own files are listed.
|
|
8
|
+
* @param {string} entryPoint path of the entry point, relative to rootDir
|
|
9
|
+
* @param {string} rootDir directory the paths are relative to
|
|
10
|
+
* @returns {Promise<string[]>} paths of the imported files
|
|
11
|
+
*/
|
|
12
|
+
export async function getLocalInputs(entryPoint, rootDir) {
|
|
13
|
+
const { metafile } = await esbuild.build({
|
|
14
|
+
absWorkingDir: rootDir,
|
|
15
|
+
bundle: true,
|
|
16
|
+
entryPoints: [entryPoint],
|
|
17
|
+
external: ['cloudflare:*'],
|
|
18
|
+
logLevel: 'silent',
|
|
19
|
+
metafile: true,
|
|
20
|
+
packages: 'external',
|
|
21
|
+
platform: 'node',
|
|
22
|
+
write: false,
|
|
23
|
+
});
|
|
24
|
+
return Object.keys(metafile.inputs);
|
|
25
|
+
}
|
|
5
26
|
export async function bundleFunction(filePath) {
|
|
6
27
|
try {
|
|
7
28
|
const buildResult = await esbuild.build({
|
|
@@ -46,6 +46,7 @@ export function createMockRequest(options: MockRequestOptions = {}): SwellReques
|
|
|
46
46
|
} = options;
|
|
47
47
|
|
|
48
48
|
const requestHeaders = new Headers(headers);
|
|
49
|
+
const requestQuery = { ...Object.fromEntries(new URL(url).searchParams), ...query };
|
|
49
50
|
const originalRequest = new Request(url, {
|
|
50
51
|
method,
|
|
51
52
|
headers: requestHeaders,
|
|
@@ -88,40 +89,20 @@ export function createMockRequest(options: MockRequestOptions = {}): SwellReques
|
|
|
88
89
|
apiHost: env.SWELL_API_BASE_URL || "",
|
|
89
90
|
logParams: undefined,
|
|
90
91
|
swell: swellClient as SwellAPI,
|
|
91
|
-
|
|
92
|
-
data,
|
|
93
|
-
|
|
92
|
+
rawBody: method === "GET" ? "" : JSON.stringify(data),
|
|
93
|
+
body: method === "GET" ? "" : { ...data },
|
|
94
|
+
data: { ...(method === "GET" ? {} : data), ...requestQuery },
|
|
95
|
+
query: requestQuery,
|
|
94
96
|
initialize: async () => {},
|
|
95
|
-
parseJson: (input: string) =>
|
|
97
|
+
parseJson: (input: string) => {
|
|
98
|
+
try { return JSON.parse(input); } catch { return {}; }
|
|
99
|
+
},
|
|
96
100
|
reject: (
|
|
97
101
|
code: string,
|
|
98
102
|
message: string,
|
|
99
103
|
options: { status?: number } = {}
|
|
100
104
|
) => {
|
|
101
|
-
|
|
102
|
-
typeof options.status === "number" &&
|
|
103
|
-
options.status >= 400 &&
|
|
104
|
-
options.status < 500
|
|
105
|
-
? options.status
|
|
106
|
-
: 422;
|
|
107
|
-
const error = new Error(
|
|
108
|
-
message || "Request rejected by function"
|
|
109
|
-
) as Error & {
|
|
110
|
-
body: { $reject: { code: string; message: string; status: number } };
|
|
111
|
-
code: string;
|
|
112
|
-
status: number;
|
|
113
|
-
};
|
|
114
|
-
error.name = "SwellRejection";
|
|
115
|
-
error.code = code;
|
|
116
|
-
error.status = status;
|
|
117
|
-
error.body = {
|
|
118
|
-
$reject: {
|
|
119
|
-
code,
|
|
120
|
-
message: error.message,
|
|
121
|
-
status,
|
|
122
|
-
},
|
|
123
|
-
};
|
|
124
|
-
return error;
|
|
105
|
+
return new SwellRejection(code, message, options);
|
|
125
106
|
},
|
|
126
107
|
appValues: (idOrValues: string | SwellData, values?: SwellData) => {
|
|
127
108
|
const targetAppId =
|
|
@@ -1,21 +1,6 @@
|
|
|
1
1
|
export function setupGlobalsTemplate() {
|
|
2
2
|
return `\
|
|
3
|
-
|
|
4
|
-
status: number;
|
|
5
|
-
body?: unknown;
|
|
6
|
-
|
|
7
|
-
constructor(message: string | object, options: { status?: number } = {}) {
|
|
8
|
-
const text =
|
|
9
|
-
typeof message === "string"
|
|
10
|
-
? message
|
|
11
|
-
: JSON.stringify(message, null, 2);
|
|
12
|
-
|
|
13
|
-
super(text);
|
|
14
|
-
this.name = "SwellError";
|
|
15
|
-
this.status = options.status ?? 500;
|
|
16
|
-
this.body = typeof message === "string" ? undefined : message;
|
|
17
|
-
}
|
|
18
|
-
}
|
|
3
|
+
import { SwellError } from "@swell/apps-sdk";
|
|
19
4
|
|
|
20
5
|
class SwellRejectionImpl extends Error {
|
|
21
6
|
status: number;
|
|
@@ -51,7 +36,7 @@ class SwellRejectionImpl extends Error {
|
|
|
51
36
|
}
|
|
52
37
|
|
|
53
38
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
54
|
-
(globalThis as any).SwellError =
|
|
39
|
+
(globalThis as any).SwellError = SwellError;
|
|
55
40
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
56
41
|
(globalThis as any).SwellRejection = SwellRejectionImpl;
|
|
57
42
|
|
package/dist/lib/create/tests.js
CHANGED
|
@@ -7,6 +7,7 @@ const TEST_DEV_DEPENDENCIES = {
|
|
|
7
7
|
'@cloudflare/vitest-pool-workers': '0.12.x',
|
|
8
8
|
vitest: '3.2.x',
|
|
9
9
|
'@swell/app-types': 'latest',
|
|
10
|
+
'@swell/apps-sdk': '2.0.0-alpha.0',
|
|
10
11
|
};
|
|
11
12
|
async function writeTextFile(filePath, contents, overwrite) {
|
|
12
13
|
if (!overwrite && (await filePathExistsAsync(filePath))) {
|
|
@@ -52,6 +53,9 @@ async function updatePackageJson(appPath) {
|
|
|
52
53
|
if (!devDeps['@swell/app-types'] && !deps['@swell/app-types']) {
|
|
53
54
|
devDeps['@swell/app-types'] = TEST_DEV_DEPENDENCIES['@swell/app-types'];
|
|
54
55
|
}
|
|
56
|
+
if (!devDeps['@swell/apps-sdk'] && !deps['@swell/apps-sdk']) {
|
|
57
|
+
devDeps['@swell/apps-sdk'] = TEST_DEV_DEPENDENCIES['@swell/apps-sdk'];
|
|
58
|
+
}
|
|
55
59
|
pkg.devDependencies = devDeps;
|
|
56
60
|
const scripts = (pkg.scripts || {});
|
|
57
61
|
scripts.test = scripts.test || 'vitest run';
|
|
@@ -2,7 +2,7 @@ import * as fs from 'node:fs/promises';
|
|
|
2
2
|
import { createRequire } from 'node:module';
|
|
3
3
|
const require = createRequire(import.meta.url);
|
|
4
4
|
const ts = require('typescript');
|
|
5
|
-
const ORDINARY_TRIGGER_KEYS = ['route', 'model', 'cron'];
|
|
5
|
+
const ORDINARY_TRIGGER_KEYS = ['route', 'model', 'cron', 'action'];
|
|
6
6
|
const WORKFLOW_FORBIDDEN_KEYS = [
|
|
7
7
|
'route',
|
|
8
8
|
'model',
|
|
@@ -111,8 +111,9 @@ class SwellRequest {
|
|
|
111
111
|
this.swell = new SwellAPI(this, context);
|
|
112
112
|
// URL of the original request
|
|
113
113
|
this.url;
|
|
114
|
-
//
|
|
115
|
-
//
|
|
114
|
+
// Request body text. On a route called through the storefront gateway with
|
|
115
|
+
// a JSON body, this is the exact text the caller sent, so verify HMAC/webhook
|
|
116
|
+
// signatures against it. Other bodies arrive re-serialized as JSON.
|
|
116
117
|
this.rawBody = '';
|
|
117
118
|
// Parsed JSON body as object, or raw text string when body isn't JSON.
|
|
118
119
|
this.body = {};
|
|
@@ -113,6 +113,9 @@ export function workflowRunMeta(run) {
|
|
|
113
113
|
if (run.status) {
|
|
114
114
|
parts.push(run.status);
|
|
115
115
|
}
|
|
116
|
+
if (run.trigger === 'action') {
|
|
117
|
+
parts.push(run.action?.id ? `action (${run.action.id})` : 'action');
|
|
118
|
+
}
|
|
116
119
|
const date = workflowRunDate(run);
|
|
117
120
|
if (date) {
|
|
118
121
|
parts.push(formatWorkflowRelativeTime(date));
|
|
@@ -13,6 +13,7 @@ interface WatchingChange {
|
|
|
13
13
|
export declare abstract class PushAppCommand extends RemoteAppCommand {
|
|
14
14
|
frontendPath: string;
|
|
15
15
|
hasLoggedLocalPreviewUrl: boolean;
|
|
16
|
+
storeUser: boolean;
|
|
16
17
|
logWatchChanges: boolean;
|
|
17
18
|
onWatchChange?: (appConfig?: AppConfig, action?: string, result?: any) => void;
|
|
18
19
|
watchingChangeQueue: Map<string, WatchingChange>;
|
|
@@ -48,6 +49,13 @@ export declare abstract class PushAppCommand extends RemoteAppCommand {
|
|
|
48
49
|
}, shouldChoose?: boolean): Promise<App | undefined>;
|
|
49
50
|
getExistingApp(appId: string): Promise<App>;
|
|
50
51
|
getFrontendDeploymentHash(): Promise<string | undefined>;
|
|
52
|
+
/**
|
|
53
|
+
* Files outside frontend/ that the worker's entry point imports, relative to
|
|
54
|
+
* the app path. Only a local entry point set by `main` in the wrangler
|
|
55
|
+
* config is followed; frameworks that build their own entry are not.
|
|
56
|
+
* @returns {Promise<string[]>} paths of the imported files
|
|
57
|
+
*/
|
|
58
|
+
getFrontendSharedInputs(): Promise<string[]>;
|
|
51
59
|
getFrontendProjectType(required?: boolean): Promise<FrontendProjectType | null>;
|
|
52
60
|
getStorefrontToPull(args?: {
|
|
53
61
|
appId?: string;
|
|
@@ -83,6 +91,7 @@ export declare abstract class PushAppCommand extends RemoteAppCommand {
|
|
|
83
91
|
runWatchChangeQueue(): Promise<void>;
|
|
84
92
|
saveCurrentStorefront(): void;
|
|
85
93
|
logLocalPreviewUrl(): void;
|
|
94
|
+
setStoreUser(storeUser: boolean): void;
|
|
86
95
|
setWatchingFiles(): Promise<void>;
|
|
87
96
|
startProxyServer(port?: number): Promise<number>;
|
|
88
97
|
updateFrontendDeployment(currentStore: string, projectType: FrontendProjectType, deploymentUrl: string, deploymentHash: string): Promise<void>;
|
package/dist/push-app-command.js
CHANGED
|
@@ -8,9 +8,10 @@ import ora from 'ora';
|
|
|
8
8
|
import { default as swellConfig } from './lib/app-config.js';
|
|
9
9
|
import { buildFrontendPackage, getManagedFrontendFramework, } from './lib/apps/frontend-package.js';
|
|
10
10
|
import { warnFrontendRouteCollisions } from './lib/apps/frontend-routes.js';
|
|
11
|
-
import { ConfigType, getFrontendProjectValidValues, allConfigFilesInDir, CUSTOM_FRAMEWORK_SLUG, appConfigFromFile, filePathExistsAsync, findAppConfig, getConfigTypeFromPath, getConfigTypeKeyFromValue, getFrontendProjectType, getProjectCommands, globAllFilesByPath, hashString, isPathDirectory, toPosixPath, } from './lib/apps/index.js';
|
|
11
|
+
import { ConfigType, getFrontendProjectValidValues, allConfigFilesInDir, CUSTOM_FRAMEWORK_SLUG, appConfigFromFile, filePathExistsAsync, findAppConfig, getConfigTypeFromPath, getConfigTypeKeyFromValue, getFrontendProjectType, getProjectCommands, globAllFilesByPath, hashFile, hashString, isPathDirectory, toPosixPath, } from './lib/apps/index.js';
|
|
12
12
|
import { getGlobIgnorePathsChecker } from './lib/apps/paths.js';
|
|
13
13
|
import { slugFromApp } from './lib/apps/slug.js';
|
|
14
|
+
import { getLocalInputs } from './lib/bundle.js';
|
|
14
15
|
import { default as localConfig } from './lib/config.js';
|
|
15
16
|
import { toAppId } from './lib/create/index.js';
|
|
16
17
|
import { detectPackageManager, transformCommand, } from './lib/package-manager.js';
|
|
@@ -21,9 +22,31 @@ import { RemoteAppCommand } from './remote-app-command.js';
|
|
|
21
22
|
const PUSH_CONCURRENCY = 3;
|
|
22
23
|
const WATCH_WINDOW_MS = 100;
|
|
23
24
|
const DEV_SERVER_FALLBACK_PORT = 3000;
|
|
25
|
+
const WRANGLER_CONFIG_FILES = [
|
|
26
|
+
'wrangler.jsonc',
|
|
27
|
+
'wrangler.json',
|
|
28
|
+
'wrangler.toml',
|
|
29
|
+
];
|
|
30
|
+
/**
|
|
31
|
+
* The `main` entry point of a wrangler config (JSON, JSONC or TOML)
|
|
32
|
+
* @param {string} frontendPath directory of the wrangler config
|
|
33
|
+
* @returns {string | undefined} the entry point, relative to frontendPath
|
|
34
|
+
*/
|
|
35
|
+
function readWranglerMain(frontendPath) {
|
|
36
|
+
for (const name of WRANGLER_CONFIG_FILES) {
|
|
37
|
+
const configPath = path.join(frontendPath, name);
|
|
38
|
+
if (fs.existsSync(configPath)) {
|
|
39
|
+
// "main": "src/index.ts" in JSON, main = "src/index.ts" in TOML
|
|
40
|
+
return fs
|
|
41
|
+
.readFileSync(configPath, 'utf8')
|
|
42
|
+
.match(/^\s*"?main"?\s*[:=]\s*"([^"]+)"/m)?.[1];
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
24
46
|
export class PushAppCommand extends RemoteAppCommand {
|
|
25
47
|
frontendPath = '';
|
|
26
48
|
hasLoggedLocalPreviewUrl = false;
|
|
49
|
+
storeUser = false;
|
|
27
50
|
logWatchChanges = true;
|
|
28
51
|
onWatchChange;
|
|
29
52
|
watchingChangeQueue = new Map();
|
|
@@ -466,9 +489,34 @@ export class PushAppCommand extends RemoteAppCommand {
|
|
|
466
489
|
if (frontendHashes.length > 0) {
|
|
467
490
|
const packageHash = localConfigs.find((config) => config.filePath === 'package.json')?.hash;
|
|
468
491
|
frontendHashes += `|${packageHash}`;
|
|
492
|
+
// Shared code the worker imports from outside frontend/, e.g. functions/lib
|
|
493
|
+
for (const input of await this.getFrontendSharedInputs()) {
|
|
494
|
+
frontendHashes += `|${hashFile(path.join(this.appPath, input), undefined, input)}`;
|
|
495
|
+
}
|
|
469
496
|
}
|
|
470
497
|
return frontendHashes.length > 0 ? hashString(frontendHashes) : undefined;
|
|
471
498
|
}
|
|
499
|
+
/**
|
|
500
|
+
* Files outside frontend/ that the worker's entry point imports, relative to
|
|
501
|
+
* the app path. Only a local entry point set by `main` in the wrangler
|
|
502
|
+
* config is followed; frameworks that build their own entry are not.
|
|
503
|
+
* @returns {Promise<string[]>} paths of the imported files
|
|
504
|
+
*/
|
|
505
|
+
async getFrontendSharedInputs() {
|
|
506
|
+
const main = readWranglerMain(path.join(this.appPath, 'frontend'));
|
|
507
|
+
const entry = main && toPosixPath(path.join('frontend', main));
|
|
508
|
+
if (!entry || !fs.existsSync(path.join(this.appPath, entry))) {
|
|
509
|
+
return [];
|
|
510
|
+
}
|
|
511
|
+
try {
|
|
512
|
+
const inputs = await getLocalInputs(entry, this.appPath);
|
|
513
|
+
return inputs.filter((input) => !input.startsWith('frontend/')).sort();
|
|
514
|
+
}
|
|
515
|
+
catch {
|
|
516
|
+
// Build errors surface when wrangler deploys the worker
|
|
517
|
+
return [];
|
|
518
|
+
}
|
|
519
|
+
}
|
|
472
520
|
async getFrontendProjectType(required = true) {
|
|
473
521
|
this.frontendPath = path.join(this.appPath, 'frontend');
|
|
474
522
|
const frontendExists = await filePathExistsAsync(this.frontendPath);
|
|
@@ -823,8 +871,17 @@ export class PushAppCommand extends RemoteAppCommand {
|
|
|
823
871
|
? `/${this.app.private_id}`
|
|
824
872
|
: '';
|
|
825
873
|
this.log(`View it at ${style.link(`${this.localFrontendUrl(currentStore, localId)}${frontendAppRoute}`)}\n`);
|
|
874
|
+
if (this.app.type !== 'storefront') {
|
|
875
|
+
this.log(`${this.storeUser ? 'Store-user' : 'Visitor'} preview. Anyone with the link sees the same view.\n`);
|
|
876
|
+
}
|
|
826
877
|
this.hasLoggedLocalPreviewUrl = true;
|
|
827
878
|
}
|
|
879
|
+
setStoreUser(storeUser) {
|
|
880
|
+
this.storeUser = storeUser;
|
|
881
|
+
if (storeUser && this.app.type === 'storefront') {
|
|
882
|
+
this.error('--store-user is not supported for storefront previews.');
|
|
883
|
+
}
|
|
884
|
+
}
|
|
828
885
|
async setWatchingFiles() {
|
|
829
886
|
this.watchingFiles = new Set(await globAllFilesByPath(this.appPath));
|
|
830
887
|
}
|
|
@@ -865,13 +922,25 @@ export class PushAppCommand extends RemoteAppCommand {
|
|
|
865
922
|
async updateLocalProxy(proxyUrl, storefrontId) {
|
|
866
923
|
const storefront = this.app.type === 'storefront' &&
|
|
867
924
|
(await this.getAppStorefront({ storefront_id: storefrontId }));
|
|
868
|
-
|
|
925
|
+
const storeUser = Boolean(proxyUrl && this.storeUser);
|
|
926
|
+
const putLocalProxy = (proxy_url, store_user) => this.api.put({ adminPath: `/client/apps/${this.app.id}/local-proxy` }, {
|
|
869
927
|
body: {
|
|
870
|
-
proxy_url
|
|
928
|
+
proxy_url,
|
|
871
929
|
storefront_id: storefront?.id || null,
|
|
872
930
|
storefront_slug: storefront?.slug || null,
|
|
931
|
+
store_user,
|
|
873
932
|
},
|
|
874
|
-
})
|
|
933
|
+
});
|
|
934
|
+
const response = await this.handleRequestErrors(async () => putLocalProxy(proxyUrl, storeUser));
|
|
935
|
+
if (storeUser && response?.local_proxy?.store_user !== true) {
|
|
936
|
+
try {
|
|
937
|
+
await putLocalProxy(null, false);
|
|
938
|
+
}
|
|
939
|
+
catch {
|
|
940
|
+
// Cleanup is best-effort and silent; the unsupported-mode error follows.
|
|
941
|
+
}
|
|
942
|
+
this.error('This Swell server does not support --store-user. Restart without the flag.');
|
|
943
|
+
}
|
|
875
944
|
}
|
|
876
945
|
async watchForChanges({ logChanges, onChange, syncAll, } = {}) {
|
|
877
946
|
this.watchingIgnoreFilter = await getGlobIgnorePathsChecker(this.appPath);
|
|
@@ -5,7 +5,7 @@ import * as path from 'node:path';
|
|
|
5
5
|
import { default as ora } from 'ora';
|
|
6
6
|
import { AppCommand } from './app-command.js';
|
|
7
7
|
import { readRcFile, writeRcFile } from './lib/app-config.js';
|
|
8
|
-
import { AppConfig, ConfigType, FunctionProcessingError, IgnoringFileError, SwellJsonFields, allBaseFilesInDir, allConfigFilesPaths, appConfigFromFile, batchAppFilesByType, deleteFile,
|
|
8
|
+
import { AppConfig, ConfigType, FunctionProcessingError, IgnoringFileError, SwellJsonFields, allBaseFilesInDir, allConfigFilesPaths, appConfigFromFile, batchAppFilesByType, deleteFile, writeFile, writeJsonFile, PUSH_CONCURRENCY, } from './lib/apps/index.js';
|
|
9
9
|
import { default as localConfig } from './lib/config.js';
|
|
10
10
|
import { getAppFrontendHost, getLocalFrontendHost, getLoginHost, getStorefrontFrontendHost, } from './lib/constants.js';
|
|
11
11
|
import { toAppId } from './lib/create/index.js';
|
|
@@ -24,6 +24,8 @@ export class RemoteAppCommand extends AppCommand {
|
|
|
24
24
|
// Indicates the app was created during the command execution
|
|
25
25
|
appCreated = false;
|
|
26
26
|
appType = 'any';
|
|
27
|
+
// Files skipped because they could not be read or compiled
|
|
28
|
+
failedFiles = [];
|
|
27
29
|
// Conflicts the user chose to disable, held until the command commits
|
|
28
30
|
pendingConflicts;
|
|
29
31
|
// API instance of the target storefront, if applicable
|
|
@@ -498,11 +500,11 @@ export class RemoteAppCommand extends AppCommand {
|
|
|
498
500
|
return true;
|
|
499
501
|
try {
|
|
500
502
|
const localConfig = localBatchConfigs.find((config) => config.filePath === batchConfig.filePath);
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
503
|
+
// Functions pushed by earlier CLI versions carry the plain file hash
|
|
504
|
+
if (localConfig &&
|
|
505
|
+
(localConfig.hash === batchConfig.hash ||
|
|
506
|
+
localConfig.fileHash === batchConfig.hash)) {
|
|
507
|
+
return false;
|
|
506
508
|
}
|
|
507
509
|
}
|
|
508
510
|
catch {
|
|
@@ -675,11 +677,13 @@ export class RemoteAppCommand extends AppCommand {
|
|
|
675
677
|
if (error instanceof IgnoringFileError) {
|
|
676
678
|
log &&
|
|
677
679
|
spinner.fail(`Ignoring file: ${error.message} in ${config.filePath}`);
|
|
680
|
+
this.failedFiles.push(config.filePath);
|
|
678
681
|
return;
|
|
679
682
|
}
|
|
680
683
|
if (error instanceof FunctionProcessingError) {
|
|
681
684
|
log &&
|
|
682
685
|
spinner.fail(`${error.message} ${error.cause?.message?.toLowerCase()}`);
|
|
686
|
+
this.failedFiles.push(config.filePath);
|
|
683
687
|
return;
|
|
684
688
|
}
|
|
685
689
|
log && spinner.fail(error.message);
|
package/oclif.manifest.json
CHANGED
|
@@ -546,7 +546,7 @@
|
|
|
546
546
|
"swell api post /products --body '{\"name\": \"Product\", \"price\": 100}'",
|
|
547
547
|
"swell api post /products --body ./product.json",
|
|
548
548
|
"swell api post /functions/my-app/create-order --body '{\"items\":[\"sku-1\"]}'",
|
|
549
|
-
"swell api post /functions/my-app/webhook --body
|
|
549
|
+
"swell api post /functions/my-app/webhook --body ./payload.json -H 'X-Webhook-Event: order.created'",
|
|
550
550
|
"swell api post /products --body '{\"name\": \"Product\"}' --api frontend"
|
|
551
551
|
],
|
|
552
552
|
"flags": {
|
|
@@ -745,6 +745,12 @@
|
|
|
745
745
|
"allowNo": false,
|
|
746
746
|
"type": "boolean"
|
|
747
747
|
},
|
|
748
|
+
"store-user": {
|
|
749
|
+
"description": "for dashboard apps, preview as your store user instead of a visitor",
|
|
750
|
+
"name": "store-user",
|
|
751
|
+
"allowNo": false,
|
|
752
|
+
"type": "boolean"
|
|
753
|
+
},
|
|
748
754
|
"port": {
|
|
749
755
|
"char": "p",
|
|
750
756
|
"description": "override the default port to run the app locally",
|
|
@@ -3510,6 +3516,12 @@
|
|
|
3510
3516
|
"allowNo": false,
|
|
3511
3517
|
"type": "boolean"
|
|
3512
3518
|
},
|
|
3519
|
+
"store-user": {
|
|
3520
|
+
"description": "for dashboard apps, preview as your store user instead of a visitor",
|
|
3521
|
+
"name": "store-user",
|
|
3522
|
+
"allowNo": false,
|
|
3523
|
+
"type": "boolean"
|
|
3524
|
+
},
|
|
3513
3525
|
"port": {
|
|
3514
3526
|
"char": "p",
|
|
3515
3527
|
"description": "override the default port to run the app locally",
|
|
@@ -3594,5 +3606,5 @@
|
|
|
3594
3606
|
]
|
|
3595
3607
|
}
|
|
3596
3608
|
},
|
|
3597
|
-
"version": "2.9.
|
|
3609
|
+
"version": "2.9.23"
|
|
3598
3610
|
}
|