@swell/cli 2.9.32 → 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/dev.js +2 -1
- package/dist/commands/app/frontend/dev.js +4 -2
- package/dist/commands/app/push.d.ts +1 -0
- package/dist/commands/app/push.js +38 -3
- 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/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/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/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/paths.d.ts +8 -0
- package/dist/lib/apps/paths.js +34 -4
- package/dist/lib/bundle.js +15 -2
- 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/inspect/extensions.d.ts +4 -3
- package/dist/lib/inspect/extensions.js +36 -5
- package/dist/lib/swell-function-wrapper.js +5 -4
- package/dist/push-app-command.js +32 -19
- package/dist/remote-app-command.d.ts +1 -1
- package/dist/remote-app-command.js +78 -47
- package/oclif.manifest.json +139 -6
- package/package.json +1 -1
|
@@ -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`,
|
|
@@ -124,9 +124,9 @@ class SwellRequest {
|
|
|
124
124
|
this.swell = new SwellAPI(this, context);
|
|
125
125
|
// URL of the original request
|
|
126
126
|
this.url;
|
|
127
|
-
// Request body text. On a route called through the storefront gateway
|
|
128
|
-
//
|
|
129
|
-
//
|
|
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.
|
|
130
130
|
this.rawBody = '';
|
|
131
131
|
// Parsed JSON body as object, or raw text string when body isn't JSON.
|
|
132
132
|
this.body = {};
|
|
@@ -508,7 +508,8 @@ class SwellResponse extends Response {
|
|
|
508
508
|
}
|
|
509
509
|
static async _respond(req, response, context) {
|
|
510
510
|
let finalResponse = response;
|
|
511
|
-
|
|
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'));
|
|
512
513
|
if (finalResponse instanceof Response &&
|
|
513
514
|
!(finalResponse instanceof SwellResponse)) {
|
|
514
515
|
// Non-hook responses pass through unchanged so the handler's body,
|
package/dist/push-app-command.js
CHANGED
|
@@ -7,11 +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
14
|
import { warnListingSources } from './lib/apps/listing.js';
|
|
14
|
-
import { getGlobIgnorePathsChecker } from './lib/apps/paths.js';
|
|
15
|
+
import { getGlobIgnorePathsChecker, globFrontendEnvFiles, isCredentialFile, } from './lib/apps/paths.js';
|
|
15
16
|
import { slugFromApp } from './lib/apps/slug.js';
|
|
16
17
|
import { getLocalInputs } from './lib/bundle.js';
|
|
17
18
|
import { default as localConfig } from './lib/config.js';
|
|
@@ -363,6 +364,9 @@ export class PushAppCommand extends RemoteAppCommand {
|
|
|
363
364
|
}
|
|
364
365
|
if (this.pushesSwellJson(file)) {
|
|
365
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
|
+
}
|
|
366
370
|
}
|
|
367
371
|
// If app exists but owned by another client, create a new instance
|
|
368
372
|
if (app?.client_id && app.client_id !== currentStore) {
|
|
@@ -372,7 +376,7 @@ export class PushAppCommand extends RemoteAppCommand {
|
|
|
372
376
|
return false;
|
|
373
377
|
}
|
|
374
378
|
}
|
|
375
|
-
else if (file) {
|
|
379
|
+
else if (file && !this.pushesSwellJson(file)) {
|
|
376
380
|
// Targeting a file: don't update the app from swell.json, but keep the
|
|
377
381
|
// remote one to diff config hashes and the frontend deployment hash
|
|
378
382
|
if (app) {
|
|
@@ -380,7 +384,8 @@ export class PushAppCommand extends RemoteAppCommand {
|
|
|
380
384
|
}
|
|
381
385
|
}
|
|
382
386
|
else {
|
|
383
|
-
//
|
|
387
|
+
// The app takes its details from swell.json before any upload, so that
|
|
388
|
+
// details the app refuses fail the push first
|
|
384
389
|
this.app = await this.getCreateUpdateApp(app);
|
|
385
390
|
}
|
|
386
391
|
return true;
|
|
@@ -501,8 +506,12 @@ export class PushAppCommand extends RemoteAppCommand {
|
|
|
501
506
|
if (frontendHashes.length > 0) {
|
|
502
507
|
const packageHash = localConfigs.find((config) => config.filePath === 'package.json')?.hash;
|
|
503
508
|
frontendHashes += `|${packageHash}`;
|
|
504
|
-
// Shared code the worker imports from outside frontend/, e.g.
|
|
505
|
-
|
|
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
|
+
]) {
|
|
506
515
|
frontendHashes += `|${hashFile(path.join(this.appPath, input), undefined, input)}`;
|
|
507
516
|
}
|
|
508
517
|
}
|
|
@@ -795,6 +804,11 @@ export class PushAppCommand extends RemoteAppCommand {
|
|
|
795
804
|
}
|
|
796
805
|
async pushFile(relativePath) {
|
|
797
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
|
+
}
|
|
798
812
|
const basePath = relativePathPosix.split('/')[0];
|
|
799
813
|
const configType = getConfigTypeFromPath(basePath) || ConfigType.FILE;
|
|
800
814
|
const config = await appConfigFromFile(relativePathPosix, configType, this.appPath);
|
|
@@ -825,7 +839,7 @@ export class PushAppCommand extends RemoteAppCommand {
|
|
|
825
839
|
const pushConcurrency = configTypeKey === 'ASSET' ? 1 : PUSH_CONCURRENCY;
|
|
826
840
|
while (pushFiles.length > 0) {
|
|
827
841
|
// eslint-disable-next-line no-await-in-loop
|
|
828
|
-
await
|
|
842
|
+
await this.runTogether(pushFiles
|
|
829
843
|
.splice(0, pushConcurrency)
|
|
830
844
|
.map((appConfig) => this.pushRemoteFile(appConfig)));
|
|
831
845
|
}
|
|
@@ -834,7 +848,7 @@ export class PushAppCommand extends RemoteAppCommand {
|
|
|
834
848
|
if (filesToRemove.length > 0) {
|
|
835
849
|
while (filesToRemove.length > 0) {
|
|
836
850
|
// eslint-disable-next-line no-await-in-loop
|
|
837
|
-
await
|
|
851
|
+
await this.runTogether(filesToRemove
|
|
838
852
|
.splice(0, PUSH_CONCURRENCY)
|
|
839
853
|
.map((appConfig) => this.removeRemoteFile(appConfig)));
|
|
840
854
|
}
|
|
@@ -870,18 +884,17 @@ export class PushAppCommand extends RemoteAppCommand {
|
|
|
870
884
|
// Process queue in batches with concurrency control
|
|
871
885
|
/* eslint-disable no-await-in-loop */
|
|
872
886
|
while (queue.length > 0) {
|
|
873
|
-
|
|
874
|
-
await
|
|
875
|
-
.
|
|
876
|
-
.
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
// 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
|
+
}
|
|
885
898
|
}
|
|
886
899
|
}
|
|
887
900
|
/* eslint-enable no-await-in-loop */
|
|
@@ -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;
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { confirm, select } from '@inquirer/prompts';
|
|
2
|
-
import { inflect } from 'inflection';
|
|
3
2
|
import sortBy from 'lodash/sortBy.js';
|
|
4
3
|
import * as path from 'node:path';
|
|
5
4
|
import { default as ora } from 'ora';
|
|
6
5
|
import { AppCommand } from './app-command.js';
|
|
6
|
+
import { errorReasons } from './lib/api-errors.js';
|
|
7
7
|
import { readRcFile, writeRcFile } from './lib/app-config.js';
|
|
8
|
-
import {
|
|
8
|
+
import { isUnloadableComponentFile } from './lib/apps/components.js';
|
|
9
|
+
import { AppConfig, ConfigType, FunctionProcessingError, IgnoringFileError, SwellJsonFields, allBaseFilesInDir, allConfigFilesPaths, appConfigFromFile, batchAppFilesByType, deleteFile, kebabCaseFilePath, writeFile, writeJsonFile, PUSH_CONCURRENCY, } from './lib/apps/index.js';
|
|
10
|
+
import { isCredentialFile } from './lib/apps/paths.js';
|
|
9
11
|
import { default as localConfig } from './lib/config.js';
|
|
10
12
|
import { getAppFrontendHost, getLocalFrontendHost, getLoginHost, getStorefrontFrontendHost, } from './lib/constants.js';
|
|
11
13
|
import { toAppId } from './lib/create/index.js';
|
|
@@ -402,38 +404,37 @@ export class RemoteAppCommand extends AppCommand {
|
|
|
402
404
|
});
|
|
403
405
|
return versionsResponse.results || [];
|
|
404
406
|
}
|
|
407
|
+
// A refused request fails with every reason the API gives, so that they
|
|
408
|
+
// reach stderr together with the failure
|
|
405
409
|
async handleRequestErrors(request, spinnerError) {
|
|
406
|
-
let
|
|
410
|
+
let response;
|
|
407
411
|
try {
|
|
408
|
-
|
|
409
|
-
if (response?.status !== 200 && response?.errors) {
|
|
410
|
-
this.showErrors(response.errors, spinnerError);
|
|
411
|
-
showedErrors = true;
|
|
412
|
-
this.error('Something went wrong.');
|
|
413
|
-
}
|
|
414
|
-
return response;
|
|
412
|
+
response = await request();
|
|
415
413
|
}
|
|
416
414
|
catch (error) {
|
|
417
415
|
let jsonError;
|
|
418
416
|
try {
|
|
419
417
|
jsonError = JSON.parse(error.message);
|
|
420
418
|
}
|
|
421
|
-
catch (
|
|
419
|
+
catch (parseError) {
|
|
422
420
|
// Skip JSON parse error
|
|
423
|
-
if (!(
|
|
424
|
-
throw
|
|
421
|
+
if (!(parseError instanceof SyntaxError)) {
|
|
422
|
+
throw parseError;
|
|
425
423
|
}
|
|
426
424
|
}
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
else if (typeof error.message === 'string') {
|
|
432
|
-
this.showErrors(error, spinnerError);
|
|
433
|
-
}
|
|
425
|
+
spinnerError?.();
|
|
426
|
+
const reasons = jsonError?.error ? errorReasons(jsonError) : [];
|
|
427
|
+
if (reasons.length > 0) {
|
|
428
|
+
this.error(reasons.join('\n'));
|
|
434
429
|
}
|
|
435
430
|
throw error;
|
|
436
431
|
}
|
|
432
|
+
if (response?.status !== 200 && response?.errors) {
|
|
433
|
+
spinnerError?.();
|
|
434
|
+
const reasons = errorReasons(response.errors);
|
|
435
|
+
this.error(reasons.length > 0 ? reasons.join('\n') : 'Something went wrong.');
|
|
436
|
+
}
|
|
437
|
+
return response;
|
|
437
438
|
}
|
|
438
439
|
async init() {
|
|
439
440
|
await super.init();
|
|
@@ -486,7 +487,11 @@ export class RemoteAppCommand extends AppCommand {
|
|
|
486
487
|
const removed = [];
|
|
487
488
|
const [localConfigs, remoteConfigs] = await Promise.all([
|
|
488
489
|
this.getLocalAppConfigs(),
|
|
489
|
-
this.pullAppJsonAndRemoteConfigs()
|
|
490
|
+
this.pullAppJsonAndRemoteConfigs()
|
|
491
|
+
.then((remoteConfigs) => this.cleanRemoteConfigs(remoteConfigs, false))
|
|
492
|
+
// An older push could send .env and .dev.vars files, which must not
|
|
493
|
+
// replace the local ones
|
|
494
|
+
.then((remoteConfigs) => remoteConfigs.filter((config) => !isCredentialFile(config.filePath))),
|
|
490
495
|
]);
|
|
491
496
|
const batches = batchAppFilesByType(remoteConfigs);
|
|
492
497
|
for (const batch of batches) {
|
|
@@ -629,7 +634,7 @@ export class RemoteAppCommand extends AppCommand {
|
|
|
629
634
|
if (pushFiles.length > 0) {
|
|
630
635
|
while (pushFiles.length > 0) {
|
|
631
636
|
// eslint-disable-next-line no-await-in-loop
|
|
632
|
-
await
|
|
637
|
+
await this.runTogether(pushFiles.splice(0, batch.concurrency).map(async (config) => {
|
|
633
638
|
const pushedConfig = await this.pushRemoteFile(config);
|
|
634
639
|
if (pushedConfig) {
|
|
635
640
|
pushed.push(pushedConfig.id);
|
|
@@ -644,7 +649,7 @@ export class RemoteAppCommand extends AppCommand {
|
|
|
644
649
|
if (filesToRemove.length > 0) {
|
|
645
650
|
while (filesToRemove.length > 0) {
|
|
646
651
|
// eslint-disable-next-line no-await-in-loop
|
|
647
|
-
await
|
|
652
|
+
await this.runTogether(filesToRemove.splice(0, batch.concurrency).map(async (config) => {
|
|
648
653
|
await this.removeRemoteFile(config);
|
|
649
654
|
removed.push(config.id);
|
|
650
655
|
}));
|
|
@@ -659,15 +664,43 @@ export class RemoteAppCommand extends AppCommand {
|
|
|
659
664
|
}
|
|
660
665
|
async pushRemoteFile(config, log = true) {
|
|
661
666
|
const spinner = ora();
|
|
667
|
+
// A file the app already has keeps deploying under its name
|
|
668
|
+
const unloadable = isUnloadableComponentFile(config.filePath);
|
|
669
|
+
const pushedBefore = this.app?.configs?.some((remote) => remote.filePath === config.filePath);
|
|
670
|
+
if (unloadable && !pushedBefore) {
|
|
671
|
+
log &&
|
|
672
|
+
spinner.fail(`${config.filePath} would never load: name a component with letters, digits and dashes only`);
|
|
673
|
+
this.failedFiles.push(config.filePath);
|
|
674
|
+
return;
|
|
675
|
+
}
|
|
662
676
|
log && spinner.start(`Pushing ${config.filePath}`);
|
|
677
|
+
// A refused push has failed the spinner, and its reasons end the command
|
|
678
|
+
let isRefused = false;
|
|
663
679
|
try {
|
|
664
680
|
const postData = await config.postData();
|
|
665
681
|
if (!postData) {
|
|
666
682
|
spinner.stop();
|
|
667
683
|
return;
|
|
668
684
|
}
|
|
669
|
-
const result = await this.handleRequestErrors(async () => this.postConfigData(postData), () =>
|
|
685
|
+
const result = await this.handleRequestErrors(async () => this.postConfigData(postData), () => {
|
|
686
|
+
isRefused = true;
|
|
687
|
+
log && spinner.fail(`Error pushing ${config.filePath}`);
|
|
688
|
+
});
|
|
670
689
|
log && spinner.succeed(`${this.timestampStyled()} ${config.filePath}`);
|
|
690
|
+
// A config named in its values deploys under that name instead of its
|
|
691
|
+
// file's, and a model as its collection, whatever name it gives
|
|
692
|
+
const deployName = config.type === ConfigType.MODEL
|
|
693
|
+
? postData.values?.collection
|
|
694
|
+
: postData.name;
|
|
695
|
+
const kebabPath = deployName === config.name
|
|
696
|
+
? kebabCaseFilePath(config.filePath)
|
|
697
|
+
: undefined;
|
|
698
|
+
if (log && kebabPath) {
|
|
699
|
+
ora().warn(`${config.filePath} deploys as '${config.name}': use kebab-case: ${kebabPath}`);
|
|
700
|
+
}
|
|
701
|
+
if (log && unloadable) {
|
|
702
|
+
ora().warn(`${config.filePath} never loads: name a component with letters, digits and dashes only`);
|
|
703
|
+
}
|
|
671
704
|
return result;
|
|
672
705
|
}
|
|
673
706
|
catch (error) {
|
|
@@ -683,10 +716,31 @@ export class RemoteAppCommand extends AppCommand {
|
|
|
683
716
|
this.failedFiles.push(config.filePath);
|
|
684
717
|
return;
|
|
685
718
|
}
|
|
719
|
+
if (isRefused) {
|
|
720
|
+
// Each reason names its file, as a batch fails with all of them
|
|
721
|
+
this.error(error.message
|
|
722
|
+
.split('\n')
|
|
723
|
+
.map((reason) => `${config.filePath}: ${reason}`)
|
|
724
|
+
.join('\n'));
|
|
725
|
+
}
|
|
686
726
|
log && spinner.fail(error.message);
|
|
687
727
|
throw error;
|
|
688
728
|
}
|
|
689
729
|
}
|
|
730
|
+
// Runs pushes or removals together and, when some fail, fails once with
|
|
731
|
+
// the reasons of each of them, not only the first
|
|
732
|
+
async runTogether(tasks) {
|
|
733
|
+
const results = await Promise.allSettled(tasks);
|
|
734
|
+
const failures = results.flatMap((result) => result.status === 'rejected' ? [result.reason] : []);
|
|
735
|
+
if (failures.length === 1) {
|
|
736
|
+
throw failures[0];
|
|
737
|
+
}
|
|
738
|
+
if (failures.length > 1) {
|
|
739
|
+
this.error(failures
|
|
740
|
+
.map((failure) => failure?.message ?? String(failure))
|
|
741
|
+
.join('\n'));
|
|
742
|
+
}
|
|
743
|
+
}
|
|
690
744
|
async removeLocalFile(config, log = true) {
|
|
691
745
|
if (!config.fileData) {
|
|
692
746
|
return;
|
|
@@ -706,29 +760,6 @@ export class RemoteAppCommand extends AppCommand {
|
|
|
706
760
|
spinner.warn(`${this.timestampStyled()} ${style.strike(config.filePath)}`);
|
|
707
761
|
return result;
|
|
708
762
|
}
|
|
709
|
-
showErrors(errors, spinnerError) {
|
|
710
|
-
const actualErrors = Object.keys(errors).filter((err) => Object.prototype.hasOwnProperty.call(errors, err));
|
|
711
|
-
if (spinnerError) {
|
|
712
|
-
spinnerError();
|
|
713
|
-
}
|
|
714
|
-
else {
|
|
715
|
-
this.logError(inflect('Error', actualErrors.length));
|
|
716
|
-
}
|
|
717
|
-
for (const err of actualErrors) {
|
|
718
|
-
if (!errors[err]?.message)
|
|
719
|
-
continue;
|
|
720
|
-
// we need to display `id` instead of `private_id`
|
|
721
|
-
// developers don't have any knowledge of private ids
|
|
722
|
-
const displayErr = err === 'private_id' ? 'id' : err;
|
|
723
|
-
this.logError([
|
|
724
|
-
' -',
|
|
725
|
-
`${displayErr}:`.toLowerCase().replace(/^error:/, ''),
|
|
726
|
-
errors[err].message,
|
|
727
|
-
]
|
|
728
|
-
.filter(Boolean)
|
|
729
|
-
.join(' '));
|
|
730
|
-
}
|
|
731
|
-
}
|
|
732
763
|
storefrontFrontendUrl(storeId, storefront, branchId, preview = true) {
|
|
733
764
|
const storefrontSlug = storefront && preview
|
|
734
765
|
? `${storeId}--${storefront.id.toLowerCase()}--preview`
|
package/oclif.manifest.json
CHANGED
|
@@ -2088,12 +2088,13 @@
|
|
|
2088
2088
|
},
|
|
2089
2089
|
"trigger": {
|
|
2090
2090
|
"default": "",
|
|
2091
|
-
"description": "cron | model | route | workflow",
|
|
2091
|
+
"description": "cron | model | route | action | workflow",
|
|
2092
2092
|
"name": "trigger",
|
|
2093
2093
|
"options": [
|
|
2094
2094
|
"cron",
|
|
2095
2095
|
"model",
|
|
2096
2096
|
"route",
|
|
2097
|
+
"action",
|
|
2097
2098
|
"workflow"
|
|
2098
2099
|
]
|
|
2099
2100
|
}
|
|
@@ -2103,6 +2104,7 @@
|
|
|
2103
2104
|
"$ swell create function order-handler.ts model -e order.created,order.updated -y",
|
|
2104
2105
|
"$ swell create function cleanup.ts cron -s \"0 0 * * *\" -y",
|
|
2105
2106
|
"$ swell create function api-endpoint.ts route -r public -y",
|
|
2107
|
+
"$ swell create function mark-shipped.ts action -y",
|
|
2106
2108
|
"$ swell create function run-import.ts --type workflow -y",
|
|
2107
2109
|
"$ swell create function run-import.ts workflow -y"
|
|
2108
2110
|
],
|
|
@@ -2206,6 +2208,11 @@
|
|
|
2206
2208
|
"flag": "-r, --route=<option>",
|
|
2207
2209
|
"description": "Access: public (default) | private"
|
|
2208
2210
|
},
|
|
2211
|
+
{
|
|
2212
|
+
"name": "action",
|
|
2213
|
+
"flag": "",
|
|
2214
|
+
"description": "Runs from a dashboard action"
|
|
2215
|
+
},
|
|
2209
2216
|
{
|
|
2210
2217
|
"name": "workflow",
|
|
2211
2218
|
"flag": "--type=workflow",
|
|
@@ -2264,7 +2271,8 @@
|
|
|
2264
2271
|
"examples": [
|
|
2265
2272
|
"$ swell create model",
|
|
2266
2273
|
"$ swell create model products -y",
|
|
2267
|
-
"$ swell create model visitors -f title:string,photo:
|
|
2274
|
+
"$ swell create model visitors -f title:string,photo:file -l \"Visitors\" -y",
|
|
2275
|
+
"$ swell create model reviews -f owner:link:accounts,tags:array:string -y"
|
|
2268
2276
|
],
|
|
2269
2277
|
"flags": {
|
|
2270
2278
|
"app-path": {
|
|
@@ -2294,7 +2302,7 @@
|
|
|
2294
2302
|
},
|
|
2295
2303
|
"fields": {
|
|
2296
2304
|
"char": "f",
|
|
2297
|
-
"description": "Fields as id:type pairs (e.g., title:string,
|
|
2305
|
+
"description": "Fields as id:type pairs, id:link:<model> or id:array:<type> (e.g., title:string,owner:link:accounts)",
|
|
2298
2306
|
"name": "fields",
|
|
2299
2307
|
"default": "",
|
|
2300
2308
|
"hasDynamicHelp": false,
|
|
@@ -2671,6 +2679,68 @@
|
|
|
2671
2679
|
"webhook.js"
|
|
2672
2680
|
]
|
|
2673
2681
|
},
|
|
2682
|
+
"inspect:components": {
|
|
2683
|
+
"aliases": [],
|
|
2684
|
+
"args": {
|
|
2685
|
+
"identifier": {
|
|
2686
|
+
"description": "Resource identifier — see \"Identifier forms\" below.",
|
|
2687
|
+
"name": "identifier",
|
|
2688
|
+
"required": false
|
|
2689
|
+
}
|
|
2690
|
+
},
|
|
2691
|
+
"description": "Lists the components of installed apps, grouped by app. Pass --app=<slug> or --app=. (current swell.json) to scope.\n\nList output: paste-back key + the targets the component renders in and the extension it belongs to.\n\nPass an identifier to view a single component config as JSON.\n\nIdentifier forms:\n app.<app>.<name> — full paste-back key (list column 1)\n <name> — requires --app= scope\n <24-char id> — any scope\n",
|
|
2692
|
+
"examples": [
|
|
2693
|
+
"swell inspect components",
|
|
2694
|
+
"swell inspect components --app=my-app",
|
|
2695
|
+
"swell inspect components app.my-app.card",
|
|
2696
|
+
"swell inspect components card --app=my-app",
|
|
2697
|
+
"swell inspect components --live"
|
|
2698
|
+
],
|
|
2699
|
+
"flags": {
|
|
2700
|
+
"app": {
|
|
2701
|
+
"description": "Filter by app slug, or \".\" for current swell.json.",
|
|
2702
|
+
"name": "app",
|
|
2703
|
+
"hasDynamicHelp": false,
|
|
2704
|
+
"multiple": false,
|
|
2705
|
+
"type": "option"
|
|
2706
|
+
},
|
|
2707
|
+
"live": {
|
|
2708
|
+
"description": "Use live environment (default: test).",
|
|
2709
|
+
"name": "live",
|
|
2710
|
+
"allowNo": false,
|
|
2711
|
+
"type": "boolean"
|
|
2712
|
+
},
|
|
2713
|
+
"json": {
|
|
2714
|
+
"description": "Emit pure JSON without the \"Next steps\" footer. Detail mode only.",
|
|
2715
|
+
"name": "json",
|
|
2716
|
+
"allowNo": false,
|
|
2717
|
+
"type": "boolean"
|
|
2718
|
+
},
|
|
2719
|
+
"yes": {
|
|
2720
|
+
"char": "y",
|
|
2721
|
+
"description": "No-op; accepted for agent compatibility.",
|
|
2722
|
+
"hidden": true,
|
|
2723
|
+
"name": "yes",
|
|
2724
|
+
"allowNo": false,
|
|
2725
|
+
"type": "boolean"
|
|
2726
|
+
}
|
|
2727
|
+
},
|
|
2728
|
+
"hasDynamicHelp": false,
|
|
2729
|
+
"hiddenAliases": [],
|
|
2730
|
+
"id": "inspect:components",
|
|
2731
|
+
"pluginAlias": "@swell/cli",
|
|
2732
|
+
"pluginName": "@swell/cli",
|
|
2733
|
+
"pluginType": "core",
|
|
2734
|
+
"strict": true,
|
|
2735
|
+
"summary": "Components with their targets.",
|
|
2736
|
+
"isESM": true,
|
|
2737
|
+
"relativePath": [
|
|
2738
|
+
"dist",
|
|
2739
|
+
"commands",
|
|
2740
|
+
"inspect",
|
|
2741
|
+
"components.js"
|
|
2742
|
+
]
|
|
2743
|
+
},
|
|
2674
2744
|
"inspect:content": {
|
|
2675
2745
|
"aliases": [],
|
|
2676
2746
|
"args": {
|
|
@@ -2798,6 +2868,67 @@
|
|
|
2798
2868
|
"extensions.js"
|
|
2799
2869
|
]
|
|
2800
2870
|
},
|
|
2871
|
+
"inspect:frontend": {
|
|
2872
|
+
"aliases": [],
|
|
2873
|
+
"args": {
|
|
2874
|
+
"identifier": {
|
|
2875
|
+
"description": "Resource identifier — see \"Identifier forms\" below.",
|
|
2876
|
+
"name": "identifier",
|
|
2877
|
+
"required": false
|
|
2878
|
+
}
|
|
2879
|
+
},
|
|
2880
|
+
"description": "Lists the frontends of installed apps. Pass --app=<slug> or --app=. (current swell.json) to scope.\n\nList output: paste-back key + hosting (managed or self-hosted), framework, and the address the store serves the frontend at.\n\nPass an identifier to view an app's frontend as JSON: the app's frontend selection (package or self-hosted URL) with its installation and address.\n\nIdentifier forms:\n app.<app> — full paste-back key (list column 1)\n <app> — the app's slug\n <24-char id> — the app's id\n",
|
|
2881
|
+
"examples": [
|
|
2882
|
+
"swell inspect frontend",
|
|
2883
|
+
"swell inspect frontend --app=.",
|
|
2884
|
+
"swell inspect frontend app.my-app",
|
|
2885
|
+
"swell inspect frontend --live"
|
|
2886
|
+
],
|
|
2887
|
+
"flags": {
|
|
2888
|
+
"app": {
|
|
2889
|
+
"description": "Filter by app slug, or \".\" for current swell.json.",
|
|
2890
|
+
"name": "app",
|
|
2891
|
+
"hasDynamicHelp": false,
|
|
2892
|
+
"multiple": false,
|
|
2893
|
+
"type": "option"
|
|
2894
|
+
},
|
|
2895
|
+
"live": {
|
|
2896
|
+
"description": "Use live environment (default: test).",
|
|
2897
|
+
"name": "live",
|
|
2898
|
+
"allowNo": false,
|
|
2899
|
+
"type": "boolean"
|
|
2900
|
+
},
|
|
2901
|
+
"json": {
|
|
2902
|
+
"description": "Emit pure JSON without the \"Next steps\" footer. Detail mode only.",
|
|
2903
|
+
"name": "json",
|
|
2904
|
+
"allowNo": false,
|
|
2905
|
+
"type": "boolean"
|
|
2906
|
+
},
|
|
2907
|
+
"yes": {
|
|
2908
|
+
"char": "y",
|
|
2909
|
+
"description": "No-op; accepted for agent compatibility.",
|
|
2910
|
+
"hidden": true,
|
|
2911
|
+
"name": "yes",
|
|
2912
|
+
"allowNo": false,
|
|
2913
|
+
"type": "boolean"
|
|
2914
|
+
}
|
|
2915
|
+
},
|
|
2916
|
+
"hasDynamicHelp": false,
|
|
2917
|
+
"hiddenAliases": [],
|
|
2918
|
+
"id": "inspect:frontend",
|
|
2919
|
+
"pluginAlias": "@swell/cli",
|
|
2920
|
+
"pluginName": "@swell/cli",
|
|
2921
|
+
"pluginType": "core",
|
|
2922
|
+
"strict": true,
|
|
2923
|
+
"summary": "App frontends with hosting and address.",
|
|
2924
|
+
"isESM": true,
|
|
2925
|
+
"relativePath": [
|
|
2926
|
+
"dist",
|
|
2927
|
+
"commands",
|
|
2928
|
+
"inspect",
|
|
2929
|
+
"frontend.js"
|
|
2930
|
+
]
|
|
2931
|
+
},
|
|
2801
2932
|
"inspect:functions": {
|
|
2802
2933
|
"aliases": [],
|
|
2803
2934
|
"args": {
|
|
@@ -2807,7 +2938,7 @@
|
|
|
2807
2938
|
"required": false
|
|
2808
2939
|
}
|
|
2809
2940
|
},
|
|
2810
|
-
"description": "Lists functions across all apps, grouped by app. Pass --app=<slug> or --app=. (current swell.json) to scope.\n\nList output: paste-back key + status (disabled, trigger, next cron run, last
|
|
2941
|
+
"description": "Lists functions across all apps, grouped by app. Pass --app=<slug> or --app=. (current swell.json) to scope.\n\nList output: paste-back key + status (disabled or auto-disabled, failing since and failure count, the date it gets disabled, trigger, next cron run) and whether its app is inactive, which stops all of its functions. The detail view also shows the last failed delivery.\n\nPass an identifier to view a single record as JSON.\n\nIdentifier forms:\n app.<app>.<name> — full paste-back key (list column 1)\n <name> — requires --app= scope\n <24-char id> — any scope\n",
|
|
2811
2942
|
"examples": [
|
|
2812
2943
|
"swell inspect functions",
|
|
2813
2944
|
"swell inspect functions --app=my-app",
|
|
@@ -2871,7 +3002,9 @@
|
|
|
2871
3002
|
"swell inspect functions app.my-app.payment-sync",
|
|
2872
3003
|
"swell inspect workflow-runs --workflow app.my-app.run-import --status active",
|
|
2873
3004
|
"swell inspect notifications com.orders.receipt.v2",
|
|
2874
|
-
"swell inspect settings app.my-app"
|
|
3005
|
+
"swell inspect settings app.my-app",
|
|
3006
|
+
"swell inspect components --app=.",
|
|
3007
|
+
"swell inspect frontend app.my-app"
|
|
2875
3008
|
],
|
|
2876
3009
|
"flags": {},
|
|
2877
3010
|
"hasDynamicHelp": false,
|
|
@@ -3858,5 +3991,5 @@
|
|
|
3858
3991
|
]
|
|
3859
3992
|
}
|
|
3860
3993
|
},
|
|
3861
|
-
"version": "2.9.
|
|
3994
|
+
"version": "2.9.33"
|
|
3862
3995
|
}
|