@appsemble/cli 0.20.44 → 0.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +145 -3
- package/commands/app/create.d.ts +2 -2
- package/commands/app/update.d.ts +2 -2
- package/commands/organization/create.d.ts +1 -1
- package/commands/organization/update.d.ts +1 -1
- package/commands/serve.d.ts +15 -0
- package/commands/serve.js +265 -0
- package/index.js +2 -0
- package/lib/app.d.ts +4 -4
- package/lib/app.js +14 -5
- package/lib/block.d.ts +2 -2
- package/lib/block.js +17 -3
- package/lib/block.test.js +6 -4
- package/lib/coercers.d.ts +2 -2
- package/lib/createServers.d.ts +19 -0
- package/lib/createServers.js +57 -0
- package/lib/getBlockConfigFromTypeScript.js +1 -0
- package/lib/getBlockConfigFromTypeScript.test.js +2 -2
- package/lib/organization.d.ts +1 -1
- package/package.json +23 -6
- package/server/controllers/action.d.ts +6 -0
- package/server/controllers/action.js +8 -0
- package/server/controllers/appMembers.d.ts +2 -0
- package/server/controllers/appMembers.js +4 -0
- package/server/controllers/appMessages.d.ts +2 -0
- package/server/controllers/appMessages.js +4 -0
- package/server/controllers/assets.d.ts +4 -0
- package/server/controllers/assets.js +6 -0
- package/server/controllers/index.d.ts +7 -0
- package/server/controllers/index.js +8 -0
- package/server/controllers/oauth2Provider.d.ts +2 -0
- package/server/controllers/oauth2Provider.js +4 -0
- package/server/controllers/resources.d.ts +7 -0
- package/server/controllers/resources.js +9 -0
- package/server/controllers/teams.d.ts +2 -0
- package/server/controllers/teams.js +4 -0
- package/server/db/index.d.ts +2 -0
- package/server/db/index.js +10 -0
- package/server/db/methods.d.ts +11 -0
- package/server/db/methods.js +232 -0
- package/server/db/methods.test.js +365 -0
- package/server/models/Resource.d.ts +14 -0
- package/server/models/Resource.js +23 -0
- package/server/models/Theme.d.ts +17 -0
- package/server/models/Theme.js +11 -0
- package/server/options/applyAppServiceSecrets.d.ts +3 -0
- package/server/options/applyAppServiceSecrets.js +2 -0
- package/server/options/checkRole.d.ts +1 -0
- package/server/options/checkRole.js +2 -0
- package/server/options/checkRole.test.d.ts +1 -0
- package/server/options/checkRole.test.js +12 -0
- package/server/options/createAppAsset.d.ts +2 -0
- package/server/options/createAppAsset.js +15 -0
- package/server/options/createAppResourcesWithAssets.d.ts +3 -0
- package/server/options/createAppResourcesWithAssets.js +18 -0
- package/server/options/createSettings.d.ts +2 -0
- package/server/options/createSettings.js +15 -0
- package/server/options/createTheme.d.ts +2 -0
- package/server/options/createTheme.js +5 -0
- package/server/options/deleteAppAsset.d.ts +2 -0
- package/server/options/deleteAppAsset.js +15 -0
- package/server/options/deleteAppResource.d.ts +2 -0
- package/server/options/deleteAppResource.js +5 -0
- package/server/options/email.d.ts +1 -0
- package/server/options/email.js +2 -0
- package/server/options/getApp.d.ts +3 -0
- package/server/options/getApp.js +5 -0
- package/server/options/getAppAssets.d.ts +2 -0
- package/server/options/getAppAssets.js +12 -0
- package/server/options/getAppBlockStyles.d.ts +2 -0
- package/server/options/getAppBlockStyles.js +26 -0
- package/server/options/getAppDetails.d.ts +2 -0
- package/server/options/getAppDetails.js +8 -0
- package/server/options/getAppIcon.d.ts +3 -0
- package/server/options/getAppIcon.js +6 -0
- package/server/options/getAppMembers.d.ts +3 -0
- package/server/options/getAppMembers.js +4 -0
- package/server/options/getAppMessages.d.ts +3 -0
- package/server/options/getAppMessages.js +4 -0
- package/server/options/getAppResource.d.ts +3 -0
- package/server/options/getAppResource.js +5 -0
- package/server/options/getAppResources.d.ts +3 -0
- package/server/options/getAppResources.js +5 -0
- package/server/options/getAppScreenshots.d.ts +2 -0
- package/server/options/getAppScreenshots.js +20 -0
- package/server/options/getAppStyles.d.ts +2 -0
- package/server/options/getAppStyles.js +7 -0
- package/server/options/getAppTeams.d.ts +2 -0
- package/server/options/getAppTeams.js +4 -0
- package/server/options/getAppUrl.d.ts +2 -0
- package/server/options/getAppUrl.js +5 -0
- package/server/options/getAppUserInfo.d.ts +3 -0
- package/server/options/getAppUserInfo.js +4 -0
- package/server/options/getBlockAsset.d.ts +2 -0
- package/server/options/getBlockAsset.js +28 -0
- package/server/options/getBlockAssetsPaths.d.ts +2 -0
- package/server/options/getBlockAssetsPaths.js +8 -0
- package/server/options/getBlockMessages.d.ts +2 -0
- package/server/options/getBlockMessages.js +4 -0
- package/server/options/getCsp.d.ts +2 -0
- package/server/options/getCsp.js +13 -0
- package/server/options/getDbUpdated.d.ts +1 -0
- package/server/options/getDbUpdated.js +2 -0
- package/server/options/getHost.d.ts +2 -0
- package/server/options/getHost.js +5 -0
- package/server/options/getTheme.d.ts +2 -0
- package/server/options/getTheme.js +8 -0
- package/server/options/index.d.ts +35 -0
- package/server/options/index.js +36 -0
- package/server/options/options.d.ts +2 -0
- package/server/options/options.js +39 -0
- package/server/options/parseQuery.d.ts +2 -0
- package/server/options/parseQuery.js +82 -0
- package/server/options/parseQuery.test.d.ts +1 -0
- package/server/options/parseQuery.test.js +43 -0
- package/server/options/reloadUser.d.ts +1 -0
- package/server/options/reloadUser.js +2 -0
- package/server/options/sendNotifications.d.ts +1 -0
- package/server/options/sendNotifications.js +2 -0
- package/server/options/updateAppResource.d.ts +3 -0
- package/server/options/updateAppResource.js +5 -0
- package/server/options/verifyResourceActionPermission.d.ts +1 -0
- package/server/options/verifyResourceActionPermission.js +2 -0
- package/server/routes/appRouter/index.d.ts +2 -0
- package/server/routes/appRouter/index.js +4 -0
- package/vitest.config.d.ts +2 -0
- package/vitest.setup.d.ts +1 -0
- package/{jest.setup.js → vitest.setup.js} +1 -1
- /package/{jest.setup.d.ts → server/db/methods.test.d.ts} +0 -0
package/README.md
CHANGED
|
@@ -1,20 +1,24 @@
|
|
|
1
|
-
#  Appsemble CLI
|
|
2
2
|
|
|
3
3
|
> Manage apps and blocks from the command line.
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/@appsemble/cli)
|
|
6
|
-
[](https://gitlab.com/appsemble/appsemble/-/releases/0.21.0)
|
|
7
7
|
[](https://prettier.io)
|
|
8
8
|
|
|
9
9
|
## Table of Contents
|
|
10
10
|
|
|
11
11
|
- [Installation](#installation)
|
|
12
12
|
- [Usage](#usage)
|
|
13
|
+
- [Development server](#development-server)
|
|
13
14
|
- [Authentication](#authentication)
|
|
14
15
|
- [Organizations](#organizations)
|
|
15
16
|
- [Apps](#apps)
|
|
16
17
|
- [Blocks](#blocks)
|
|
18
|
+
- [Teams](#teams)
|
|
17
19
|
- [Assets](#assets)
|
|
20
|
+
- [Resources](#resources)
|
|
21
|
+
- [Cronjobs](#cronjobs)
|
|
18
22
|
- [License](#license)
|
|
19
23
|
|
|
20
24
|
## Installation
|
|
@@ -33,6 +37,76 @@ appsemble --help
|
|
|
33
37
|
|
|
34
38
|
Every subcommand also supports the `--help` flag.
|
|
35
39
|
|
|
40
|
+
### Development server
|
|
41
|
+
|
|
42
|
+
The development server can create an app from a specified folder containing an `app-definition.yml`
|
|
43
|
+
file. It will check what blocks are needed for the app and will try to load them from the local
|
|
44
|
+
workspaces, listed in the `package.json` file in the root of the project, if they are present. This
|
|
45
|
+
way, all default Appsemble blocks shipped with Appsemble are loaded automatically.
|
|
46
|
+
|
|
47
|
+
Once the development server is started, making a change to a block’s code or styles will reflect in
|
|
48
|
+
the browser immediately after refreshing the page, without the need of increasing the block’s
|
|
49
|
+
version. Running docker database containers, creating a user account and creating an organization
|
|
50
|
+
are not needed.
|
|
51
|
+
|
|
52
|
+
The Appsemble CLI can be used to start the development server. For example, the following command
|
|
53
|
+
serves `my-app`:
|
|
54
|
+
|
|
55
|
+
```sh
|
|
56
|
+
appsemble serve <path-to-app-directory>
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
This will serve the app on `http://localhost:9090`.
|
|
60
|
+
|
|
61
|
+
A different port can be specified with the `--port` parameter.
|
|
62
|
+
|
|
63
|
+
The following option allows you to view the app with a specified role:
|
|
64
|
+
|
|
65
|
+
```sh
|
|
66
|
+
appsemble serve <path-to-app-directory> --user-role test
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
The following option allows you to view the app as a Manager of a team:
|
|
70
|
+
|
|
71
|
+
```sh
|
|
72
|
+
appsemble serve <path-to-app-directory> --team-role Manager
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
App data is stored within a `db.json` file in your machine’s cache directory. Each app has their own
|
|
76
|
+
directory `<my-app>`.
|
|
77
|
+
|
|
78
|
+
```sh
|
|
79
|
+
MacOS - /Users/<my-name>/Library/Caches/appsemble/<my-app>
|
|
80
|
+
Linux - /home/<my-name>/.cache/appsemble/<my-app>
|
|
81
|
+
Windows - C:\Users\<my-name>\AppData\Local\appsemble\Cache\<my-app>
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
App assets will be served from the local file system.
|
|
85
|
+
|
|
86
|
+
The development server will automatically fetch all blocks that are needed for the served app but
|
|
87
|
+
are missing from the local workspaces. These are typically third-party or proprietary blocks. The
|
|
88
|
+
development server will use `https://appsemble.app` as the default remote server to fetch blocks
|
|
89
|
+
from. The following option allows you to specify a different remote server:
|
|
90
|
+
|
|
91
|
+
```sh
|
|
92
|
+
appsemble serve <path-to-app-directory> --remote <remote>
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
The development server will use the corresponding block directory in your machine’s cache directory
|
|
96
|
+
to store and read block manifests and assets.
|
|
97
|
+
|
|
98
|
+
```sh
|
|
99
|
+
MacOS - /Users/<my-name>/Library/Caches/appsemble/blocks/<organisation>/<block-name>/<block-version>
|
|
100
|
+
Linux - /home/<my-name>/.cache/appsemble/blocks/<organisation>/<block-name>/<block-version>
|
|
101
|
+
Windows - C:\Users\<my-name>\AppData\Local\appsemble\Cache\blocks\<organisation>\<block-name>\<block-version>
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
You can overwrite the existing block cache with the following option:
|
|
105
|
+
|
|
106
|
+
```sh
|
|
107
|
+
appsemble serve <path-to-app-directory> --remote <remote> --overwrite-block-cache
|
|
108
|
+
```
|
|
109
|
+
|
|
36
110
|
### Authentication
|
|
37
111
|
|
|
38
112
|
The Appsemble CLI uses OAuth2 client credentials to authenticate to the Appsemble server.
|
|
@@ -69,6 +143,12 @@ The Appsemble CLI can be used to create and update apps. For example, to create
|
|
|
69
143
|
appsemble app create apps/my-app
|
|
70
144
|
```
|
|
71
145
|
|
|
146
|
+
Similarly, to update an app using an app directory, run the following command:
|
|
147
|
+
|
|
148
|
+
```sh
|
|
149
|
+
appsemble app update --id <app-id> <path/to/updated app>.
|
|
150
|
+
```
|
|
151
|
+
|
|
72
152
|
For a more in depth explanation of how to build apps, use our
|
|
73
153
|
[app building guide](https://appsemble.app/docs/guide).
|
|
74
154
|
|
|
@@ -94,6 +174,15 @@ npm install webpack@webpack-4 @appsemble/webpack-config
|
|
|
94
174
|
For a more in-depth explanation of how to build apps, use our
|
|
95
175
|
[block development guide](https://appsemble.app/docs/development/developing-blocks).
|
|
96
176
|
|
|
177
|
+
### Teams
|
|
178
|
+
|
|
179
|
+
The Appsemble CLI can be used to manage teams for apps. For example, the following command creates a
|
|
180
|
+
new team named `My Team`:
|
|
181
|
+
|
|
182
|
+
```sh
|
|
183
|
+
appsemble team create 'My Team' --app-id 1 --context development
|
|
184
|
+
```
|
|
185
|
+
|
|
97
186
|
### Assets
|
|
98
187
|
|
|
99
188
|
The Appsemble CLI can be used to upload assets from disk. For example, the following command creates
|
|
@@ -103,7 +192,60 @@ an asset named `example-asset`:
|
|
|
103
192
|
appsemble asset create --app-id 1 path/to/example-asset.png
|
|
104
193
|
```
|
|
105
194
|
|
|
195
|
+
### Resources
|
|
196
|
+
|
|
197
|
+
The Appsemble CLI can be used to create a resource from a JSON file or directory:
|
|
198
|
+
|
|
199
|
+
```json
|
|
200
|
+
[
|
|
201
|
+
{
|
|
202
|
+
"title": "My Resource",
|
|
203
|
+
"description": "This record is an example."
|
|
204
|
+
}
|
|
205
|
+
]
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
```sh
|
|
209
|
+
appsemble resource create --app-id 1 --context development --app path/to/my-app my-resource path/to/resources/my-resource.json
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
```sh
|
|
213
|
+
appsemble resource create --app-id 1 --context development --app path/to/my-app my-resource path/to/resources/*
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
And resources can also be updated when they contain an id in the JSON file.
|
|
217
|
+
|
|
218
|
+
```json
|
|
219
|
+
[
|
|
220
|
+
{
|
|
221
|
+
"id": 1,
|
|
222
|
+
"title": "My Updated Resource",
|
|
223
|
+
"description": "This will be the updated content of the first my-resource record."
|
|
224
|
+
}
|
|
225
|
+
]
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
```sh
|
|
229
|
+
appsemble resource update --app-id 1 --context development --app path/to/my-app my-resource path/to/resources/*
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
### Cronjobs
|
|
233
|
+
|
|
234
|
+
The Appsemble CLI can be used to run app cronjobs. The following command runs all cronjobs that were
|
|
235
|
+
scheduled to run in the past 5 minutes:
|
|
236
|
+
|
|
237
|
+
```sh
|
|
238
|
+
appsemble run-cronjobs
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
How often jobs are run (more accurately how far back the job can be scheduled for it to run) the
|
|
242
|
+
time interval (in minutes) can also be set with:
|
|
243
|
+
|
|
244
|
+
```sh
|
|
245
|
+
appsemble run-cronjobs --interval 30
|
|
246
|
+
```
|
|
247
|
+
|
|
106
248
|
## License
|
|
107
249
|
|
|
108
|
-
[LGPL-3.0-only](https://gitlab.com/appsemble/appsemble/-/blob/0.
|
|
250
|
+
[LGPL-3.0-only](https://gitlab.com/appsemble/appsemble/-/blob/0.21.0/LICENSE.md) ©
|
|
109
251
|
[Appsemble](https://appsemble.com)
|
package/commands/app/create.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
/// <reference types="node" />
|
|
2
|
-
/// <reference types="node" />
|
|
1
|
+
/// <reference types="node" resolution-mode="require"/>
|
|
2
|
+
/// <reference types="node" resolution-mode="require"/>
|
|
3
3
|
import { type ReadStream } from 'node:fs';
|
|
4
4
|
import { type AppVisibility } from '@appsemble/types';
|
|
5
5
|
import { type Argv } from 'yargs';
|
package/commands/app/update.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
/// <reference types="node" />
|
|
2
|
-
/// <reference types="node" />
|
|
1
|
+
/// <reference types="node" resolution-mode="require"/>
|
|
2
|
+
/// <reference types="node" resolution-mode="require"/>
|
|
3
3
|
import { type ReadStream } from 'node:fs';
|
|
4
4
|
import { type AppVisibility } from '@appsemble/types';
|
|
5
5
|
import { type Argv } from 'yargs';
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type Argv } from 'yargs';
|
|
2
|
+
import { type BaseArguments } from '../types.js';
|
|
3
|
+
interface ServeArguments extends BaseArguments {
|
|
4
|
+
path: string;
|
|
5
|
+
port: number;
|
|
6
|
+
'api-port': number;
|
|
7
|
+
'user-role': string;
|
|
8
|
+
'team-role': string;
|
|
9
|
+
'overwrite-block-cache': boolean;
|
|
10
|
+
}
|
|
11
|
+
export declare const command = "serve path";
|
|
12
|
+
export declare const description = "Serve an app with a local development server.";
|
|
13
|
+
export declare function builder(yargs: Argv): Argv<any>;
|
|
14
|
+
export declare function handler(argv: ServeArguments): Promise<void>;
|
|
15
|
+
export {};
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
import { createWriteStream, existsSync } from 'node:fs';
|
|
2
|
+
import { mkdir, readFile } from 'node:fs/promises';
|
|
3
|
+
import http from 'node:http';
|
|
4
|
+
import { basename, extname, join, parse } from 'node:path';
|
|
5
|
+
import { Readable } from 'node:stream';
|
|
6
|
+
import { AppsembleError, logger, opendirSafe, readData, writeData, } from '@appsemble/node-utils';
|
|
7
|
+
import { asciiLogo, getAppBlocks, normalize, parseBlockName, } from '@appsemble/utils';
|
|
8
|
+
import axios from 'axios';
|
|
9
|
+
import csvToJson from 'csvtojson';
|
|
10
|
+
import FormData from 'form-data';
|
|
11
|
+
import globalCacheDir from 'global-cache-dir';
|
|
12
|
+
import { traverseAppDirectory } from '../lib/app.js';
|
|
13
|
+
import { buildBlock, getBlockConfig, makePayload } from '../lib/block.js';
|
|
14
|
+
import { createApiServer, createStaticServer } from '../lib/createServers.js';
|
|
15
|
+
import { loadWebpackConfig } from '../lib/loadWebpackConfig.js';
|
|
16
|
+
import { setAppName } from '../server/db/methods.js';
|
|
17
|
+
import { Resource } from '../server/models/Resource.js';
|
|
18
|
+
export const command = 'serve path';
|
|
19
|
+
export const description = 'Serve an app with a local development server.';
|
|
20
|
+
export function builder(yargs) {
|
|
21
|
+
return yargs
|
|
22
|
+
.positional('path', {
|
|
23
|
+
describe: 'The path to the app to publish.',
|
|
24
|
+
})
|
|
25
|
+
.option('port', {
|
|
26
|
+
desc: 'The static HTTP server port to use. This is the port where the app is accessible.',
|
|
27
|
+
type: 'number',
|
|
28
|
+
default: 9090,
|
|
29
|
+
})
|
|
30
|
+
.option('api-port', {
|
|
31
|
+
desc: 'The Appsemble development HTTP server port to use. This is the port where the Appsemble development API endpoints are available.',
|
|
32
|
+
type: 'number',
|
|
33
|
+
default: 9191,
|
|
34
|
+
})
|
|
35
|
+
.option('user-role', {
|
|
36
|
+
desc: 'The role to set to the mocked authenticated user.',
|
|
37
|
+
type: 'string',
|
|
38
|
+
})
|
|
39
|
+
.option('team-role', {
|
|
40
|
+
desc: 'The role to set to the mocked authenticated user in the team.',
|
|
41
|
+
type: 'string',
|
|
42
|
+
default: 'member',
|
|
43
|
+
})
|
|
44
|
+
.option('remote', {
|
|
45
|
+
desc: 'The address to fetch remote blocks from.',
|
|
46
|
+
type: 'string',
|
|
47
|
+
})
|
|
48
|
+
.option('overwrite-block-cache', {
|
|
49
|
+
desc: 'Whether to overwrite remote blocks cache if it exists.',
|
|
50
|
+
type: 'boolean',
|
|
51
|
+
default: false,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
export async function handler(argv) {
|
|
55
|
+
const appPath = join(process.cwd(), argv.path);
|
|
56
|
+
const [, , , appsembleApp] = await traverseAppDirectory(appPath, 'development', new FormData());
|
|
57
|
+
const appRoles = appsembleApp.definition.roles;
|
|
58
|
+
const passedUserRole = argv['user-role'];
|
|
59
|
+
if (passedUserRole && !(appRoles === null || appRoles === void 0 ? void 0 : appRoles.includes(passedUserRole))) {
|
|
60
|
+
throw appRoles
|
|
61
|
+
? new AppsembleError(`The specified role "${passedUserRole}" is not supported by this app. Supported roles are [${appRoles}]`)
|
|
62
|
+
: new AppsembleError('This app does not support roles');
|
|
63
|
+
}
|
|
64
|
+
const appSecurity = appsembleApp.definition.security;
|
|
65
|
+
const appMembers = [
|
|
66
|
+
{
|
|
67
|
+
id: '1',
|
|
68
|
+
name: 'dev',
|
|
69
|
+
primaryEmail: 'dev@example.com',
|
|
70
|
+
role: passedUserRole || (appSecurity === null || appSecurity === void 0 ? void 0 : appSecurity.default.role),
|
|
71
|
+
},
|
|
72
|
+
];
|
|
73
|
+
const allowedTeamRoles = ['manager', 'member'];
|
|
74
|
+
const passedTeamRole = argv['team-role'];
|
|
75
|
+
if (passedTeamRole && !allowedTeamRoles.includes(passedTeamRole)) {
|
|
76
|
+
throw new AppsembleError(`The specified team role ${passedTeamRole} is not supported. Allowed roles are [member,manager]`);
|
|
77
|
+
}
|
|
78
|
+
const appTeams = [
|
|
79
|
+
{
|
|
80
|
+
id: 1,
|
|
81
|
+
name: 'team',
|
|
82
|
+
size: 1,
|
|
83
|
+
role: passedTeamRole,
|
|
84
|
+
annotations: {},
|
|
85
|
+
},
|
|
86
|
+
];
|
|
87
|
+
const appUserInfo = {
|
|
88
|
+
email: 'dev@example.com',
|
|
89
|
+
email_verified: true,
|
|
90
|
+
name: 'dev',
|
|
91
|
+
sub: '1',
|
|
92
|
+
};
|
|
93
|
+
const appName = normalize(appsembleApp.definition.name);
|
|
94
|
+
const appId = 1;
|
|
95
|
+
setAppName(appName);
|
|
96
|
+
const identifiableBlocks = getAppBlocks(appsembleApp.definition);
|
|
97
|
+
const localIdentifiableBlocks = [];
|
|
98
|
+
const remoteIdentifiableBlocks = [];
|
|
99
|
+
for (const identifiableBlock of identifiableBlocks) {
|
|
100
|
+
const [, blockName] = parseBlockName(identifiableBlock.type);
|
|
101
|
+
if (existsSync(join(process.cwd(), 'blocks', blockName))) {
|
|
102
|
+
localIdentifiableBlocks.push(identifiableBlock);
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
remoteIdentifiableBlocks.push(identifiableBlock);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
const localBlocksConfigs = await Promise.all(localIdentifiableBlocks.map(async (identifiableBlock) => {
|
|
109
|
+
const [organization, blockName] = parseBlockName(identifiableBlock.type);
|
|
110
|
+
const blockConfig = await getBlockConfig(join(process.cwd(), 'blocks', blockName));
|
|
111
|
+
return {
|
|
112
|
+
...blockConfig,
|
|
113
|
+
OrganizationId: organization,
|
|
114
|
+
};
|
|
115
|
+
}));
|
|
116
|
+
const localBlocksPromises = localBlocksConfigs.map(async (blockConfig) => {
|
|
117
|
+
await buildBlock(blockConfig);
|
|
118
|
+
const [, blockManifest] = await makePayload(blockConfig);
|
|
119
|
+
blockManifest.version = localIdentifiableBlocks.find((identifiableBlock) => identifiableBlock.type === blockManifest.name).version;
|
|
120
|
+
return blockManifest;
|
|
121
|
+
});
|
|
122
|
+
const cacheDir = await globalCacheDir('appsemble');
|
|
123
|
+
const remoteBlocksPromises = remoteIdentifiableBlocks.map(async (identifiableBlock) => {
|
|
124
|
+
const [organization, blockName] = parseBlockName(identifiableBlock.type);
|
|
125
|
+
const blockCacheDir = join(cacheDir, 'blocks', organization, blockName, identifiableBlock.version);
|
|
126
|
+
const cachedBlockManifest = join(blockCacheDir, 'manifest.json');
|
|
127
|
+
const cacheExists = existsSync(cachedBlockManifest);
|
|
128
|
+
if (!cacheExists || (cacheExists && argv['overwrite-block-cache'])) {
|
|
129
|
+
const blockUrl = `/api/blocks/@${organization}/${blockName}/versions/${identifiableBlock.version}`;
|
|
130
|
+
const { data: blockManifest } = await axios.get(String(new URL(blockUrl, argv.remote)));
|
|
131
|
+
await writeData(cachedBlockManifest, blockManifest);
|
|
132
|
+
const assetsDir = join(blockCacheDir, 'assets');
|
|
133
|
+
if (!existsSync(assetsDir)) {
|
|
134
|
+
await mkdir(assetsDir);
|
|
135
|
+
}
|
|
136
|
+
const blockFilesPromises = blockManifest.files.map(async (filename) => {
|
|
137
|
+
const writer = createWriteStream(join(assetsDir, filename));
|
|
138
|
+
const { data: content } = await axios.get(String(new URL(`${blockUrl}/asset?filename=${filename}`, argv.remote)), {
|
|
139
|
+
responseType: 'stream',
|
|
140
|
+
});
|
|
141
|
+
content.pipe(writer);
|
|
142
|
+
});
|
|
143
|
+
await Promise.all(blockFilesPromises);
|
|
144
|
+
return blockManifest;
|
|
145
|
+
}
|
|
146
|
+
const [blockManifest] = await readData(cachedBlockManifest);
|
|
147
|
+
return blockManifest;
|
|
148
|
+
});
|
|
149
|
+
const localBlocks = await Promise.all(localBlocksPromises);
|
|
150
|
+
const remoteBlocks = await Promise.all(remoteBlocksPromises);
|
|
151
|
+
const appBlocks = [...localBlocks, ...remoteBlocks];
|
|
152
|
+
const webpackConfigs = await Promise.all(localBlocksConfigs.map((blockConfig) => loadWebpackConfig(blockConfig, 'development', join(blockConfig.dir, blockConfig.output))));
|
|
153
|
+
// Get app messages
|
|
154
|
+
const appMessages = [];
|
|
155
|
+
await opendirSafe(join(appPath, 'i18n'), async (path) => {
|
|
156
|
+
logger.verbose(`Processing ${path} ⚙️`);
|
|
157
|
+
const { name: language } = parse(path);
|
|
158
|
+
if (appMessages.some((entry) => entry.language === language)) {
|
|
159
|
+
throw new AppsembleError(`Found duplicate language “${language}”. Make sure each language only exists once in the directory.`);
|
|
160
|
+
}
|
|
161
|
+
const [messages] = await readData(path);
|
|
162
|
+
appMessages.push({ language, messages });
|
|
163
|
+
}, { allowMissing: true });
|
|
164
|
+
// Get app assets
|
|
165
|
+
const appAssets = [];
|
|
166
|
+
await opendirSafe(join(appPath, 'assets'), (path) => {
|
|
167
|
+
logger.verbose(`Processing ${path} ⚙️`);
|
|
168
|
+
const extension = extname(path);
|
|
169
|
+
const name = basename(path, extension);
|
|
170
|
+
appAssets.push({
|
|
171
|
+
id: name,
|
|
172
|
+
mime: extension,
|
|
173
|
+
filename: path,
|
|
174
|
+
name,
|
|
175
|
+
});
|
|
176
|
+
}, { allowMissing: true });
|
|
177
|
+
// Get app resources
|
|
178
|
+
await opendirSafe(join(appPath, 'resources'), async (path, stat) => {
|
|
179
|
+
logger.verbose(`Processing ${path} ⚙️`);
|
|
180
|
+
let resources;
|
|
181
|
+
if (path.endsWith('.csv')) {
|
|
182
|
+
const data = await readFile(path);
|
|
183
|
+
const stream = Readable.from(data);
|
|
184
|
+
resources = await csvToJson({}).fromStream(stream);
|
|
185
|
+
}
|
|
186
|
+
else {
|
|
187
|
+
const [resource] = await readData(path);
|
|
188
|
+
if (typeof resource !== 'object') {
|
|
189
|
+
throw new AppsembleError(`File at ${path} does not contain an object or array of objects`);
|
|
190
|
+
}
|
|
191
|
+
resources = [].concat(resource);
|
|
192
|
+
}
|
|
193
|
+
logger.info(`Creating resource(s) from ${path}`);
|
|
194
|
+
const { name } = parse(stat.name);
|
|
195
|
+
await Resource.bulkCreate(resources, name, true);
|
|
196
|
+
}, { allowMissing: true });
|
|
197
|
+
const stubbedApp = {
|
|
198
|
+
...appsembleApp,
|
|
199
|
+
id: appId,
|
|
200
|
+
path: appPath,
|
|
201
|
+
coreStyle: appsembleApp.coreStyle || '',
|
|
202
|
+
sharedStyle: appsembleApp.sharedStyle || '',
|
|
203
|
+
$updated: new Date().toISOString(),
|
|
204
|
+
};
|
|
205
|
+
const appsembleApiServer = createApiServer({
|
|
206
|
+
argv,
|
|
207
|
+
context: {
|
|
208
|
+
appHost: `http://localhost:${argv.port}`,
|
|
209
|
+
appsembleApp: stubbedApp,
|
|
210
|
+
appBlocks,
|
|
211
|
+
appMessages,
|
|
212
|
+
...(appSecurity
|
|
213
|
+
? {
|
|
214
|
+
appMembers,
|
|
215
|
+
appUserInfo,
|
|
216
|
+
appTeams,
|
|
217
|
+
}
|
|
218
|
+
: {}),
|
|
219
|
+
appAssets,
|
|
220
|
+
blockConfigs: localBlocksConfigs,
|
|
221
|
+
},
|
|
222
|
+
});
|
|
223
|
+
appsembleApiServer.on('error', (err) => {
|
|
224
|
+
if (err.expose) {
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
logger.error(err);
|
|
228
|
+
});
|
|
229
|
+
const api = http.createServer(appsembleApiServer.callback());
|
|
230
|
+
const staticServer = await createStaticServer({
|
|
231
|
+
argv,
|
|
232
|
+
context: {
|
|
233
|
+
apiUrl: `http://localhost:${argv['api-port']}`,
|
|
234
|
+
appHost: `http://localhost:${argv.port}`,
|
|
235
|
+
appsembleApp: stubbedApp,
|
|
236
|
+
appBlocks,
|
|
237
|
+
appMessages,
|
|
238
|
+
...(appSecurity
|
|
239
|
+
? {
|
|
240
|
+
appMembers,
|
|
241
|
+
appUserInfo,
|
|
242
|
+
appTeams,
|
|
243
|
+
}
|
|
244
|
+
: {}),
|
|
245
|
+
appAssets,
|
|
246
|
+
blockConfigs: localBlocksConfigs,
|
|
247
|
+
},
|
|
248
|
+
webpackConfigs,
|
|
249
|
+
});
|
|
250
|
+
staticServer.on('error', (err) => {
|
|
251
|
+
if (err.expose) {
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
logger.error(err);
|
|
255
|
+
});
|
|
256
|
+
const app = http.createServer(staticServer.callback());
|
|
257
|
+
api.listen(argv['api-port'], '::', () => {
|
|
258
|
+
logger.info(asciiLogo);
|
|
259
|
+
logger.info(`The api can be found on\n> http://localhost:${argv['api-port']}`);
|
|
260
|
+
});
|
|
261
|
+
app.listen(argv.port, '::', () => {
|
|
262
|
+
logger.info(`\nThe app can be found on\n> http://localhost:${argv.port}`);
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
//# sourceMappingURL=serve.js.map
|
package/index.js
CHANGED
|
@@ -14,6 +14,7 @@ import * as migrate from './commands/migrate.js';
|
|
|
14
14
|
import * as organization from './commands/organization/index.js';
|
|
15
15
|
import * as resource from './commands/resource/index.js';
|
|
16
16
|
import * as runCronJobs from './commands/runCronJobs.js';
|
|
17
|
+
import * as serve from './commands/serve.js';
|
|
17
18
|
import * as start from './commands/start.js';
|
|
18
19
|
import * as team from './commands/team/index.js';
|
|
19
20
|
import { CREDENTIALS_ENV_VAR } from './lib/authentication.js';
|
|
@@ -56,6 +57,7 @@ let parser = yargs(process.argv.slice(2))
|
|
|
56
57
|
.command(resource)
|
|
57
58
|
.command(runCronJobs)
|
|
58
59
|
.command(start)
|
|
60
|
+
.command(serve)
|
|
59
61
|
.command(team)
|
|
60
62
|
.demandCommand(1)
|
|
61
63
|
// .strict() isn’t used because of https://github.com/yargs/yargs/issues/2058
|
package/lib/app.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
/// <reference types="node" />
|
|
2
|
-
/// <reference types="node" />
|
|
1
|
+
/// <reference types="node" resolution-mode="require"/>
|
|
2
|
+
/// <reference types="node" resolution-mode="require"/>
|
|
3
3
|
import { type ReadStream } from 'node:fs';
|
|
4
|
-
import { type AppVisibility } from '@appsemble/types';
|
|
4
|
+
import { type App, type AppVisibility } from '@appsemble/types';
|
|
5
5
|
import FormData from 'form-data';
|
|
6
6
|
import { type AppsembleContext, type AppsembleRC } from '../types.js';
|
|
7
7
|
interface CreateAppParams {
|
|
@@ -137,7 +137,7 @@ interface UpdateAppParams {
|
|
|
137
137
|
* @param formData The FormData object to append the results into..
|
|
138
138
|
* @returns The context from `.appsemblerc.yaml` if a match was found.
|
|
139
139
|
*/
|
|
140
|
-
export declare function traverseAppDirectory(path: string, context: string, formData: FormData): Promise<[AppsembleContext, AppsembleRC, string]>;
|
|
140
|
+
export declare function traverseAppDirectory(path: string, context: string, formData: FormData): Promise<[AppsembleContext, AppsembleRC, string, App]>;
|
|
141
141
|
/**
|
|
142
142
|
* Upload messages for an app.
|
|
143
143
|
*
|
package/lib/app.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createReadStream, existsSync } from 'node:fs';
|
|
2
2
|
import { mkdir, readdir, readFile, stat } from 'node:fs/promises';
|
|
3
|
-
import { join, parse, relative, resolve } from 'node:path';
|
|
3
|
+
import { basename, join, parse, relative, resolve } from 'node:path';
|
|
4
4
|
import { inspect } from 'node:util';
|
|
5
5
|
import { AppsembleError, logger, opendirSafe, readData, writeData } from '@appsemble/node-utils';
|
|
6
6
|
import { extractAppMessages, has, normalizeBlockName } from '@appsemble/utils';
|
|
@@ -26,6 +26,9 @@ export async function traverseAppDirectory(path, context, formData) {
|
|
|
26
26
|
let iconPath;
|
|
27
27
|
let maskableIconPath;
|
|
28
28
|
let yaml;
|
|
29
|
+
const gatheredData = {
|
|
30
|
+
screenshotUrls: [],
|
|
31
|
+
};
|
|
29
32
|
logger.info(`Traversing directory for App files in ${path} 🕵`);
|
|
30
33
|
await opendirSafe(path, async (filepath, filestat) => {
|
|
31
34
|
switch (filestat.name.toLowerCase()) {
|
|
@@ -34,6 +37,7 @@ export async function traverseAppDirectory(path, context, formData) {
|
|
|
34
37
|
[rc] = await readData(filepath);
|
|
35
38
|
if ('iconBackground' in rc) {
|
|
36
39
|
formData.append('iconBackground', rc.iconBackground);
|
|
40
|
+
gatheredData.iconBackground = rc.iconBackground;
|
|
37
41
|
}
|
|
38
42
|
if (context && has(rc === null || rc === void 0 ? void 0 : rc.context, context)) {
|
|
39
43
|
discoveredContext = rc.context[context];
|
|
@@ -45,14 +49,16 @@ export async function traverseAppDirectory(path, context, formData) {
|
|
|
45
49
|
if (yaml !== undefined) {
|
|
46
50
|
throw new AppsembleError('Found duplicate app definition');
|
|
47
51
|
}
|
|
48
|
-
const [,
|
|
49
|
-
yaml =
|
|
50
|
-
formData.append('yaml',
|
|
52
|
+
const [data, dataString] = await readData(filepath);
|
|
53
|
+
yaml = dataString;
|
|
54
|
+
formData.append('yaml', dataString);
|
|
55
|
+
gatheredData.definition = data;
|
|
51
56
|
return;
|
|
52
57
|
}
|
|
53
58
|
case 'icon.png':
|
|
54
59
|
case 'icon.svg':
|
|
55
60
|
iconPath = filepath;
|
|
61
|
+
gatheredData.iconUrl = filepath;
|
|
56
62
|
return;
|
|
57
63
|
case 'maskable-icon.png':
|
|
58
64
|
maskableIconPath = filepath;
|
|
@@ -60,6 +66,7 @@ export async function traverseAppDirectory(path, context, formData) {
|
|
|
60
66
|
case 'readme.md':
|
|
61
67
|
logger.info(`Including longDescription from ${filepath}`);
|
|
62
68
|
formData.append('longDescription', await readFile(filepath, 'utf8'));
|
|
69
|
+
gatheredData.longDescription = await readFile(filepath, 'utf8');
|
|
63
70
|
return;
|
|
64
71
|
case 'screenshots':
|
|
65
72
|
return opendirSafe(filepath, (screenshotPath, screenshotStat) => {
|
|
@@ -68,6 +75,7 @@ export async function traverseAppDirectory(path, context, formData) {
|
|
|
68
75
|
throw new AppsembleError(`Expected ${screenshotPath} to be an image file`);
|
|
69
76
|
}
|
|
70
77
|
formData.append('screenshots', createReadStream(screenshotPath));
|
|
78
|
+
gatheredData.screenshotUrls.push(basename(screenshotPath));
|
|
71
79
|
}, { allowMissing: true });
|
|
72
80
|
case 'theme':
|
|
73
81
|
return opendirSafe(filepath, async (themeDir, themeStat) => {
|
|
@@ -80,6 +88,7 @@ export async function traverseAppDirectory(path, context, formData) {
|
|
|
80
88
|
}
|
|
81
89
|
const css = await processCss(join(themeDir, 'index.css'));
|
|
82
90
|
formData.append(`${name}Style`, css);
|
|
91
|
+
gatheredData[`${name}Style`] = await processCss(join(themeDir, 'index.css'));
|
|
83
92
|
});
|
|
84
93
|
default:
|
|
85
94
|
logger.warn(`Found unused file ${filepath}`);
|
|
@@ -95,7 +104,7 @@ export async function traverseAppDirectory(path, context, formData) {
|
|
|
95
104
|
discoveredContext.maskableIcon = discoveredContext.maskableIcon
|
|
96
105
|
? resolve(path, discoveredContext.maskableIcon)
|
|
97
106
|
: maskableIconPath;
|
|
98
|
-
return [discoveredContext, rc, yaml];
|
|
107
|
+
return [discoveredContext, rc, yaml, gatheredData];
|
|
99
108
|
}
|
|
100
109
|
/**
|
|
101
110
|
* Upload messages for an app.
|
package/lib/block.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type BlockConfig } from '@appsemble/types';
|
|
1
|
+
import { type BlockConfig, type BlockManifest } from '@appsemble/types';
|
|
2
2
|
import FormData from 'form-data';
|
|
3
3
|
import { type Stats } from 'webpack';
|
|
4
4
|
/**
|
|
@@ -30,7 +30,7 @@ export declare function discoverBlocks(root: string): Promise<BlockConfig[]>;
|
|
|
30
30
|
* @param config The block configuration
|
|
31
31
|
* @returns The payload that should be sent to the version endpoint.
|
|
32
32
|
*/
|
|
33
|
-
export declare function makePayload(config: BlockConfig): Promise<FormData>;
|
|
33
|
+
export declare function makePayload(config: BlockConfig): Promise<[FormData, BlockManifest]>;
|
|
34
34
|
/**
|
|
35
35
|
* Publish a new block version.
|
|
36
36
|
*
|