@swell/cli 2.9.31 → 2.9.33
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/app/action.d.ts +25 -0
- package/dist/commands/app/action.js +233 -0
- package/dist/commands/app/dev.js +2 -1
- package/dist/commands/app/frontend/deploy.d.ts +1 -0
- package/dist/commands/app/frontend/deploy.js +4 -0
- package/dist/commands/app/frontend/dev.js +4 -2
- package/dist/commands/app/push.d.ts +3 -0
- package/dist/commands/app/push.js +100 -4
- package/dist/commands/app/version.js +3 -0
- package/dist/commands/create/function.js +14 -4
- package/dist/commands/create/model.js +9 -24
- package/dist/commands/create/webhook.js +7 -13
- package/dist/commands/inspect/components.d.ts +31 -0
- package/dist/commands/inspect/components.js +110 -0
- package/dist/commands/inspect/extensions.js +18 -9
- package/dist/commands/inspect/frontend.d.ts +37 -0
- package/dist/commands/inspect/frontend.js +75 -0
- package/dist/commands/inspect/functions.d.ts +11 -1
- package/dist/commands/inspect/functions.js +89 -6
- package/dist/commands/inspect/index.js +2 -0
- package/dist/commands/schema.js +5 -0
- package/dist/help/custom-help.js +5 -1
- package/dist/inspect-resource-command.d.ts +1 -1
- package/dist/inspect-resource-command.js +8 -2
- package/dist/lib/api-errors.d.ts +1 -0
- package/dist/lib/api-errors.js +28 -0
- package/dist/lib/apps/actions.d.ts +45 -0
- package/dist/lib/apps/actions.js +114 -0
- package/dist/lib/apps/components.d.ts +7 -0
- package/dist/lib/apps/components.js +10 -0
- package/dist/lib/apps/extension-manifest.d.ts +1 -0
- package/dist/lib/apps/extension-manifest.js +43 -0
- package/dist/lib/apps/index.d.ts +1 -0
- package/dist/lib/apps/index.js +30 -0
- package/dist/lib/apps/listing.d.ts +1 -0
- package/dist/lib/apps/listing.js +41 -0
- package/dist/lib/apps/paths.d.ts +8 -0
- package/dist/lib/apps/paths.js +34 -4
- package/dist/lib/bundle.js +26 -41
- package/dist/lib/create/function.d.ts +1 -1
- package/dist/lib/create/function.js +9 -5
- package/dist/lib/create/index.js +2 -1
- package/dist/lib/create/model.d.ts +7 -3
- package/dist/lib/create/model.js +67 -9
- package/dist/lib/function-declarations.d.ts +8 -0
- package/dist/lib/function-declarations.js +27 -0
- package/dist/lib/inspect/extensions.d.ts +4 -3
- package/dist/lib/inspect/extensions.js +36 -5
- package/dist/lib/swell-function-wrapper.js +13 -5
- package/dist/lib/swell-workflow-request.d.ts +34 -0
- package/dist/lib/swell-workflow-request.js +58 -0
- package/dist/push-app-command.d.ts +2 -1
- package/dist/push-app-command.js +43 -22
- package/dist/remote-app-command.d.ts +2 -2
- package/dist/remote-app-command.js +79 -51
- package/oclif.manifest.json +263 -10
- package/package.json +2 -1
|
@@ -24,7 +24,7 @@ export interface ManifestEntry {
|
|
|
24
24
|
}
|
|
25
25
|
/** CLI-resolved type. Diverges from raw manifest `type` for payment (card vs alt). */
|
|
26
26
|
export type ExtensionType = 'card' | 'alt' | 'shipping' | 'tax';
|
|
27
|
-
export type ExtensionStatus = 'not deployed' | 'not activated' | 'app id mismatch' | 'id mismatch' | 'not selected' | 'gateway missing' | 'not enabled' | 'no handler' | 'handler mismatch' | 'activated';
|
|
27
|
+
export type ExtensionStatus = 'not deployed' | 'settings unavailable' | 'not activated' | 'app id mismatch' | 'id mismatch' | 'not selected' | 'gateway missing' | 'not enabled' | 'no handler' | 'handler mismatch' | 'activated';
|
|
28
28
|
export type ActionOwner = 'dev' | 'merchant' | null;
|
|
29
29
|
export interface FunctionRecord {
|
|
30
30
|
id?: string;
|
|
@@ -163,8 +163,6 @@ export declare function formatExtensionKey(slug: string, extId: string): string;
|
|
|
163
163
|
* resolves to `card` even with no explicit `method`.
|
|
164
164
|
*/
|
|
165
165
|
export declare function resolveExtensionType(entry: Pick<ManifestEntry, 'type' | 'method' | 'id'>): ExtensionType;
|
|
166
|
-
/** Apply the platform's `if(method, method, id)` formula. */
|
|
167
|
-
export declare function paymentMethodIdFor(entry: Pick<ManifestEntry, 'method' | 'id'>): string;
|
|
168
166
|
/** Shipping records use `app_<appId>_<extId>` as both the row id and the carrier id. */
|
|
169
167
|
export declare function shippingBindId(appId: string, extId: string): string;
|
|
170
168
|
/** Card-gateway records use the same id form as shipping rows. */
|
|
@@ -229,12 +227,15 @@ export interface BuildDetailInput {
|
|
|
229
227
|
payments?: PaymentSettings | null;
|
|
230
228
|
shipments?: ShippingSettings | null;
|
|
231
229
|
taxes?: TaxSettingsRecord | null;
|
|
230
|
+
/** Why each settings record could not be read, if it could not. */
|
|
231
|
+
unavailableSettings?: UnavailableSettings;
|
|
232
232
|
/** Functions in the same app whose top-level `extension` matches this extId. */
|
|
233
233
|
functions: FunctionRecord[];
|
|
234
234
|
/** Components in the same app whose `values.extension` matches this extId. */
|
|
235
235
|
components: ComponentRecord[];
|
|
236
236
|
localDiff?: LocalDiff | null;
|
|
237
237
|
}
|
|
238
|
+
export type UnavailableSettings = Partial<Record<'payments' | 'shipments' | 'taxes', string>>;
|
|
238
239
|
export interface BuildOrphanInput {
|
|
239
240
|
appId: string;
|
|
240
241
|
appSlug: string;
|
|
@@ -42,10 +42,6 @@ export function resolveExtensionType(entry) {
|
|
|
42
42
|
const method = entry.method ?? entry.id;
|
|
43
43
|
return method === 'card' ? 'card' : 'alt';
|
|
44
44
|
}
|
|
45
|
-
/** Apply the platform's `if(method, method, id)` formula. */
|
|
46
|
-
export function paymentMethodIdFor(entry) {
|
|
47
|
-
return entry.method ?? entry.id;
|
|
48
|
-
}
|
|
49
45
|
/** Shipping records use `app_<appId>_<extId>` as both the row id and the carrier id. */
|
|
50
46
|
export function shippingBindId(appId, extId) {
|
|
51
47
|
return `app_${appId}_${extId}`;
|
|
@@ -200,7 +196,9 @@ function findShippingCarrier(shipments, carrierId) {
|
|
|
200
196
|
* gate on `enabled`) is intentional.
|
|
201
197
|
*/
|
|
202
198
|
function deriveAltDispatch(ctx) {
|
|
203
|
-
|
|
199
|
+
// The dashboard keys an extension's method by the extension's id, whatever
|
|
200
|
+
// its `method`, and checkout dispatches on that id
|
|
201
|
+
const methodId = ctx.extId;
|
|
204
202
|
const expectedGatewayId = gatewayBindId(ctx.appId, ctx.extId);
|
|
205
203
|
const method = findPaymentMethod(ctx.payments, methodId);
|
|
206
204
|
const gateway = findPaymentGateway(ctx.payments, expectedGatewayId);
|
|
@@ -254,8 +252,11 @@ function deriveCardDispatch(ctx) {
|
|
|
254
252
|
const expectedGatewayId = gatewayBindId(ctx.appId, ctx.extId);
|
|
255
253
|
const cardMethod = findPaymentMethod(ctx.payments, 'card');
|
|
256
254
|
const gateway = findPaymentGateway(ctx.payments, expectedGatewayId);
|
|
255
|
+
// Charges and refunds go to the app and extension the card method names
|
|
257
256
|
const methodChecks = {
|
|
258
257
|
gateway: cardMethod?.gateway === expectedGatewayId,
|
|
258
|
+
extension_app_id: cardMethod?.extension_app_id === ctx.appId,
|
|
259
|
+
extension_config_id: cardMethod?.extension_config_id === ctx.extId,
|
|
259
260
|
};
|
|
260
261
|
const gatewayChecks = {
|
|
261
262
|
extension_app_id: gateway?.extension_app_id === ctx.appId,
|
|
@@ -285,6 +286,12 @@ function deriveCardDispatch(ctx) {
|
|
|
285
286
|
if (cardMethod?.gateway !== expectedGatewayId) {
|
|
286
287
|
return { status: 'not activated', native_bindings };
|
|
287
288
|
}
|
|
289
|
+
if (cardMethod.extension_app_id !== ctx.appId) {
|
|
290
|
+
return { status: 'app id mismatch', native_bindings };
|
|
291
|
+
}
|
|
292
|
+
if (cardMethod.extension_config_id !== ctx.extId) {
|
|
293
|
+
return { status: 'id mismatch', native_bindings };
|
|
294
|
+
}
|
|
288
295
|
return { status: 'activated', native_bindings };
|
|
289
296
|
}
|
|
290
297
|
/**
|
|
@@ -401,6 +408,26 @@ export function buildExtensionDetail(input) {
|
|
|
401
408
|
throw new Error('buildExtensionDetail: missing manifest for a deployed extension');
|
|
402
409
|
}
|
|
403
410
|
const type = resolveExtensionType(input.manifest);
|
|
411
|
+
// Without the settings the activation cannot be told
|
|
412
|
+
const settingsRecord = type === 'shipping' ? 'shipments' : type === 'tax' ? 'taxes' : 'payments';
|
|
413
|
+
const unavailable = input.unavailableSettings?.[settingsRecord];
|
|
414
|
+
if (unavailable) {
|
|
415
|
+
return {
|
|
416
|
+
status: 'settings unavailable',
|
|
417
|
+
action_owner: 'dev',
|
|
418
|
+
action: `Could not read /settings/${settingsRecord}: ${unavailable}`,
|
|
419
|
+
type,
|
|
420
|
+
manifest: input.manifest,
|
|
421
|
+
native_bindings: [],
|
|
422
|
+
bound: {
|
|
423
|
+
functions: input.functions.map((f) => liftBoundFunction(f)),
|
|
424
|
+
components: input.components.map((c) => liftBoundComponent(c)),
|
|
425
|
+
},
|
|
426
|
+
required_events: requiredEventsFor(type),
|
|
427
|
+
missing_required_events: missingRequiredEvents(type, input.functions),
|
|
428
|
+
local_diff: input.localDiff ?? null,
|
|
429
|
+
};
|
|
430
|
+
}
|
|
404
431
|
const dispatch = deriveDispatch(type, {
|
|
405
432
|
appId: input.appId,
|
|
406
433
|
extId: input.extId,
|
|
@@ -487,6 +514,10 @@ function describeAction(status, ctx) {
|
|
|
487
514
|
case 'not deployed': {
|
|
488
515
|
return { action: 'swell app push', action_owner: 'dev' };
|
|
489
516
|
}
|
|
517
|
+
// buildExtensionDetail names the settings it could not read
|
|
518
|
+
case 'settings unavailable': {
|
|
519
|
+
return { action: null, action_owner: 'dev' };
|
|
520
|
+
}
|
|
490
521
|
case 'not activated': {
|
|
491
522
|
return {
|
|
492
523
|
action: `Open Settings → ${section} → ${label} → Save`,
|
|
@@ -30,7 +30,13 @@ async function request(originalRequest, _env, context) {
|
|
|
30
30
|
...(error.body ?? { error: error.message }),
|
|
31
31
|
retry: error.retry === false ? false : undefined,
|
|
32
32
|
};
|
|
33
|
-
|
|
33
|
+
const retryAfter = Number(error.retryAfter);
|
|
34
|
+
response = new SwellResponse(errorBody, {
|
|
35
|
+
status: error.status || 500,
|
|
36
|
+
...(retryAfter > 0
|
|
37
|
+
? { headers: { 'Retry-After': String(Math.ceil(retryAfter)) } }
|
|
38
|
+
: {}),
|
|
39
|
+
});
|
|
34
40
|
}
|
|
35
41
|
return SwellResponse._respond(req, response, context);
|
|
36
42
|
}
|
|
@@ -118,9 +124,9 @@ class SwellRequest {
|
|
|
118
124
|
this.swell = new SwellAPI(this, context);
|
|
119
125
|
// URL of the original request
|
|
120
126
|
this.url;
|
|
121
|
-
// Request body text. On a route called through the storefront gateway
|
|
122
|
-
//
|
|
123
|
-
//
|
|
127
|
+
// Request body text. On a route called through the storefront gateway, this
|
|
128
|
+
// is the exact body the caller sent, so verify HMAC/webhook signatures
|
|
129
|
+
// against it. Form-encoded bodies arrive re-serialized as JSON.
|
|
124
130
|
this.rawBody = '';
|
|
125
131
|
// Parsed JSON body as object, or raw text string when body isn't JSON.
|
|
126
132
|
this.body = {};
|
|
@@ -447,6 +453,7 @@ class SwellError extends Error {
|
|
|
447
453
|
this.body = body;
|
|
448
454
|
this.code = options.code || body?.error?.code;
|
|
449
455
|
this.retry = options.retry;
|
|
456
|
+
this.retryAfter = options.retryAfter;
|
|
450
457
|
}
|
|
451
458
|
get isRetryable() {
|
|
452
459
|
return RETRYABLE_CODES.has(this.code);
|
|
@@ -501,7 +508,8 @@ class SwellResponse extends Response {
|
|
|
501
508
|
}
|
|
502
509
|
static async _respond(req, response, context) {
|
|
503
510
|
let finalResponse = response;
|
|
504
|
-
|
|
511
|
+
// Only the platform marks a hook's call: a route's data comes from its caller
|
|
512
|
+
const isHook = Boolean(req.headers.get('Swell-Event-Hook'));
|
|
505
513
|
if (finalResponse instanceof Response &&
|
|
506
514
|
!(finalResponse instanceof SwellResponse)) {
|
|
507
515
|
// Non-hook responses pass through unchanged so the handler's body,
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export function runtimeEnvironmentId(runtime: any): any;
|
|
2
|
+
export function buildWorkflowRequest(invocation: any, proxy: any): {
|
|
3
|
+
id: any;
|
|
4
|
+
appId: any;
|
|
5
|
+
environmentId: any;
|
|
6
|
+
store: {
|
|
7
|
+
id: any;
|
|
8
|
+
};
|
|
9
|
+
app: {
|
|
10
|
+
admin_url: any;
|
|
11
|
+
frontend_url: any;
|
|
12
|
+
};
|
|
13
|
+
data: any;
|
|
14
|
+
workflow: {
|
|
15
|
+
workflow_id: any;
|
|
16
|
+
workflow_name: any;
|
|
17
|
+
workflow_instance_id: any;
|
|
18
|
+
trigger: any;
|
|
19
|
+
request_id: any;
|
|
20
|
+
};
|
|
21
|
+
isLocalDev: boolean;
|
|
22
|
+
swell: {
|
|
23
|
+
get: (path: any, data: any) => any;
|
|
24
|
+
post: (path: any, data: any) => any;
|
|
25
|
+
put: (path: any, data: any) => any;
|
|
26
|
+
delete: (path: any, data: any) => any;
|
|
27
|
+
settings: () => any;
|
|
28
|
+
};
|
|
29
|
+
appValues(idOrValues: any, values: any): {
|
|
30
|
+
$app: {
|
|
31
|
+
[x: number]: any;
|
|
32
|
+
};
|
|
33
|
+
};
|
|
34
|
+
};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// The request a workflow's run(req, step) gets, built from the invocation the
|
|
2
|
+
// platform starts the run with. Every workflow bundle includes this module.
|
|
3
|
+
// 'test' in the test environment, null in live
|
|
4
|
+
export function runtimeEnvironmentId(runtime) {
|
|
5
|
+
return runtime.environment_id || null;
|
|
6
|
+
}
|
|
7
|
+
export function buildWorkflowRequest(invocation, proxy) {
|
|
8
|
+
const { data, runtime } = invocation;
|
|
9
|
+
const appId = runtime.app_slug || runtime.app_id;
|
|
10
|
+
return {
|
|
11
|
+
id: runtime.request_id,
|
|
12
|
+
appId,
|
|
13
|
+
environmentId: runtimeEnvironmentId(runtime),
|
|
14
|
+
store: {
|
|
15
|
+
id: runtime.store_id,
|
|
16
|
+
},
|
|
17
|
+
app: {
|
|
18
|
+
admin_url: runtime.app_admin_url,
|
|
19
|
+
frontend_url: runtime.app_frontend_url,
|
|
20
|
+
},
|
|
21
|
+
data,
|
|
22
|
+
workflow: {
|
|
23
|
+
workflow_id: runtime.workflow_id,
|
|
24
|
+
workflow_name: runtime.workflow_name,
|
|
25
|
+
workflow_instance_id: runtime.workflow_instance_id,
|
|
26
|
+
trigger: runtime.trigger,
|
|
27
|
+
request_id: runtime.request_id,
|
|
28
|
+
},
|
|
29
|
+
isLocalDev: false,
|
|
30
|
+
swell: {
|
|
31
|
+
get: (path, data) => proxy.api('get', path, data),
|
|
32
|
+
post: (path, data) => proxy.api('post', path, data),
|
|
33
|
+
put: (path, data) => proxy.api('put', path, data),
|
|
34
|
+
delete: (path, data) => proxy.api('delete', path, data),
|
|
35
|
+
settings: () => proxy.settings(),
|
|
36
|
+
},
|
|
37
|
+
appValues(idOrValues, values) {
|
|
38
|
+
const targetAppId = typeof idOrValues === 'string' ? idOrValues : appId;
|
|
39
|
+
const appValues = typeof idOrValues === 'string' ? values : idOrValues;
|
|
40
|
+
if (!targetAppId) {
|
|
41
|
+
throw new Error('appValues: missing app id (req.appId is empty)');
|
|
42
|
+
}
|
|
43
|
+
if (!isOrdinaryObject(appValues)) {
|
|
44
|
+
throw new Error('appValues: values must be a plain object (arrays, class instances, null, and primitives are not allowed)');
|
|
45
|
+
}
|
|
46
|
+
return {
|
|
47
|
+
$app: {
|
|
48
|
+
[targetAppId]: appValues,
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
function isOrdinaryObject(val) {
|
|
55
|
+
return (typeof val === 'object' &&
|
|
56
|
+
val !== null &&
|
|
57
|
+
Object.getPrototypeOf(val) === Object.prototype);
|
|
58
|
+
}
|
|
@@ -21,13 +21,14 @@ export declare abstract class PushAppCommand extends RemoteAppCommand {
|
|
|
21
21
|
watchingIgnoreFilter: GlobbyFilterFunction | null;
|
|
22
22
|
watchingTimer: NodeJS.Timeout | null;
|
|
23
23
|
watchListener: (eventType: fs.WatchEventType, filename: null | string) => Promise<void>;
|
|
24
|
+
protected pushesSwellJson(file?: string): boolean;
|
|
24
25
|
protected showFrontendMigrationError(): never;
|
|
25
26
|
buildFrontend(projectType?: FrontendProjectType): Promise<void>;
|
|
26
27
|
chooseAppToPull(query?: any): Promise<App>;
|
|
27
28
|
createAppStorefront(hasOtherStorefronts?: boolean, nonInteractive?: boolean): Promise<any>;
|
|
28
29
|
deployAppFrontend(force?: boolean, log?: boolean): Promise<void>;
|
|
29
30
|
deployFrontend(projectType: FrontendProjectType): Promise<string>;
|
|
30
|
-
ensureAppExists(file?: string, shouldCreate?: boolean
|
|
31
|
+
ensureAppExists(file?: string, shouldCreate?: boolean): Promise<boolean>;
|
|
31
32
|
ensureLoggedIn(): Promise<void>;
|
|
32
33
|
exec(command: string, cwd?: string, onOutput?: (string: string) => any | false): Promise<void>;
|
|
33
34
|
execFrontend(command: string, onOutput?: (string: string) => any | false): Promise<void>;
|
package/dist/push-app-command.js
CHANGED
|
@@ -7,10 +7,12 @@ import Stream from 'node:stream';
|
|
|
7
7
|
import ora from 'ora';
|
|
8
8
|
import { default as swellConfig } from './lib/app-config.js';
|
|
9
9
|
import { componentsToRebuild } from './lib/apps/components.js';
|
|
10
|
+
import { extensionManifestWarnings } from './lib/apps/extension-manifest.js';
|
|
10
11
|
import { buildFrontendPackage, getManagedFrontendFramework, } from './lib/apps/frontend-package.js';
|
|
11
12
|
import { warnFrontendRouteCollisions } from './lib/apps/frontend-routes.js';
|
|
12
13
|
import { ConfigType, getFrontendProjectValidValues, allConfigFilesInDir, CUSTOM_FRAMEWORK_SLUG, appConfigFromFile, filePathExistsAsync, findAppConfig, getConfigTypeFromPath, getConfigTypeKeyFromValue, getFrontendProjectType, getProjectCommands, globAllFilesByPath, hashFile, hashString, isPathDirectory, toPosixPath, } from './lib/apps/index.js';
|
|
13
|
-
import {
|
|
14
|
+
import { warnListingSources } from './lib/apps/listing.js';
|
|
15
|
+
import { getGlobIgnorePathsChecker, globFrontendEnvFiles, isCredentialFile, } from './lib/apps/paths.js';
|
|
14
16
|
import { slugFromApp } from './lib/apps/slug.js';
|
|
15
17
|
import { getLocalInputs } from './lib/bundle.js';
|
|
16
18
|
import { default as localConfig } from './lib/config.js';
|
|
@@ -119,6 +121,10 @@ export class PushAppCommand extends RemoteAppCommand {
|
|
|
119
121
|
}
|
|
120
122
|
}
|
|
121
123
|
};
|
|
124
|
+
// The platform applies the listing in swell.json when the file is pushed
|
|
125
|
+
pushesSwellJson(file) {
|
|
126
|
+
return !file;
|
|
127
|
+
}
|
|
122
128
|
showFrontendMigrationError() {
|
|
123
129
|
return this.error(style.funcWarn(`⚠️ Your frontend directory exists but appears to use an older structure.\n` +
|
|
124
130
|
`Swell CLI v2.1.0+ requires a Workers-based frontend with package.json.\n\n` +
|
|
@@ -330,7 +336,7 @@ export class PushAppCommand extends RemoteAppCommand {
|
|
|
330
336
|
this.error(`Error deploying frontend: ${error.message}`);
|
|
331
337
|
}
|
|
332
338
|
}
|
|
333
|
-
async ensureAppExists(file, shouldCreate = true
|
|
339
|
+
async ensureAppExists(file, shouldCreate = true) {
|
|
334
340
|
const klass = this.ctor;
|
|
335
341
|
const currentStore = localConfig.getDefaultStore();
|
|
336
342
|
await this.ensureLoggedIn();
|
|
@@ -356,15 +362,21 @@ export class PushAppCommand extends RemoteAppCommand {
|
|
|
356
362
|
this.app = app;
|
|
357
363
|
return true;
|
|
358
364
|
}
|
|
365
|
+
if (this.pushesSwellJson(file)) {
|
|
366
|
+
warnListingSources(this.appPath, this.swellConfig.store, (message) => this.warn(message));
|
|
367
|
+
for (const message of extensionManifestWarnings(this.swellConfig.get('extensions'))) {
|
|
368
|
+
this.warn(message);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
359
371
|
// If app exists but owned by another client, create a new instance
|
|
360
372
|
if (app?.client_id && app.client_id !== currentStore) {
|
|
361
373
|
// Create a new app
|
|
362
|
-
this.app = await this.getCreateUpdateApp(
|
|
374
|
+
this.app = await this.getCreateUpdateApp();
|
|
363
375
|
if (!this.app) {
|
|
364
376
|
return false;
|
|
365
377
|
}
|
|
366
378
|
}
|
|
367
|
-
else if (file) {
|
|
379
|
+
else if (file && !this.pushesSwellJson(file)) {
|
|
368
380
|
// Targeting a file: don't update the app from swell.json, but keep the
|
|
369
381
|
// remote one to diff config hashes and the frontend deployment hash
|
|
370
382
|
if (app) {
|
|
@@ -372,8 +384,9 @@ export class PushAppCommand extends RemoteAppCommand {
|
|
|
372
384
|
}
|
|
373
385
|
}
|
|
374
386
|
else {
|
|
375
|
-
//
|
|
376
|
-
|
|
387
|
+
// The app takes its details from swell.json before any upload, so that
|
|
388
|
+
// details the app refuses fail the push first
|
|
389
|
+
this.app = await this.getCreateUpdateApp(app);
|
|
377
390
|
}
|
|
378
391
|
return true;
|
|
379
392
|
}
|
|
@@ -493,8 +506,12 @@ export class PushAppCommand extends RemoteAppCommand {
|
|
|
493
506
|
if (frontendHashes.length > 0) {
|
|
494
507
|
const packageHash = localConfigs.find((config) => config.filePath === 'package.json')?.hash;
|
|
495
508
|
frontendHashes += `|${packageHash}`;
|
|
496
|
-
// Shared code the worker imports from outside frontend/, e.g.
|
|
497
|
-
|
|
509
|
+
// Shared code the worker imports from outside frontend/, e.g.
|
|
510
|
+
// functions/lib, and the .env files its build reads
|
|
511
|
+
for (const input of [
|
|
512
|
+
...(await this.getFrontendSharedInputs()),
|
|
513
|
+
...(await globFrontendEnvFiles(this.appPath)),
|
|
514
|
+
]) {
|
|
498
515
|
frontendHashes += `|${hashFile(path.join(this.appPath, input), undefined, input)}`;
|
|
499
516
|
}
|
|
500
517
|
}
|
|
@@ -787,6 +804,11 @@ export class PushAppCommand extends RemoteAppCommand {
|
|
|
787
804
|
}
|
|
788
805
|
async pushFile(relativePath) {
|
|
789
806
|
const relativePathPosix = toPosixPath(relativePath);
|
|
807
|
+
if (isCredentialFile(relativePathPosix)) {
|
|
808
|
+
ora().fail(`${relativePathPosix} is never pushed: .env and .dev.vars files can hold credentials`);
|
|
809
|
+
this.failedFiles.push(relativePathPosix);
|
|
810
|
+
return;
|
|
811
|
+
}
|
|
790
812
|
const basePath = relativePathPosix.split('/')[0];
|
|
791
813
|
const configType = getConfigTypeFromPath(basePath) || ConfigType.FILE;
|
|
792
814
|
const config = await appConfigFromFile(relativePathPosix, configType, this.appPath);
|
|
@@ -817,7 +839,7 @@ export class PushAppCommand extends RemoteAppCommand {
|
|
|
817
839
|
const pushConcurrency = configTypeKey === 'ASSET' ? 1 : PUSH_CONCURRENCY;
|
|
818
840
|
while (pushFiles.length > 0) {
|
|
819
841
|
// eslint-disable-next-line no-await-in-loop
|
|
820
|
-
await
|
|
842
|
+
await this.runTogether(pushFiles
|
|
821
843
|
.splice(0, pushConcurrency)
|
|
822
844
|
.map((appConfig) => this.pushRemoteFile(appConfig)));
|
|
823
845
|
}
|
|
@@ -826,7 +848,7 @@ export class PushAppCommand extends RemoteAppCommand {
|
|
|
826
848
|
if (filesToRemove.length > 0) {
|
|
827
849
|
while (filesToRemove.length > 0) {
|
|
828
850
|
// eslint-disable-next-line no-await-in-loop
|
|
829
|
-
await
|
|
851
|
+
await this.runTogether(filesToRemove
|
|
830
852
|
.splice(0, PUSH_CONCURRENCY)
|
|
831
853
|
.map((appConfig) => this.removeRemoteFile(appConfig)));
|
|
832
854
|
}
|
|
@@ -862,18 +884,17 @@ export class PushAppCommand extends RemoteAppCommand {
|
|
|
862
884
|
// Process queue in batches with concurrency control
|
|
863
885
|
/* eslint-disable no-await-in-loop */
|
|
864
886
|
while (queue.length > 0) {
|
|
865
|
-
|
|
866
|
-
await
|
|
867
|
-
.
|
|
868
|
-
.
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
// TODO: Ignore only some errors
|
|
887
|
+
const results = await Promise.allSettled(queue.splice(0, PUSH_CONCURRENCY).map(async ({ action, appConfig }) => {
|
|
888
|
+
const result = await (action === 'remove'
|
|
889
|
+
? this.removeRemoteFile(appConfig, this.logWatchChanges)
|
|
890
|
+
: this.pushRemoteFile(appConfig, this.logWatchChanges));
|
|
891
|
+
this.onWatchChange?.(appConfig, action, result);
|
|
892
|
+
}));
|
|
893
|
+
// A failed change ends no watch: its reasons show, and watching goes on
|
|
894
|
+
for (const result of results) {
|
|
895
|
+
if (result.status === 'rejected') {
|
|
896
|
+
this.logError(result.reason?.message ?? String(result.reason));
|
|
897
|
+
}
|
|
877
898
|
}
|
|
878
899
|
}
|
|
879
900
|
/* eslint-enable no-await-in-loop */
|
|
@@ -26,7 +26,7 @@ export declare abstract class RemoteAppCommand extends AppCommand {
|
|
|
26
26
|
protected getApp(id?: string): Promise<App>;
|
|
27
27
|
protected getAppWithConfig(id?: string): Promise<App>;
|
|
28
28
|
getConfigData(configId: string): Promise<any>;
|
|
29
|
-
protected getCreateUpdateApp(updateApp?: App | null
|
|
29
|
+
protected getCreateUpdateApp(updateApp?: App | null): Promise<App>;
|
|
30
30
|
protected getInstalledApp(): Promise<any>;
|
|
31
31
|
/**
|
|
32
32
|
* Anything on the target store already using the services named by
|
|
@@ -88,9 +88,9 @@ export declare abstract class RemoteAppCommand extends AppCommand {
|
|
|
88
88
|
removed: string[];
|
|
89
89
|
}>;
|
|
90
90
|
protected pushRemoteFile(config: AppConfig, log?: boolean): Promise<any>;
|
|
91
|
+
protected runTogether(tasks: Promise<unknown>[]): Promise<void>;
|
|
91
92
|
protected removeLocalFile(config: AppConfig, log?: boolean): Promise<any>;
|
|
92
93
|
protected removeRemoteFile(config: AppConfig, log?: boolean): Promise<any>;
|
|
93
|
-
protected showErrors(errors: any, spinnerError?: () => void): void;
|
|
94
94
|
storefrontFrontendUrl(storeId: string, storefront?: any, branchId?: string, preview?: boolean): string;
|
|
95
95
|
protected swellRcPath(): string;
|
|
96
96
|
protected timestampStyled(): string;
|