@swell/cli 2.9.30 → 2.9.32
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/delete.d.ts +33 -0
- package/dist/commands/app/delete.js +179 -0
- package/dist/commands/app/frontend/deploy.d.ts +1 -0
- package/dist/commands/app/frontend/deploy.js +4 -0
- package/dist/commands/app/push.d.ts +2 -0
- package/dist/commands/app/push.js +63 -2
- package/dist/commands/create/webhook.js +7 -13
- package/dist/commands/schema.js +5 -0
- package/dist/help/custom-help.js +5 -1
- package/dist/lib/apps/actions.d.ts +45 -0
- package/dist/lib/apps/actions.js +114 -0
- package/dist/lib/apps/listing.d.ts +1 -0
- package/dist/lib/apps/listing.js +41 -0
- package/dist/lib/bundle.js +11 -39
- package/dist/lib/function-declarations.d.ts +8 -0
- package/dist/lib/function-declarations.js +27 -0
- package/dist/lib/swell-function-wrapper.js +8 -1
- 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 +11 -3
- package/dist/remote-app-command.d.ts +1 -1
- package/dist/remote-app-command.js +1 -4
- package/oclif.manifest.json +178 -5
- package/package.json +2 -1
package/dist/help/custom-help.js
CHANGED
|
@@ -280,7 +280,11 @@ export default class CustomHelp extends Help {
|
|
|
280
280
|
const value = ('helpValue' in flag && flag.helpValue) ||
|
|
281
281
|
('options' in flag && flag.options ? 'option' : 'value');
|
|
282
282
|
const suffix = flag.type === 'boolean' ? '' : `=<${value}>`;
|
|
283
|
-
|
|
283
|
+
const negatable = flag.type === 'boolean' && flag.allowNo;
|
|
284
|
+
return [
|
|
285
|
+
name,
|
|
286
|
+
`${shortChar}--${negatable ? '[no-]' : ''}${name}${suffix}`,
|
|
287
|
+
];
|
|
284
288
|
}));
|
|
285
289
|
const flagWidth = Math.max(30, ...[...flagLabels.values()].map((label) => label.length));
|
|
286
290
|
for (const [name, flag] of flagEntries) {
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
type Values = Record<string, any>;
|
|
2
|
+
export type ActionSource = 'bulk' | 'field' | 'list' | 'record' | 'settings';
|
|
3
|
+
/** An app function action, with what the dashboard sends to run it */
|
|
4
|
+
export interface DeclaredAction {
|
|
5
|
+
function: string;
|
|
6
|
+
id: string;
|
|
7
|
+
modal?: Values;
|
|
8
|
+
/** The query of the view whose records a bulk action runs on */
|
|
9
|
+
query?: Values;
|
|
10
|
+
/** The name of the settings config, for a settings action or action field */
|
|
11
|
+
settings?: string;
|
|
12
|
+
source: ActionSource;
|
|
13
|
+
view?: string;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* The function actions a content config declares.
|
|
17
|
+
*
|
|
18
|
+
* @param values the values of the content config
|
|
19
|
+
* @returns the actions
|
|
20
|
+
*/
|
|
21
|
+
export declare function getContentActions(values: Values): DeclaredAction[];
|
|
22
|
+
/**
|
|
23
|
+
* The function actions a settings config declares.
|
|
24
|
+
*
|
|
25
|
+
* @param values the values of the settings config
|
|
26
|
+
* @param settings the name of the settings config
|
|
27
|
+
* @returns the actions
|
|
28
|
+
*/
|
|
29
|
+
export declare function getSettingsActions(values: Values, settings: string): DeclaredAction[];
|
|
30
|
+
/**
|
|
31
|
+
* The ids of the fields of an action's modal, as the platform finds them to
|
|
32
|
+
* pass on only their values.
|
|
33
|
+
*
|
|
34
|
+
* @param modal the modal of the action
|
|
35
|
+
* @returns the field ids
|
|
36
|
+
*/
|
|
37
|
+
export declare function getModalFieldIds(modal: Values): string[];
|
|
38
|
+
/**
|
|
39
|
+
* A function name as the platform compares an action's `function` with it.
|
|
40
|
+
*
|
|
41
|
+
* @param name a function name
|
|
42
|
+
* @returns the name in the form the platform compares
|
|
43
|
+
*/
|
|
44
|
+
export declare function functionSlug(name: string): string;
|
|
45
|
+
export {};
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
function isFunctionAction(item) {
|
|
2
|
+
return (typeof item?.id === 'string' &&
|
|
3
|
+
typeof item?.function === 'string');
|
|
4
|
+
}
|
|
5
|
+
// The dashboard renders the fields of rows and groups in place
|
|
6
|
+
function flattenFields(fields) {
|
|
7
|
+
if (!Array.isArray(fields)) {
|
|
8
|
+
return [];
|
|
9
|
+
}
|
|
10
|
+
return fields.flatMap((field) => field?.type === 'field_row' || field?.type === 'field_group'
|
|
11
|
+
? flattenFields(field.fields)
|
|
12
|
+
: [field]);
|
|
13
|
+
}
|
|
14
|
+
function getActionFields(fields) {
|
|
15
|
+
return flattenFields(fields).filter((field) => field?.type === 'action');
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* The function actions a content config declares.
|
|
19
|
+
*
|
|
20
|
+
* @param values the values of the content config
|
|
21
|
+
* @returns the actions
|
|
22
|
+
*/
|
|
23
|
+
export function getContentActions(values) {
|
|
24
|
+
const views = Array.isArray(values?.views) ? values.views : [];
|
|
25
|
+
const actions = [];
|
|
26
|
+
for (const view of views) {
|
|
27
|
+
const source = view.type || (view.id === 'list' ? 'list' : 'record');
|
|
28
|
+
for (const action of [
|
|
29
|
+
...(view.actions || []),
|
|
30
|
+
...(view.extra_actions || []),
|
|
31
|
+
]) {
|
|
32
|
+
if (isFunctionAction(action)) {
|
|
33
|
+
actions.push({ ...pickAction(action), source, view: view.id });
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
for (const action of view.bulk_actions || []) {
|
|
37
|
+
if (isFunctionAction(action)) {
|
|
38
|
+
actions.push({
|
|
39
|
+
...pickAction(action),
|
|
40
|
+
...(view.query ? { query: view.query } : undefined),
|
|
41
|
+
source: 'bulk',
|
|
42
|
+
view: view.id,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
const fields = [
|
|
48
|
+
...(values?.fields || []),
|
|
49
|
+
...views.flatMap((view) => [
|
|
50
|
+
...(view.fields || []),
|
|
51
|
+
...(view.tabs || []).flatMap((tab) => tab?.fields || []),
|
|
52
|
+
]),
|
|
53
|
+
];
|
|
54
|
+
for (const field of getActionFields(fields)) {
|
|
55
|
+
if (isFunctionAction(field)) {
|
|
56
|
+
actions.push({ ...pickAction(field), source: 'field' });
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return actions;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* The function actions a settings config declares.
|
|
63
|
+
*
|
|
64
|
+
* @param values the values of the settings config
|
|
65
|
+
* @param settings the name of the settings config
|
|
66
|
+
* @returns the actions
|
|
67
|
+
*/
|
|
68
|
+
export function getSettingsActions(values, settings) {
|
|
69
|
+
const actions = [];
|
|
70
|
+
for (const action of values?.actions || []) {
|
|
71
|
+
if (isFunctionAction(action)) {
|
|
72
|
+
actions.push({ ...pickAction(action), settings, source: 'settings' });
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
for (const field of getActionFields(values?.fields)) {
|
|
76
|
+
if (isFunctionAction(field)) {
|
|
77
|
+
actions.push({ ...pickAction(field), settings, source: 'field' });
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return actions;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* The ids of the fields of an action's modal, as the platform finds them to
|
|
84
|
+
* pass on only their values.
|
|
85
|
+
*
|
|
86
|
+
* @param modal the modal of the action
|
|
87
|
+
* @returns the field ids
|
|
88
|
+
*/
|
|
89
|
+
export function getModalFieldIds(modal) {
|
|
90
|
+
return flattenFields(modal.fields)
|
|
91
|
+
.map((field) => field?.id)
|
|
92
|
+
.filter((id) => typeof id === 'string');
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* A function name as the platform compares an action's `function` with it.
|
|
96
|
+
*
|
|
97
|
+
* @param name a function name
|
|
98
|
+
* @returns the name in the form the platform compares
|
|
99
|
+
*/
|
|
100
|
+
export function functionSlug(name) {
|
|
101
|
+
return name
|
|
102
|
+
.replaceAll(/[^\s\w./:-]/g, '')
|
|
103
|
+
.replaceAll(/[\s./:_-]+/g, '-')
|
|
104
|
+
.replaceAll(/^-+|-+$/g, '')
|
|
105
|
+
.replaceAll(/([a-z])([A-Z])/g, '$1-$2')
|
|
106
|
+
.toLowerCase();
|
|
107
|
+
}
|
|
108
|
+
function pickAction(action) {
|
|
109
|
+
return {
|
|
110
|
+
function: action.function,
|
|
111
|
+
id: action.id,
|
|
112
|
+
...(action.modal ? { modal: action.modal } : undefined),
|
|
113
|
+
};
|
|
114
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function warnListingSources(appPath: string, config: Record<string, any>, warn: (message: string) => void): void;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { statSync } from 'node:fs';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
const LISTING_SOURCE_FIELDS = ['logo_src', 'preview_src', 'preview_mobile_src'];
|
|
4
|
+
const LISTING_SOURCE_LISTS = ['images', 'highlights'];
|
|
5
|
+
// The listing takes its full description from DESCRIPTION.md and each image
|
|
6
|
+
// from the asset whose path from the app root is the swell.json value as
|
|
7
|
+
// written, so values that point elsewhere do nothing. The platform takes an
|
|
8
|
+
// empty value as unset.
|
|
9
|
+
export function warnListingSources(appPath, config, warn) {
|
|
10
|
+
if (config.full_description !== undefined) {
|
|
11
|
+
warn('swell.json sets full_description, which push does not apply: the full description is the content of DESCRIPTION.md at the app root.');
|
|
12
|
+
}
|
|
13
|
+
const sources = [
|
|
14
|
+
...LISTING_SOURCE_FIELDS.map((field) => [
|
|
15
|
+
field,
|
|
16
|
+
config[field],
|
|
17
|
+
]),
|
|
18
|
+
...LISTING_SOURCE_LISTS.flatMap((list) => (Array.isArray(config[list]) ? config[list] : []).map((item, index) => [
|
|
19
|
+
`${list}[${index}].image_src`,
|
|
20
|
+
item?.image_src,
|
|
21
|
+
])),
|
|
22
|
+
];
|
|
23
|
+
for (const [field, source] of sources) {
|
|
24
|
+
if (typeof source !== 'string' || source === '') {
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
const filePath = path.posix.normalize(source.replaceAll('\\', '/'));
|
|
28
|
+
const stats = statSync(path.join(appPath, filePath), {
|
|
29
|
+
throwIfNoEntry: false,
|
|
30
|
+
});
|
|
31
|
+
if (!stats?.isFile()) {
|
|
32
|
+
warn(`swell.json ${field} names ${source}, which the app does not have: the listing gets no image from it.`);
|
|
33
|
+
}
|
|
34
|
+
else if (!filePath.startsWith('assets/')) {
|
|
35
|
+
warn(`swell.json ${field} names ${source}, which is not in assets/: the listing gets no image from it. Move the file to assets/ and write assets/${path.posix.basename(filePath)}.`);
|
|
36
|
+
}
|
|
37
|
+
else if (filePath !== source) {
|
|
38
|
+
warn(`swell.json ${field} names ${source}, which is not the file's path from the app root: the listing gets no image from it. Write ${filePath}.`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
package/dist/lib/bundle.js
CHANGED
|
@@ -60,7 +60,7 @@ export async function bundleFunction(filePath) {
|
|
|
60
60
|
}
|
|
61
61
|
export async function bundleWorkflow(filePath, analysis) {
|
|
62
62
|
try {
|
|
63
|
-
const entrySource = getSwellWorkflowEntrypoint(filePath);
|
|
63
|
+
const entrySource = await getSwellWorkflowEntrypoint(filePath);
|
|
64
64
|
const buildResult = await esbuild.build({
|
|
65
65
|
bundle: true,
|
|
66
66
|
stdin: {
|
|
@@ -130,13 +130,22 @@ async function getSwellFunctionWrapper() {
|
|
|
130
130
|
const __dirname = path.dirname(__filename);
|
|
131
131
|
return fs.readFile(path.join(__dirname, './swell-function-wrapper.js'), 'utf8');
|
|
132
132
|
}
|
|
133
|
-
|
|
133
|
+
// The entry defines these functions itself, so the bundle names no file of
|
|
134
|
+
// the CLI and the workflow module exports only its entrypoint
|
|
135
|
+
async function getSwellWorkflowRequest() {
|
|
136
|
+
const source = await fs.readFile(path.join(path.dirname(fileURLToPath(import.meta.url)), './swell-workflow-request.js'), 'utf8');
|
|
137
|
+
return source.replaceAll(/^export /gm, '');
|
|
138
|
+
}
|
|
139
|
+
async function getSwellWorkflowEntrypoint(filePath) {
|
|
134
140
|
const workflowImport = `./${path.basename(filePath)}`;
|
|
141
|
+
const workflowRequest = await getSwellWorkflowRequest();
|
|
135
142
|
return `
|
|
136
143
|
import { WorkflowEntrypoint } from 'cloudflare:workers';
|
|
137
144
|
import { NonRetryableError } from 'cloudflare:workflows';
|
|
138
145
|
import UserWorkflow from ${JSON.stringify(workflowImport)};
|
|
139
146
|
|
|
147
|
+
${workflowRequest}
|
|
148
|
+
|
|
140
149
|
const RUNTIME_PROXY_PATH = '/:workflows/runtime/request';
|
|
141
150
|
const RUNTIME_TOKEN_PATTERN = /\\bwf_rt_[0-9A-Za-z]+_[0-9A-Za-z]{12,}\\b/g;
|
|
142
151
|
|
|
@@ -210,38 +219,6 @@ function getInvocation(event) {
|
|
|
210
219
|
return payload;
|
|
211
220
|
}
|
|
212
221
|
|
|
213
|
-
function buildWorkflowRequest(invocation, proxy) {
|
|
214
|
-
const runtime = invocation.runtime;
|
|
215
|
-
|
|
216
|
-
return {
|
|
217
|
-
id: runtime.request_id,
|
|
218
|
-
appId: runtime.app_slug || runtime.app_id,
|
|
219
|
-
environmentId: runtimeEnvironmentId(runtime),
|
|
220
|
-
store: {
|
|
221
|
-
id: runtime.store_id,
|
|
222
|
-
},
|
|
223
|
-
app: {
|
|
224
|
-
admin_url: runtime.app_admin_url,
|
|
225
|
-
},
|
|
226
|
-
data: invocation.data,
|
|
227
|
-
workflow: {
|
|
228
|
-
workflow_id: runtime.workflow_id,
|
|
229
|
-
workflow_name: runtime.workflow_name,
|
|
230
|
-
workflow_instance_id: runtime.workflow_instance_id,
|
|
231
|
-
trigger: runtime.trigger,
|
|
232
|
-
request_id: runtime.request_id,
|
|
233
|
-
},
|
|
234
|
-
isLocalDev: false,
|
|
235
|
-
swell: {
|
|
236
|
-
get: (path, data) => proxy.api('get', path, data),
|
|
237
|
-
post: (path, data) => proxy.api('post', path, data),
|
|
238
|
-
put: (path, data) => proxy.api('put', path, data),
|
|
239
|
-
delete: (path, data) => proxy.api('delete', path, data),
|
|
240
|
-
settings: () => proxy.settings(),
|
|
241
|
-
},
|
|
242
|
-
};
|
|
243
|
-
}
|
|
244
|
-
|
|
245
222
|
function buildWorkflowStep(cloudflareStep, proxy) {
|
|
246
223
|
return Object.freeze({
|
|
247
224
|
do(name, optionsOrCallback, maybeCallback) {
|
|
@@ -361,11 +338,6 @@ function getRuntimeApiHost(runtime) {
|
|
|
361
338
|
return apiHost.replace(/\\/$/, '');
|
|
362
339
|
}
|
|
363
340
|
|
|
364
|
-
// 'test' in the test environment, null in live
|
|
365
|
-
function runtimeEnvironmentId(runtime) {
|
|
366
|
-
return runtime.environment_id || null;
|
|
367
|
-
}
|
|
368
|
-
|
|
369
341
|
function runtimeIdentity(runtime) {
|
|
370
342
|
return {
|
|
371
343
|
app_id: runtime.app_id,
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The types an app's functions type-check against: the app's own
|
|
3
|
+
* @swell/app-types when it has the package, otherwise the CLI's.
|
|
4
|
+
*
|
|
5
|
+
* @param directory a directory of the app
|
|
6
|
+
* @returns the declarations, after a line naming the package version
|
|
7
|
+
*/
|
|
8
|
+
export declare function getFunctionDeclarations(directory: string): Promise<string>;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import * as fs from 'node:fs/promises';
|
|
2
|
+
import { createRequire } from 'node:module';
|
|
3
|
+
import * as path from 'node:path';
|
|
4
|
+
/**
|
|
5
|
+
* The types an app's functions type-check against: the app's own
|
|
6
|
+
* @swell/app-types when it has the package, otherwise the CLI's.
|
|
7
|
+
*
|
|
8
|
+
* @param directory a directory of the app
|
|
9
|
+
* @returns the declarations, after a line naming the package version
|
|
10
|
+
*/
|
|
11
|
+
export async function getFunctionDeclarations(directory) {
|
|
12
|
+
const file = resolveAppTypes(path.join(directory, 'package.json')) ??
|
|
13
|
+
resolveAppTypes(import.meta.url);
|
|
14
|
+
if (!file) {
|
|
15
|
+
throw new Error('@swell/app-types is not installed');
|
|
16
|
+
}
|
|
17
|
+
const { version } = JSON.parse(await fs.readFile(path.join(path.dirname(file), 'package.json'), 'utf8'));
|
|
18
|
+
return `// @swell/app-types ${version}\n${await fs.readFile(file, 'utf8')}`;
|
|
19
|
+
}
|
|
20
|
+
function resolveAppTypes(from) {
|
|
21
|
+
try {
|
|
22
|
+
return createRequire(from).resolve('@swell/app-types/index.d.ts');
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -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
|
}
|
|
@@ -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);
|
|
@@ -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
|
@@ -10,6 +10,7 @@ import { componentsToRebuild } from './lib/apps/components.js';
|
|
|
10
10
|
import { buildFrontendPackage, getManagedFrontendFramework, } from './lib/apps/frontend-package.js';
|
|
11
11
|
import { warnFrontendRouteCollisions } from './lib/apps/frontend-routes.js';
|
|
12
12
|
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 { warnListingSources } from './lib/apps/listing.js';
|
|
13
14
|
import { getGlobIgnorePathsChecker } from './lib/apps/paths.js';
|
|
14
15
|
import { slugFromApp } from './lib/apps/slug.js';
|
|
15
16
|
import { getLocalInputs } from './lib/bundle.js';
|
|
@@ -119,6 +120,10 @@ export class PushAppCommand extends RemoteAppCommand {
|
|
|
119
120
|
}
|
|
120
121
|
}
|
|
121
122
|
};
|
|
123
|
+
// The platform applies the listing in swell.json when the file is pushed
|
|
124
|
+
pushesSwellJson(file) {
|
|
125
|
+
return !file;
|
|
126
|
+
}
|
|
122
127
|
showFrontendMigrationError() {
|
|
123
128
|
return this.error(style.funcWarn(`⚠️ Your frontend directory exists but appears to use an older structure.\n` +
|
|
124
129
|
`Swell CLI v2.1.0+ requires a Workers-based frontend with package.json.\n\n` +
|
|
@@ -330,7 +335,7 @@ export class PushAppCommand extends RemoteAppCommand {
|
|
|
330
335
|
this.error(`Error deploying frontend: ${error.message}`);
|
|
331
336
|
}
|
|
332
337
|
}
|
|
333
|
-
async ensureAppExists(file, shouldCreate = true
|
|
338
|
+
async ensureAppExists(file, shouldCreate = true) {
|
|
334
339
|
const klass = this.ctor;
|
|
335
340
|
const currentStore = localConfig.getDefaultStore();
|
|
336
341
|
await this.ensureLoggedIn();
|
|
@@ -356,10 +361,13 @@ export class PushAppCommand extends RemoteAppCommand {
|
|
|
356
361
|
this.app = app;
|
|
357
362
|
return true;
|
|
358
363
|
}
|
|
364
|
+
if (this.pushesSwellJson(file)) {
|
|
365
|
+
warnListingSources(this.appPath, this.swellConfig.store, (message) => this.warn(message));
|
|
366
|
+
}
|
|
359
367
|
// If app exists but owned by another client, create a new instance
|
|
360
368
|
if (app?.client_id && app.client_id !== currentStore) {
|
|
361
369
|
// Create a new app
|
|
362
|
-
this.app = await this.getCreateUpdateApp(
|
|
370
|
+
this.app = await this.getCreateUpdateApp();
|
|
363
371
|
if (!this.app) {
|
|
364
372
|
return false;
|
|
365
373
|
}
|
|
@@ -373,7 +381,7 @@ export class PushAppCommand extends RemoteAppCommand {
|
|
|
373
381
|
}
|
|
374
382
|
else {
|
|
375
383
|
// Update existing app if not targeting a file
|
|
376
|
-
this.app = await this.getCreateUpdateApp(app
|
|
384
|
+
this.app = await this.getCreateUpdateApp(app);
|
|
377
385
|
}
|
|
378
386
|
return true;
|
|
379
387
|
}
|
|
@@ -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
|
|
@@ -176,7 +176,7 @@ export class RemoteAppCommand extends AppCommand {
|
|
|
176
176
|
adminPath: `/apps/${this.app.id}/configs/${configId}/data`,
|
|
177
177
|
}, { query: { ...(themeId ? { theme_id: themeId } : undefined) } });
|
|
178
178
|
}
|
|
179
|
-
async getCreateUpdateApp(updateApp
|
|
179
|
+
async getCreateUpdateApp(updateApp) {
|
|
180
180
|
const spinner = ora();
|
|
181
181
|
let sourceApp;
|
|
182
182
|
try {
|
|
@@ -205,9 +205,6 @@ export class RemoteAppCommand extends AppCommand {
|
|
|
205
205
|
? { private_id: this.swellConfig.store.id }
|
|
206
206
|
: undefined),
|
|
207
207
|
...(sourceApp?.id ? { source_id: sourceApp.id } : undefined),
|
|
208
|
-
// Overwrite name/description/logo_icon on the server
|
|
209
|
-
// instead of only filling them in when empty
|
|
210
|
-
...(force ? { $force_meta: true } : undefined),
|
|
211
208
|
};
|
|
212
209
|
const appLabel = this.app.type === 'theme' ? 'theme' : 'app';
|
|
213
210
|
let app = updateApp || {};
|