@appsemble/node-utils 0.30.14-test.6 → 0.32.1-test.15
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 +3 -3
- package/app.js +8 -0
- package/assets.d.ts +16 -0
- package/assets.js +65 -0
- package/authentication.d.ts +1 -1
- package/container/helpers.d.ts +1 -1
- package/container/helpers.js +1 -0
- package/container/logs.js +8 -5
- package/container/operations.js +22 -14
- package/container/scale.js +3 -2
- package/container/specs.js +1 -1
- package/fs.js +4 -0
- package/getAppsembleMessages.js +2 -0
- package/getMessagesUtil.js +5 -4
- package/getValidTrainings.d.ts +8 -0
- package/getValidTrainings.js +47 -0
- package/icon.js +5 -0
- package/index.d.ts +5 -1
- package/index.js +5 -1
- package/interceptors.js +1 -1
- package/koa.d.ts +13 -1
- package/koa.js +17 -1
- package/logger.js +5 -3
- package/middleware/loggerMiddleware.js +1 -0
- package/odata.js +2 -1
- package/package.json +22 -14
- package/parsers.d.ts +1 -0
- package/parsers.js +130 -0
- package/render.js +1 -1
- package/resource.d.ts +18 -3
- package/resource.js +50 -10
- package/s3.d.ts +18 -0
- package/s3.js +131 -0
- package/server/controllers/common/apps/assets/createGetAppAssetByIdController.js +4 -3
- package/server/controllers/common/apps/groups/createGetAppGroupsController.js +2 -2
- package/server/controllers/common/apps/messages/createGetAppMessagesController.js +2 -0
- package/server/controllers/common/apps/resources/createCreateAppResourceController.js +16 -2
- package/server/controllers/common/apps/resources/createDeleteAppResourceController.js +2 -2
- package/server/controllers/common/apps/resources/createGetAppResourceByIdController.js +3 -3
- package/server/controllers/common/apps/resources/createQueryAppResourcesController.js +1 -1
- package/server/controllers/common/apps/resources/createUpdateAppResourceController.js +6 -3
- package/server/controllers/common/apps/variables/createGetAppVariablesController.js +2 -2
- package/server/controllers/main/apps/assets/createCreateAppAssetController.js +4 -4
- package/server/controllers/main/apps/resources/createDeleteAppSeedResourcesController.js +2 -2
- package/server/routes/appRouter/blockAssetHandler.js +5 -3
- package/server/routes/appRouter/blockCssHandler.js +5 -3
- package/server/routes/appRouter/bulmaHandler.js +1 -0
- package/server/routes/appRouter/cssHandler.js +2 -2
- package/server/routes/appRouter/iconHandler.js +4 -1
- package/server/routes/appRouter/indexHandler.js +3 -1
- package/server/routes/appRouter/manifestHandler.js +2 -2
- package/server/routes/appRouter/readmeHandler.js +4 -3
- package/server/routes/appRouter/robotsHandler.js +2 -2
- package/server/routes/appRouter/screenshotHandler.js +4 -3
- package/server/routes/appRouter/serviceWorkerHandler.js +2 -2
- package/server/types.d.ts +22 -7
- package/server/types.js +7 -1
- package/server/utils/actions.js +16 -10
- package/server/utils/resources.js +4 -1
- package/tinyRouter.js +3 -3
- package/uploads.d.ts +2 -0
- package/uploads.js +27 -0
- package/versions.d.ts +3 -0
- package/versions.js +7 -0
- package/AppsembleError.test.d.ts +0 -1
- package/AppsembleError.test.js +0 -7
- package/basicAuth.test.d.ts +0 -1
- package/basicAuth.test.js +0 -7
- package/formData.test.d.ts +0 -1
- package/formData.test.js +0 -93
- package/fs.test.d.ts +0 -1
- package/fs.test.js +0 -75
- package/handleError.test.d.ts +0 -1
- package/handleError.test.js +0 -33
- package/interceptors.test.d.ts +0 -1
- package/interceptors.test.js +0 -83
- package/logger.test.d.ts +0 -1
- package/logger.test.js +0 -37
- package/middleware/error.test.d.ts +0 -1
- package/middleware/error.test.js +0 -59
- package/middleware/loggerMiddleware.test.d.ts +0 -1
- package/middleware/loggerMiddleware.test.js +0 -120
- package/readFileOrString.test.d.ts +0 -1
- package/readFileOrString.test.js +0 -12
- package/server/controllers/common/apps/resources/resources.test.d.ts +0 -1
- package/server/controllers/common/apps/resources/resources.test.js +0 -580
- package/tinyRouter.test.d.ts +0 -1
- package/tinyRouter.test.js +0 -83
- package/versions.cjs +0 -4
package/README.md
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
#  Appsemble Node Utilities
|
|
2
2
|
|
|
3
3
|
> NodeJS utilities used by Appsemble internally.
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/@appsemble/node-utils)
|
|
6
|
-
[](https://gitlab.com/appsemble/appsemble/-/releases/0.32.1-test.15)
|
|
7
7
|
[](https://prettier.io)
|
|
8
8
|
|
|
9
9
|
## Table of Contents
|
|
@@ -26,5 +26,5 @@ compatibility is not guaranteed.
|
|
|
26
26
|
|
|
27
27
|
## License
|
|
28
28
|
|
|
29
|
-
[LGPL-3.0-only](https://gitlab.com/appsemble/appsemble/-/blob/0.
|
|
29
|
+
[LGPL-3.0-only](https://gitlab.com/appsemble/appsemble/-/blob/0.32.1-test.15/LICENSE.md) ©
|
|
30
30
|
[Appsemble](https://appsemble.com)
|
package/app.js
CHANGED
|
@@ -36,23 +36,30 @@ export async function getRemapperContext(app, language, options, context) {
|
|
|
36
36
|
const appMemberInfo = await getCurrentAppMember({ context });
|
|
37
37
|
const cache = objectCache((message) => new IntlMessageFormat(message, language, undefined, {
|
|
38
38
|
formatters: {
|
|
39
|
+
// XXX: idk
|
|
40
|
+
// @ts-expect-error 2322 undefined is not assignable to type ... (strictNullChecks)
|
|
39
41
|
getNumberFormat,
|
|
40
42
|
getPluralRules,
|
|
41
43
|
getDateTimeFormat: memoize((locale, opts) => new Intl.DateTimeFormat(locale, { ...opts, timeZone: appMemberInfo?.zoneinfo })),
|
|
42
44
|
},
|
|
43
45
|
}));
|
|
44
46
|
return {
|
|
47
|
+
// @ts-expect-error 2322 null is not assignable to type (strictNullChecks)
|
|
45
48
|
appId: app.id,
|
|
46
49
|
appUrl,
|
|
47
50
|
url: appUrl,
|
|
48
51
|
getMessage({ defaultMessage, id }) {
|
|
52
|
+
// @ts-expect-error 2345 argument of type is not assignable to parameter of type
|
|
53
|
+
// (strictNullChecks)
|
|
49
54
|
const msg = appMessages.find(({ messages }) => has(messages.messageIds, id));
|
|
55
|
+
// @ts-expect-error 2538 type undefined cannot be used as an index type
|
|
50
56
|
const message = msg ? msg.messages.messageIds[id] : defaultMessage;
|
|
51
57
|
return cache(message || `'{${id}}'`);
|
|
52
58
|
},
|
|
53
59
|
getVariable(variableName) {
|
|
54
60
|
return appVariables.find((appVariable) => appVariable.name === variableName)?.value;
|
|
55
61
|
},
|
|
62
|
+
// @ts-expect-error 2322 null is not assignable to type (strictNullChecks)
|
|
56
63
|
appMemberInfo,
|
|
57
64
|
context: {},
|
|
58
65
|
locale: appMemberInfo?.locale ?? app.definition.defaultLanguage ?? defaultLocale,
|
|
@@ -150,6 +157,7 @@ async function patchStyle(replacementsPath, replacements) {
|
|
|
150
157
|
parseValue: false,
|
|
151
158
|
});
|
|
152
159
|
for (const { property, selector, value } of replacements) {
|
|
160
|
+
// @ts-expect-error 2322 undefined is not assignable to type (strictNullChecks)
|
|
153
161
|
doc = replaceStyle(doc, selector, property, String(value));
|
|
154
162
|
}
|
|
155
163
|
await writeFile(replacementsPath, generate(doc));
|
package/assets.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type Context } from 'koa';
|
|
2
|
+
import { type BucketItemStat } from 'minio';
|
|
3
|
+
export declare function setAssetHeaders(ctx: Context, mime: string, filename: string | null, stats?: BucketItemStat): void;
|
|
4
|
+
interface FileMeta {
|
|
5
|
+
filename?: string;
|
|
6
|
+
mime: string;
|
|
7
|
+
}
|
|
8
|
+
export declare function getCompressedFileMeta({ filename, mime }: FileMeta): FileMeta;
|
|
9
|
+
export interface AssetToUpload {
|
|
10
|
+
id: string;
|
|
11
|
+
mime: string;
|
|
12
|
+
path: string;
|
|
13
|
+
}
|
|
14
|
+
export declare function uploadAsset(appId: number, asset: AssetToUpload): Promise<string[]>;
|
|
15
|
+
export declare function uploadAssets(appId: number, assets: AssetToUpload[]): Promise<void>;
|
|
16
|
+
export {};
|
package/assets.js
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { createReadStream } from 'node:fs';
|
|
2
|
+
import { stat } from 'node:fs/promises';
|
|
3
|
+
import sharp from 'sharp';
|
|
4
|
+
import { logger } from './logger.js';
|
|
5
|
+
import { uploadS3File } from './s3.js';
|
|
6
|
+
import { removeUploads } from './uploads.js';
|
|
7
|
+
export function setAssetHeaders(ctx, mime, filename, stats) {
|
|
8
|
+
ctx.set('content-type', mime || 'application/octet-stream');
|
|
9
|
+
if (filename) {
|
|
10
|
+
ctx.set('content-disposition', `${mime?.startsWith('image') ? 'inline' : 'attachment'}; filename=${JSON.stringify(filename)}`);
|
|
11
|
+
}
|
|
12
|
+
if (stats) {
|
|
13
|
+
ctx.set('Content-Length', String(stats.size));
|
|
14
|
+
ctx.set('ETag', stats.etag);
|
|
15
|
+
ctx.set('Last-Modified', stats.lastModified.toUTCString());
|
|
16
|
+
}
|
|
17
|
+
ctx.set('Access-Control-Expose-Headers', 'Content-Disposition');
|
|
18
|
+
ctx.set('Cache-Control', 'max-age=31536000,immutable');
|
|
19
|
+
}
|
|
20
|
+
const ignoredMimes = new Set(['image/avif']);
|
|
21
|
+
export function getCompressedFileMeta({ filename, mime }) {
|
|
22
|
+
if (mime?.startsWith('image') && !ignoredMimes.has(mime)) {
|
|
23
|
+
return {
|
|
24
|
+
filename: filename
|
|
25
|
+
? filename.includes('.')
|
|
26
|
+
? `${filename.slice(0, filename.lastIndexOf('.'))}.avif`
|
|
27
|
+
: `${filename}.avif`
|
|
28
|
+
: undefined,
|
|
29
|
+
mime: 'image/avif',
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
return { filename, mime };
|
|
33
|
+
}
|
|
34
|
+
export async function uploadAsset(appId, asset) {
|
|
35
|
+
const { id, mime, path } = asset;
|
|
36
|
+
const filesToUnlink = [path];
|
|
37
|
+
let uploadFrom = path;
|
|
38
|
+
if (mime?.startsWith('image') && !ignoredMimes.has(mime)) {
|
|
39
|
+
uploadFrom = `${path}_compressed`;
|
|
40
|
+
await sharp(path).rotate().toFormat('avif').toFile(uploadFrom);
|
|
41
|
+
filesToUnlink.push(uploadFrom);
|
|
42
|
+
}
|
|
43
|
+
try {
|
|
44
|
+
const stats = await stat(uploadFrom);
|
|
45
|
+
const stream = createReadStream(uploadFrom);
|
|
46
|
+
await uploadS3File(`app-${appId}`, id, stream, stats.size);
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
logger.error(error);
|
|
50
|
+
}
|
|
51
|
+
return filesToUnlink;
|
|
52
|
+
}
|
|
53
|
+
export async function uploadAssets(appId, assets) {
|
|
54
|
+
const filesToUnlink = [];
|
|
55
|
+
for (const asset of assets) {
|
|
56
|
+
const toUnlink = await uploadAsset(appId, asset);
|
|
57
|
+
for (const path of toUnlink) {
|
|
58
|
+
if (!filesToUnlink.includes(path)) {
|
|
59
|
+
filesToUnlink.push(path);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
await removeUploads(filesToUnlink);
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=assets.js.map
|
package/authentication.d.ts
CHANGED
|
@@ -9,4 +9,4 @@ export declare function getKeytar(): Promise<typeof import('keytar')>;
|
|
|
9
9
|
* multiple scopes.
|
|
10
10
|
* @param inputCredentials Client credentials passed from the command line.
|
|
11
11
|
*/
|
|
12
|
-
export declare function authenticate(remote: string, scope: string, inputCredentials
|
|
12
|
+
export declare function authenticate(remote: string, scope: string, inputCredentials?: string): Promise<void>;
|
package/container/helpers.d.ts
CHANGED
|
@@ -28,6 +28,6 @@ export declare function parseServiceUrl(url: string): {
|
|
|
28
28
|
appName: string;
|
|
29
29
|
namespace: string;
|
|
30
30
|
};
|
|
31
|
-
export declare function validateContainerResources(resources
|
|
31
|
+
export declare function validateContainerResources(resources?: ContainerResources): ContainerResources;
|
|
32
32
|
export declare function isWhitelisted(fullImageName: string): Boolean;
|
|
33
33
|
export declare function formatEnv(input: ContainerEnvVar[], appName: string, appId: string): V1EnvVar[];
|
package/container/helpers.js
CHANGED
|
@@ -70,6 +70,7 @@ export function parseServiceUrl(url) {
|
|
|
70
70
|
const namespace = elements[1];
|
|
71
71
|
const appId = deploymentName.split('-').pop();
|
|
72
72
|
const appName = deploymentName.replace(`-${appId}`, '');
|
|
73
|
+
// @ts-expect-error 2322 null is not assignable to type (strictNullChecks)
|
|
73
74
|
return { deploymentName, appId, appName, namespace };
|
|
74
75
|
}
|
|
75
76
|
export function validateContainerResources(resources) {
|
package/container/logs.js
CHANGED
|
@@ -66,19 +66,22 @@ export async function getLogs(deploymentName, fromAppsemble) {
|
|
|
66
66
|
for (const pod of pods.items) {
|
|
67
67
|
if (pod.metadata?.ownerReferences?.find((r) => r.kind === 'ReplicaSet') &&
|
|
68
68
|
pod.status?.phase === 'Running') {
|
|
69
|
-
if (fromAppsemble && !pod.metadata?.name
|
|
69
|
+
if (fromAppsemble && !pod.metadata?.name?.includes(appsembleServiceName)) {
|
|
70
70
|
continue;
|
|
71
71
|
}
|
|
72
|
-
if (!fromAppsemble && !pod.metadata?.name
|
|
72
|
+
if (!fromAppsemble && !pod.metadata?.name?.includes(deploymentName)) {
|
|
73
73
|
continue;
|
|
74
74
|
}
|
|
75
75
|
logger.silly(`Pod: ${pod.metadata?.name}, status: ${pod.status}, deployment: ${deploymentName}`);
|
|
76
76
|
try {
|
|
77
|
-
const res = await fetchLogs(fromAppsemble ? appsembleNamespace : containerNamespace,
|
|
77
|
+
const res = await fetchLogs(fromAppsemble ? appsembleNamespace : containerNamespace,
|
|
78
|
+
// @ts-expect-error 2345 argument of type is not assignable to parameter of type
|
|
79
|
+
// (strictNullChecks)
|
|
80
|
+
pod.metadata?.name, fromAppsemble ? 'appsemble' : deploymentName);
|
|
78
81
|
let entries = res.split('\n');
|
|
79
82
|
logger.silly(`Number of entries found for pod ${pod.metadata?.name}: ${entries.length}`);
|
|
80
|
-
entries = filterLogEntries([...entries], deploymentName, fromAppsemble);
|
|
81
|
-
podLogs.push({ fromAppsemble, entries });
|
|
83
|
+
entries = filterLogEntries([...entries], deploymentName, fromAppsemble ?? false);
|
|
84
|
+
podLogs.push({ fromAppsemble: fromAppsemble ?? false, entries });
|
|
82
85
|
}
|
|
83
86
|
catch (error) {
|
|
84
87
|
handleKubernetesError(error);
|
package/container/operations.js
CHANGED
|
@@ -41,7 +41,7 @@ export async function updateNamespacedSecret(secretName, value, appName, appId)
|
|
|
41
41
|
existing.data[secretName] = Buffer.from(value).toString('base64');
|
|
42
42
|
try {
|
|
43
43
|
const { body: updatedSecret } = await coreApi.replaceNamespacedSecret(formattedName, namespace, existing);
|
|
44
|
-
logger.info(`Secret ${updatedSecret.metadata
|
|
44
|
+
logger.info(`Secret ${updatedSecret.metadata?.name} updated successfully`);
|
|
45
45
|
}
|
|
46
46
|
catch (error) {
|
|
47
47
|
handleKubernetesError(error);
|
|
@@ -68,7 +68,7 @@ export async function deleteSecret(appName, appId, key) {
|
|
|
68
68
|
try {
|
|
69
69
|
const { body: secret } = await coreApi.readNamespacedSecret(secretName, namespace);
|
|
70
70
|
logger.verbose(`Deleteing key ${key} of secret ${secretName} from namespace ${namespace} ...`);
|
|
71
|
-
if (!(key in secret.data)) {
|
|
71
|
+
if (!(secret.data && key in secret.data)) {
|
|
72
72
|
logger.warn(`Key ${key} does not exist on secret ${secret.metadata?.name}`);
|
|
73
73
|
return;
|
|
74
74
|
}
|
|
@@ -113,17 +113,19 @@ export async function updateCompanionContainers(definitions, appName, appId, reg
|
|
|
113
113
|
// Update or create deployment and service
|
|
114
114
|
for (const def of definitions) {
|
|
115
115
|
const serviceName = formatServiceName(def.name, appName, appId);
|
|
116
|
-
const existing = services?.items?.find((s) => s.metadata
|
|
116
|
+
const existing = services?.items?.find((s) => s.metadata?.name === serviceName);
|
|
117
117
|
// Update
|
|
118
118
|
if (existing) {
|
|
119
|
-
logger.verbose(`Updating resources for ${existing.metadata
|
|
119
|
+
logger.verbose(`Updating resources for ${existing.metadata?.name} in namespace ${namespace}`);
|
|
120
120
|
const patchOptions = [
|
|
121
|
-
|
|
121
|
+
// @ts-expect-error 18048 variable is possibly undefined (strictNullChecks)
|
|
122
|
+
existing.metadata.name,
|
|
122
123
|
namespace,
|
|
123
124
|
[
|
|
124
125
|
{
|
|
125
126
|
op: 'replace',
|
|
126
127
|
path: '/metadata',
|
|
128
|
+
// @ts-expect-error 18048 variable is possibly undefined (strictNullChecks)
|
|
127
129
|
value: { ...def.metadata, name: existing.metadata.name },
|
|
128
130
|
},
|
|
129
131
|
{
|
|
@@ -159,6 +161,7 @@ export async function updateCompanionContainers(definitions, appName, appId, reg
|
|
|
159
161
|
{
|
|
160
162
|
op: 'replace',
|
|
161
163
|
path: '/metadata',
|
|
164
|
+
// @ts-expect-error 18048 variable is possibly undefined (strictNullChecks)
|
|
162
165
|
value: { ...def.metadata, name: existing.metadata.name },
|
|
163
166
|
},
|
|
164
167
|
{
|
|
@@ -169,7 +172,7 @@ export async function updateCompanionContainers(definitions, appName, appId, reg
|
|
|
169
172
|
{
|
|
170
173
|
op: 'replace',
|
|
171
174
|
path: '/spec/template/spec/containers/0/env',
|
|
172
|
-
value: [...formatEnv(def.env, appName, appId)],
|
|
175
|
+
value: [...formatEnv(def.env ?? [], appName, appId)],
|
|
173
176
|
},
|
|
174
177
|
{
|
|
175
178
|
op: 'add',
|
|
@@ -197,18 +200,19 @@ export async function updateCompanionContainers(definitions, appName, appId, reg
|
|
|
197
200
|
logger.silly(`Deployment ${serviceName} has 0 pods, skipping ...`);
|
|
198
201
|
continue;
|
|
199
202
|
}
|
|
200
|
-
const pod = body?.items?.find((i) => i.metadata
|
|
203
|
+
const pod = body?.items?.find((i) => i.metadata?.labels?.app === existing.metadata?.name);
|
|
201
204
|
// Set base labels, such as selector, pod name, pod hash
|
|
202
205
|
const props = {
|
|
203
206
|
...def.metadata,
|
|
204
207
|
labels: {
|
|
205
208
|
...(def.metadata ? def.metadata.labels : null),
|
|
206
|
-
app: pod
|
|
207
|
-
'pod-template-hash': pod
|
|
209
|
+
app: pod?.metadata?.labels?.app,
|
|
210
|
+
'pod-template-hash': pod?.metadata?.labels?.['pod-template-hash'],
|
|
208
211
|
appId,
|
|
209
212
|
},
|
|
210
|
-
name: pod
|
|
213
|
+
name: pod?.metadata?.name,
|
|
211
214
|
};
|
|
215
|
+
// @ts-expect-error 2322 null is not assignable to type (strictNullChecks)
|
|
212
216
|
patchOptions[0] = pod?.metadata?.name;
|
|
213
217
|
patchOptions[2] = [
|
|
214
218
|
{
|
|
@@ -227,7 +231,9 @@ export async function updateCompanionContainers(definitions, appName, appId, reg
|
|
|
227
231
|
// Create
|
|
228
232
|
else {
|
|
229
233
|
const { deployment, service } = generateDeploymentAndServiceSpecs(def, serviceName, appName, appId, registry);
|
|
230
|
-
|
|
234
|
+
// @ts-expect-error 2345 argument of type is not assignable to parameter of type
|
|
235
|
+
// (strictNullChecks)
|
|
236
|
+
if (isWhitelisted(deployment.spec?.template.spec?.containers[0].image)) {
|
|
231
237
|
logger.verbose(`Creating resources in namespace ${namespace} ...`);
|
|
232
238
|
try {
|
|
233
239
|
const deploymentObj = await appsApi.createNamespacedDeployment(namespace, deployment);
|
|
@@ -246,20 +252,22 @@ export async function updateCompanionContainers(definitions, appName, appId, reg
|
|
|
246
252
|
}
|
|
247
253
|
}
|
|
248
254
|
else {
|
|
249
|
-
logger.error(`Image ${deployment.spec
|
|
255
|
+
logger.error(`Image ${deployment.spec?.template.spec?.containers[0].image} is not in the repository whitelist!`);
|
|
250
256
|
}
|
|
251
257
|
}
|
|
252
258
|
}
|
|
253
259
|
// Delete companions removed from the definition
|
|
254
260
|
for (const service of services.items) {
|
|
255
|
-
if (service.spec
|
|
261
|
+
if (service.spec?.type === 'ExternalName') {
|
|
256
262
|
continue;
|
|
257
263
|
}
|
|
258
264
|
if (service.metadata?.labels &&
|
|
259
265
|
appIdLabel in service.metadata.labels &&
|
|
260
266
|
service.metadata.labels[appIdLabel] === appId) {
|
|
261
|
-
const existing = definitions.find((d) => formatServiceName(d.name, appName, appId) === service.metadata
|
|
267
|
+
const existing = definitions.find((d) => formatServiceName(d.name, appName, appId) === service.metadata?.name);
|
|
262
268
|
if (!existing) {
|
|
269
|
+
// @ts-expect-error 2345 argument of type is not assignable to parameter of type
|
|
270
|
+
// (strictNullChecks)
|
|
263
271
|
await deleteCompanionContainers(service.metadata?.name);
|
|
264
272
|
}
|
|
265
273
|
}
|
package/container/scale.js
CHANGED
|
@@ -30,9 +30,10 @@ export async function stopIdleContainers(interval = 10) {
|
|
|
30
30
|
return;
|
|
31
31
|
}
|
|
32
32
|
for (const deployment of deployments.items) {
|
|
33
|
+
// @ts-expect-error 2339 Property does not exist on type undefined
|
|
33
34
|
const { annotations, name, namespace } = deployment.metadata;
|
|
34
35
|
if (deployment.metadata?.namespace !== getContainerNamespace() ||
|
|
35
|
-
deployment.spec
|
|
36
|
+
deployment.spec?.replicas === 0) {
|
|
36
37
|
continue;
|
|
37
38
|
}
|
|
38
39
|
const milliseconds = interval * 60 * 1000;
|
|
@@ -67,7 +68,7 @@ export async function stopIdleContainers(interval = 10) {
|
|
|
67
68
|
}
|
|
68
69
|
}
|
|
69
70
|
export async function waitForPodReadiness(namespace, appSelector, maxWait = 10000) {
|
|
70
|
-
let isReady;
|
|
71
|
+
let isReady = false;
|
|
71
72
|
const interval = 500;
|
|
72
73
|
let elapsed = 0;
|
|
73
74
|
const { coreApi } = getKubeConfig();
|
package/container/specs.js
CHANGED
|
@@ -18,7 +18,7 @@ export function generateDeploymentAndServiceSpecs(definition, name, appName, app
|
|
|
18
18
|
image = `${registry.endsWith('/') ? registry.slice(0, -1) : registry}/${image}`;
|
|
19
19
|
}
|
|
20
20
|
const validatedResources = validateContainerResources(resources);
|
|
21
|
-
const env = formatEnv(definition.env, appName, appId);
|
|
21
|
+
const env = formatEnv(definition.env ?? [], appName, appId);
|
|
22
22
|
// Env should have string keys!
|
|
23
23
|
const podTemplateSpec = {
|
|
24
24
|
metadata: {
|
package/fs.js
CHANGED
|
@@ -61,15 +61,19 @@ export async function readData(path) {
|
|
|
61
61
|
* @returns The formatted content.
|
|
62
62
|
*/
|
|
63
63
|
export async function writeData(path, data, { compare = compareStrings, sort = true } = {}) {
|
|
64
|
+
// @ts-expect-error 2769 No overload matches this call (strictNullChecks)
|
|
64
65
|
const sorted = sort ? sortKeys(data, { deep: true, compare: compare || undefined }) : data;
|
|
65
66
|
let buffer;
|
|
66
67
|
try {
|
|
67
68
|
const { default: prettier } = await import('prettier');
|
|
68
69
|
const { inferredParser } = await prettier.getFileInfo(path, { resolveConfig: true });
|
|
69
70
|
const prettierOptions = await prettier.resolveConfig(path, { editorconfig: true });
|
|
71
|
+
// @ts-expect-error 18048 variable is possibly null (strictNullChecks)
|
|
70
72
|
prettierOptions.parser = inferredParser;
|
|
71
73
|
buffer =
|
|
72
74
|
inferredParser === 'yaml' ? stringify(sorted) : `${JSON.stringify(sorted, undefined, 2)}\n`;
|
|
75
|
+
// @ts-expect-error 2345 argument of type is not assignable to parameter of type
|
|
76
|
+
// (strictNullChecks)
|
|
73
77
|
buffer = await prettier.format(buffer, prettierOptions);
|
|
74
78
|
}
|
|
75
79
|
catch {
|
package/getAppsembleMessages.js
CHANGED
|
@@ -17,6 +17,8 @@ export async function getAppsembleMessages(language, baseLanguage) {
|
|
|
17
17
|
const baseLang = baseLanguage?.toLowerCase();
|
|
18
18
|
const languages = await getSupportedLanguages();
|
|
19
19
|
const messages = {};
|
|
20
|
+
// @ts-expect-error 2345 argument of type is not assignable to parameter of type
|
|
21
|
+
// (strictNullChecks)
|
|
20
22
|
if (baseLang && languages.has(baseLanguage)) {
|
|
21
23
|
Object.assign(messages, JSON.parse(await readFile(new URL(`${baseLang}.json`, translationsDir), 'utf8')));
|
|
22
24
|
}
|
package/getMessagesUtil.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { getAppsembleMessages, mergeMessages, } from '@appsemble/node-utils';
|
|
2
2
|
import { defaultLocale, extractAppMessages, normalizeBlockName, } from '@appsemble/utils';
|
|
3
3
|
import tags from 'language-tags';
|
|
4
|
-
import {
|
|
4
|
+
import { assertKoaCondition, throwKoaError } from './koa.js';
|
|
5
5
|
export async function getMessagesUtil(ctx, language, appId, merge, { getApp, getAppMessages, getBlockMessages }, override = 'true') {
|
|
6
6
|
if (!tags.check(language)) {
|
|
7
7
|
throwKoaError(ctx, 400, '`Language “${language}” is invalid`');
|
|
@@ -12,7 +12,7 @@ export async function getMessagesUtil(ctx, language, appId, merge, { getApp, get
|
|
|
12
12
|
.find((sub) => sub.type() === 'language');
|
|
13
13
|
const baseLang = baseLanguage && String(baseLanguage).toLowerCase();
|
|
14
14
|
const app = await getApp({ context: ctx, query: { where: { id: appId } } });
|
|
15
|
-
|
|
15
|
+
assertKoaCondition(app != null, ctx, 404, 'App not found');
|
|
16
16
|
const blockPrefixes = [];
|
|
17
17
|
const blockQuery = [];
|
|
18
18
|
const appMessages = await getAppMessages({ context: ctx, app, language });
|
|
@@ -27,13 +27,14 @@ export async function getMessagesUtil(ctx, language, appId, merge, { getApp, get
|
|
|
27
27
|
blockPrefixes.push([blockName, prefix]);
|
|
28
28
|
}),
|
|
29
29
|
};
|
|
30
|
+
// @ts-expect-error 2322 null is not assignable to type (strictNullChecks)
|
|
30
31
|
const blockMessages = await getBlockMessages({ context: ctx, blockQuery, baseLang, lang });
|
|
31
32
|
if ((!appMessages.length || (merge && !appMessages.some((m) => m.language === lang))) &&
|
|
32
33
|
lang !== (app.definition.defaultLanguage || defaultLocale)) {
|
|
33
34
|
throwKoaError(ctx, 404, `Language “${language}” could not be found`);
|
|
34
35
|
}
|
|
35
|
-
const baseLanguageMessages = override === 'true'
|
|
36
|
-
const languageMessages = override === 'true'
|
|
36
|
+
const baseLanguageMessages = override === 'true' ? appMessages.find((m) => m.language === baseLang) : undefined;
|
|
37
|
+
const languageMessages = override === 'true' ? appMessages.find((m) => m.language === lang) : undefined;
|
|
37
38
|
for (const block of blockMessages) {
|
|
38
39
|
const { name } = block;
|
|
39
40
|
const defaultMessages = block.messages?.[defaultLocale];
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Returns all the directory names that are specified in the
|
|
3
|
+
* 'trainingOrder' of the chapters' `properties.json`
|
|
4
|
+
*
|
|
5
|
+
* @param path The absolute path to read the trainings from
|
|
6
|
+
* @returns List of the directory names that contain valid trainings
|
|
7
|
+
*/
|
|
8
|
+
export declare function getValidTrainings(path: string): Promise<string[]>;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { readdir, readFile } from 'node:fs/promises';
|
|
2
|
+
import { logger } from './logger.js';
|
|
3
|
+
/**
|
|
4
|
+
* Returns all the directory names that are specified in the
|
|
5
|
+
* 'trainingOrder' of the chapters' `properties.json`
|
|
6
|
+
*
|
|
7
|
+
* @param path The absolute path to read the trainings from
|
|
8
|
+
* @returns List of the directory names that contain valid trainings
|
|
9
|
+
*/
|
|
10
|
+
export async function getValidTrainings(path) {
|
|
11
|
+
const chapters = await readdir(path);
|
|
12
|
+
const trainingIds = [];
|
|
13
|
+
for (const chapterDirectory of chapters) {
|
|
14
|
+
let properties;
|
|
15
|
+
try {
|
|
16
|
+
properties = JSON.parse(await readFile(`${path}/${chapterDirectory}/properties.json`, 'utf8'));
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
logger.error(`Chapter directory "${chapterDirectory}" is missing a 'properties.json' file`);
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
const missingProperties = ['blockedBy', 'title', 'trainingOrder'].filter((prop) => !Object.prototype.hasOwnProperty.call(properties, prop));
|
|
23
|
+
if (missingProperties.length > 0) {
|
|
24
|
+
logger.error(`Missing properties [${missingProperties}] in "properties.json"`);
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
for (const trainingDirectory of properties.trainingOrder) {
|
|
28
|
+
try {
|
|
29
|
+
await readdir(`${path}/${chapterDirectory}/${trainingDirectory}`);
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
logger.error(`Could not find "${trainingDirectory}"`);
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
try {
|
|
36
|
+
await readFile(`${path}/${chapterDirectory}/${trainingDirectory}/index.md`);
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
logger.error(`Training "${trainingDirectory}" is missing an index.md`);
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
trainingIds.push(trainingDirectory);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return trainingIds;
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=getValidTrainings.js.map
|
package/icon.js
CHANGED
|
@@ -16,6 +16,7 @@ export async function serveIcon(ctx, { background, cache, fallback, height, icon
|
|
|
16
16
|
if (raw) {
|
|
17
17
|
const { format } = await img.metadata();
|
|
18
18
|
ctx.body = buffer;
|
|
19
|
+
// @ts-expect-error 2322 null is not assignable to type (strictNullChecks)
|
|
19
20
|
ctx.type = format;
|
|
20
21
|
return;
|
|
21
22
|
}
|
|
@@ -41,8 +42,10 @@ export async function serveIcon(ctx, { background, cache, fallback, height, icon
|
|
|
41
42
|
// Make the regular icon maskable
|
|
42
43
|
const actual = img;
|
|
43
44
|
const metadata = await actual.metadata();
|
|
45
|
+
// @ts-expect-error 18048 variable is possibly undefined (strictNullChecks)
|
|
44
46
|
const angle = Math.atan(metadata.height / metadata.width);
|
|
45
47
|
actual.resize({
|
|
48
|
+
// @ts-expect-error 18048 variable is possibly undefined (strictNullChecks)
|
|
46
49
|
width: Math.ceil(Math.cos(angle) * safeAreaDiameter * width),
|
|
47
50
|
// By leaving out height, libvips will determine this for us. This has better precision than
|
|
48
51
|
// calculating this using JavasScript and passing it manually.
|
|
@@ -50,7 +53,9 @@ export async function serveIcon(ctx, { background, cache, fallback, height, icon
|
|
|
50
53
|
fit: 'contain',
|
|
51
54
|
background,
|
|
52
55
|
});
|
|
56
|
+
// @ts-expect-error 18048 variable is possibly undefined (strictNullChecks)
|
|
53
57
|
img = sharp(Buffer.alloc(width * height * 4, 0), {
|
|
58
|
+
// @ts-expect-error 2322 null is not assignable to type (strictNullChecks)
|
|
54
59
|
raw: { width, height, channels: 4 },
|
|
55
60
|
});
|
|
56
61
|
img.resize({ width, height });
|
package/index.d.ts
CHANGED
|
@@ -30,10 +30,14 @@ export * from './server/routes/appRouter/iconHandler.js';
|
|
|
30
30
|
export * from './server/controllers/index.js';
|
|
31
31
|
export * from './server/types.js';
|
|
32
32
|
export * from './koa.js';
|
|
33
|
-
export { bulmaVersion, faVersion, version } from './versions.
|
|
33
|
+
export { bulmaVersion, faVersion, version } from './versions.js';
|
|
34
34
|
export * from './EmailQuotaExceededError.js';
|
|
35
35
|
export * from './EmailError.js';
|
|
36
36
|
export * from './AppMemberPropertiesError.js';
|
|
37
37
|
export * from './authentication.js';
|
|
38
38
|
export * from './getMessagesUtil.js';
|
|
39
39
|
export * from './container/index.js';
|
|
40
|
+
export * from './s3.js';
|
|
41
|
+
export * from './uploads.js';
|
|
42
|
+
export * from './assets.js';
|
|
43
|
+
export * from './getValidTrainings.js';
|
package/index.js
CHANGED
|
@@ -30,11 +30,15 @@ export * from './server/routes/appRouter/iconHandler.js';
|
|
|
30
30
|
export * from './server/controllers/index.js';
|
|
31
31
|
export * from './server/types.js';
|
|
32
32
|
export * from './koa.js';
|
|
33
|
-
export { bulmaVersion, faVersion, version } from './versions.
|
|
33
|
+
export { bulmaVersion, faVersion, version } from './versions.js';
|
|
34
34
|
export * from './EmailQuotaExceededError.js';
|
|
35
35
|
export * from './EmailError.js';
|
|
36
36
|
export * from './AppMemberPropertiesError.js';
|
|
37
37
|
export * from './authentication.js';
|
|
38
38
|
export * from './getMessagesUtil.js';
|
|
39
39
|
export * from './container/index.js';
|
|
40
|
+
export * from './s3.js';
|
|
41
|
+
export * from './uploads.js';
|
|
42
|
+
export * from './assets.js';
|
|
43
|
+
export * from './getValidTrainings.js';
|
|
40
44
|
//# sourceMappingURL=index.js.map
|
package/interceptors.js
CHANGED
|
@@ -27,7 +27,7 @@ export function formData(config) {
|
|
|
27
27
|
*/
|
|
28
28
|
export function requestLogger(config) {
|
|
29
29
|
const time = Date.now();
|
|
30
|
-
logger.verbose(`> ${time} ${highlight(`${config.method
|
|
30
|
+
logger.verbose(`> ${time} ${highlight(`${config.method?.toUpperCase()} ${axios.getUri(config)} HTTP/1.1`, {
|
|
31
31
|
language: 'http',
|
|
32
32
|
})}`);
|
|
33
33
|
// eslint-disable-next-line no-param-reassign
|
package/koa.d.ts
CHANGED
|
@@ -56,7 +56,7 @@ type HttpErrorCodes = keyof typeof errorStatusMap;
|
|
|
56
56
|
* @param message The message for in the response body.
|
|
57
57
|
* @param data Optional data to include.
|
|
58
58
|
*/
|
|
59
|
-
export declare function throwKoaError(ctx: Context, status: HttpErrorCodes, message?: string, data?: Record<string, any>):
|
|
59
|
+
export declare function throwKoaError(ctx: Context, status: HttpErrorCodes, message?: string, data?: Record<string, any>): never;
|
|
60
60
|
/**
|
|
61
61
|
* Throw standard Koa error response when condition is met.
|
|
62
62
|
*
|
|
@@ -67,3 +67,15 @@ export declare function throwKoaError(ctx: Context, status: HttpErrorCodes, mess
|
|
|
67
67
|
* @param data Optional data to include.
|
|
68
68
|
*/
|
|
69
69
|
export declare function assertKoaError(condition: boolean, ctx: Context, status: HttpErrorCodes, message?: string, data?: Record<string, any>): void;
|
|
70
|
+
/**
|
|
71
|
+
* Throws standard Koa error response when condition is _not_ met.
|
|
72
|
+
* Does the opposite of `assertKoaError`.
|
|
73
|
+
*
|
|
74
|
+
* @param condition The condition to check.
|
|
75
|
+
* @param ctx The Koa context used to throw the error response.
|
|
76
|
+
* @param status The HTTP status code to use.
|
|
77
|
+
* @param message The message for in the response body.
|
|
78
|
+
* @param data Optional data to include.
|
|
79
|
+
* @see throwKoaError
|
|
80
|
+
*/
|
|
81
|
+
export declare function assertKoaCondition(condition: boolean, ctx: Context, status: HttpErrorCodes, message?: string, data?: Record<string, any>): asserts condition;
|
package/koa.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { convertToCsv } from '@appsemble/utils';
|
|
2
2
|
import { bufferParser, jsonParser, bodyParser as koaBodyParser } from 'koas-body-parser';
|
|
3
3
|
import { serializer as koaSerializer } from 'koas-serializer';
|
|
4
|
-
import { csvParser, xWwwFormUrlencodedParser } from './parsers.js';
|
|
4
|
+
import { csvParser, streamParser, xWwwFormUrlencodedParser } from './parsers.js';
|
|
5
5
|
function serializer() {
|
|
6
6
|
return koaSerializer({
|
|
7
7
|
serializers: {
|
|
8
8
|
'application/scim+json': (body) => JSON.stringify(body),
|
|
9
|
+
// @ts-expect-error 2322 unknown is not assignable to type string (strictNullChecks)
|
|
9
10
|
'application/xml': (body) => body,
|
|
10
11
|
'text/csv': convertToCsv,
|
|
11
12
|
},
|
|
@@ -17,6 +18,7 @@ function bodyParser() {
|
|
|
17
18
|
'application/scim+json': jsonParser,
|
|
18
19
|
'application/x-www-form-urlencoded': xWwwFormUrlencodedParser,
|
|
19
20
|
'text/csv': csvParser,
|
|
21
|
+
'multipart/form-data': streamParser,
|
|
20
22
|
'*/*': bufferParser,
|
|
21
23
|
},
|
|
22
24
|
});
|
|
@@ -98,4 +100,18 @@ export function assertKoaError(condition, ctx, status, message, data) {
|
|
|
98
100
|
throwKoaError(ctx, status, message, data);
|
|
99
101
|
}
|
|
100
102
|
}
|
|
103
|
+
/**
|
|
104
|
+
* Throws standard Koa error response when condition is _not_ met.
|
|
105
|
+
* Does the opposite of `assertKoaError`.
|
|
106
|
+
*
|
|
107
|
+
* @param condition The condition to check.
|
|
108
|
+
* @param ctx The Koa context used to throw the error response.
|
|
109
|
+
* @param status The HTTP status code to use.
|
|
110
|
+
* @param message The message for in the response body.
|
|
111
|
+
* @param data Optional data to include.
|
|
112
|
+
* @see throwKoaError
|
|
113
|
+
*/
|
|
114
|
+
export function assertKoaCondition(condition, ctx, status, message, data) {
|
|
115
|
+
assertKoaError(!condition, ctx, status, message, data);
|
|
116
|
+
}
|
|
101
117
|
//# sourceMappingURL=koa.js.map
|
package/logger.js
CHANGED
|
@@ -15,8 +15,8 @@ function httpErrorToString(error) {
|
|
|
15
15
|
return [
|
|
16
16
|
chalk.blue.bold('Request:'),
|
|
17
17
|
highlight([
|
|
18
|
-
`${config
|
|
19
|
-
...Object.entries(config
|
|
18
|
+
`${config?.method?.toUpperCase()} ${axios.getUri(config)} HTTP/${request?.res?.httpVersion ?? '1.1'}`,
|
|
19
|
+
...Object.entries(config?.headers ?? {})
|
|
20
20
|
.map(([key, value]) => [headerCase(key), value])
|
|
21
21
|
.map(([key, value]) => `${key}: ${key === 'Authorization' ? 'xxxxxxxxxx' : value}`)
|
|
22
22
|
.sort(),
|
|
@@ -40,11 +40,13 @@ function httpErrorToString(error) {
|
|
|
40
40
|
function toString(info) {
|
|
41
41
|
if (info instanceof Error) {
|
|
42
42
|
if ('sql' in info) {
|
|
43
|
-
|
|
43
|
+
// @ts-expect-error 2322 null is not assignable to type (strictNullChecks)
|
|
44
|
+
return info.stack?.replace(/^Error/, `${info.sql}\n${info.name}: ${info.message}`);
|
|
44
45
|
}
|
|
45
46
|
if (axios.isAxiosError(info)) {
|
|
46
47
|
return httpErrorToString(info);
|
|
47
48
|
}
|
|
49
|
+
// @ts-expect-error 2322 null is not assignable to type (strictNullChecks)
|
|
48
50
|
return info.stack;
|
|
49
51
|
}
|
|
50
52
|
if (typeof info.message === 'string') {
|