@crowdin/app-project-module 3.2.2 → 3.4.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/out/middlewares/integration-access-denied.d.ts +6 -2
- package/out/middlewares/integration-access-denied.js +25 -11
- package/out/modules/about.js +2 -10
- package/out/modules/api/api.js +7 -0
- package/out/modules/api/components.d.ts +6 -1
- package/out/modules/api/components.js +6 -1
- package/out/modules/integration/handlers/crowdin-update.js +33 -6
- package/out/modules/integration/handlers/integration-update.js +1 -1
- package/out/modules/integration/handlers/job-list.d.ts +8 -2
- package/out/modules/integration/handlers/job-list.js +30 -11
- package/out/modules/integration/handlers/main.js +15 -5
- package/out/modules/integration/types.d.ts +8 -0
- package/out/modules/integration/util/types.d.ts +2 -0
- package/out/static/css/styles.css +1 -1
- package/out/static/img/crowdin_small.svg +8 -7
- package/out/static/ui/about.bundle.js +11 -1
- package/out/static/ui/about.bundle.js.map +1 -1
- package/out/static/ui/error.bundle.js +8 -8
- package/out/static/ui/error.bundle.js.map +1 -1
- package/out/static/ui/form.bundle.js.map +1 -1
- package/out/static/ui/login.bundle.js +4 -4
- package/out/static/ui/login.bundle.js.map +1 -1
- package/out/static/ui/main.bundle.js +750 -687
- package/out/static/ui/main.bundle.js.map +1 -1
- package/out/static/ui/subscription.bundle.js +1 -1
- package/out/static/ui/subscription.bundle.js.map +1 -1
- package/out/storage/drizzle/index.js +6 -1
- package/out/util/index.d.ts +1 -0
- package/out/util/index.js +16 -1
- package/out/util/logo.d.ts +4 -0
- package/out/util/logo.js +47 -0
- package/out/util/static-files.d.ts +4 -1
- package/out/util/static-files.js +26 -4
- package/package.json +2 -1
|
@@ -906,9 +906,14 @@ class DrizzleStorage {
|
|
|
906
906
|
}
|
|
907
907
|
getAllJobs(params) {
|
|
908
908
|
return __awaiter(this, void 0, void 0, function* () {
|
|
909
|
+
var _a;
|
|
909
910
|
yield this.migrationPromise;
|
|
910
911
|
const items = yield this.db.query.job.findMany({
|
|
911
|
-
where: (0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(this.schema.job.integration_id, params.integrationId), (0, drizzle_orm_1.eq)(this.schema.job.crowdin_id, params.crowdinId)
|
|
912
|
+
where: (0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(this.schema.job.integration_id, params.integrationId), (0, drizzle_orm_1.eq)(this.schema.job.crowdin_id, params.crowdinId), params.finished === undefined
|
|
913
|
+
? undefined
|
|
914
|
+
: params.finished
|
|
915
|
+
? (0, drizzle_orm_1.isNotNull)(this.schema.job.finished_at)
|
|
916
|
+
: (0, drizzle_orm_1.isNull)(this.schema.job.finished_at), ((_a = params.excludeTypes) === null || _a === void 0 ? void 0 : _a.length) ? (0, drizzle_orm_1.notInArray)(this.schema.job.type, params.excludeTypes) : undefined),
|
|
912
917
|
orderBy: (0, drizzle_orm_1.desc)(this.schema.job.created_at),
|
|
913
918
|
limit: params.limit,
|
|
914
919
|
offset: params.offset,
|
package/out/util/index.d.ts
CHANGED
|
@@ -30,6 +30,7 @@ export declare function getFormattedDate({ date, userTimezone }: {
|
|
|
30
30
|
date: Date | number;
|
|
31
31
|
userTimezone: string | undefined;
|
|
32
32
|
}): string;
|
|
33
|
+
export declare function escapeHtml(value: string): string;
|
|
33
34
|
export declare function kebabCase(str?: string): string;
|
|
34
35
|
export declare function snakeCase(str?: string): string;
|
|
35
36
|
export declare function getEnvInt(name: string, defaultValue: number): number;
|
package/out/util/index.js
CHANGED
|
@@ -59,6 +59,7 @@ exports.prepareFormDataMetadataIdFromContext = prepareFormDataMetadataIdFromCont
|
|
|
59
59
|
exports.prepareFormDataMetadataId = prepareFormDataMetadataId;
|
|
60
60
|
exports.validateEmail = validateEmail;
|
|
61
61
|
exports.getFormattedDate = getFormattedDate;
|
|
62
|
+
exports.escapeHtml = escapeHtml;
|
|
62
63
|
exports.kebabCase = kebabCase;
|
|
63
64
|
exports.snakeCase = snakeCase;
|
|
64
65
|
exports.getEnvInt = getEnvInt;
|
|
@@ -69,6 +70,7 @@ const types_1 = require("../types");
|
|
|
69
70
|
const jsx_renderer_1 = require("./jsx-renderer");
|
|
70
71
|
const logger_1 = require("./logger");
|
|
71
72
|
const static_files_1 = require("./static-files");
|
|
73
|
+
const logo_1 = require("./logo");
|
|
72
74
|
const token_1 = require("./app-functions/token");
|
|
73
75
|
/**
|
|
74
76
|
* Extract file name from path (works in both Node.js and Cloudflare Workers)
|
|
@@ -252,7 +254,12 @@ function serveLogo(config, moduleConfig) {
|
|
|
252
254
|
}
|
|
253
255
|
const imagePath = config.imagePath;
|
|
254
256
|
const fileName = basename(imagePath);
|
|
255
|
-
|
|
257
|
+
// Generated letter logo when the logo file is missing
|
|
258
|
+
const svg = (0, logo_1.generateLetterLogoSvg)(config.name);
|
|
259
|
+
const fileHandler = (0, static_files_1.serveFile)(config, imagePath, (req, res) => {
|
|
260
|
+
res.setHeader('Content-Type', 'image/svg+xml; charset=utf-8');
|
|
261
|
+
res.send(svg);
|
|
262
|
+
});
|
|
256
263
|
return (req, res, next) => __awaiter(this, void 0, void 0, function* () {
|
|
257
264
|
// Match exact paths: /logo.png (backwards-compatible) or /{actual-file-name}
|
|
258
265
|
if (req.path === '/logo.png' || req.path === `/${fileName}`) {
|
|
@@ -326,6 +333,14 @@ function getFormattedDate({ date, userTimezone }) {
|
|
|
326
333
|
timeZone: userTimezone,
|
|
327
334
|
}).format(date);
|
|
328
335
|
}
|
|
336
|
+
function escapeHtml(value) {
|
|
337
|
+
return value
|
|
338
|
+
.replace(/&/g, '&')
|
|
339
|
+
.replace(/</g, '<')
|
|
340
|
+
.replace(/>/g, '>')
|
|
341
|
+
.replace(/"/g, '"')
|
|
342
|
+
.replace(/'/g, ''');
|
|
343
|
+
}
|
|
329
344
|
function kebabCase(str) {
|
|
330
345
|
if (!str) {
|
|
331
346
|
return '';
|
package/out/util/logo.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Generated fallback logo (rounded square with the first letter of the app name)
|
|
4
|
+
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.generateLetterLogoSvg = generateLetterLogoSvg;
|
|
7
|
+
const index_1 = require("./index");
|
|
8
|
+
const PALETTE = [
|
|
9
|
+
{ background: '#DBEAFE', foreground: '#1E3A8A' },
|
|
10
|
+
{ background: '#DCFCE7', foreground: '#14532D' },
|
|
11
|
+
{ background: '#FEF3C7', foreground: '#78350F' },
|
|
12
|
+
{ background: '#FCE7F3', foreground: '#831843' },
|
|
13
|
+
{ background: '#EDE9FE', foreground: '#4C1D95' },
|
|
14
|
+
{ background: '#CFFAFE', foreground: '#164E63' },
|
|
15
|
+
{ background: '#FFE4E6', foreground: '#881337' },
|
|
16
|
+
{ background: '#E0E7FF', foreground: '#312E81' },
|
|
17
|
+
];
|
|
18
|
+
const NEUTRAL = { background: '#E2E8F0', foreground: '#334155' };
|
|
19
|
+
function getLogoColors(name) {
|
|
20
|
+
// FNV-1a
|
|
21
|
+
let hash = 0x811c9dc5;
|
|
22
|
+
for (let i = 0; i < name.length; i++) {
|
|
23
|
+
hash ^= name.charCodeAt(i);
|
|
24
|
+
hash = Math.imul(hash, 0x01000193) >>> 0;
|
|
25
|
+
}
|
|
26
|
+
return PALETTE[hash % PALETTE.length];
|
|
27
|
+
}
|
|
28
|
+
function generateLetterLogoSvg(name) {
|
|
29
|
+
const text = (name || '').trim();
|
|
30
|
+
const letter = text.charAt(0).toUpperCase();
|
|
31
|
+
const colors = letter ? getLogoColors(text) : NEUTRAL;
|
|
32
|
+
const content = letter
|
|
33
|
+
? `<text x="16" y="16" text-anchor="middle" dominant-baseline="central"
|
|
34
|
+
font-family="system-ui,-apple-system,Segoe UI,Roboto,Arial,sans-serif" font-size="19" font-weight="600"
|
|
35
|
+
fill="${colors.foreground}">${(0, index_1.escapeHtml)(letter)}</text>`
|
|
36
|
+
: `<g fill="${colors.foreground}">
|
|
37
|
+
<rect x="8" y="8" width="7" height="7" rx="1.5"/>
|
|
38
|
+
<rect x="17" y="8" width="7" height="7" rx="1.5"/>
|
|
39
|
+
<rect x="8" y="17" width="7" height="7" rx="1.5"/>
|
|
40
|
+
<rect x="17" y="17" width="7" height="7" rx="1.5"/>
|
|
41
|
+
</g>`;
|
|
42
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
|
|
43
|
+
<title>${(0, index_1.escapeHtml)(text || 'App')}</title>
|
|
44
|
+
<rect width="32" height="32" rx="7" fill="${colors.background}"/>
|
|
45
|
+
${content}
|
|
46
|
+
</svg>`;
|
|
47
|
+
}
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import express, { Request, Response } from 'express';
|
|
6
6
|
import { Config, UnauthorizedConfig } from '../types';
|
|
7
|
+
type MissingFileHandler = (req: Request, res: Response) => void;
|
|
7
8
|
/**
|
|
8
9
|
* Serve static directory with dual compatibility
|
|
9
10
|
* @param config - App configuration
|
|
@@ -14,5 +15,7 @@ export declare function serveStatic(config: Config | UnauthorizedConfig, staticP
|
|
|
14
15
|
* Serve single file (e.g., logo.png, icons/star.svg) with dual compatibility
|
|
15
16
|
* @param config - App configuration
|
|
16
17
|
* @param filePath - Path to file
|
|
18
|
+
* @param onMissing - Called instead of the default handling when the file does not exist
|
|
17
19
|
*/
|
|
18
|
-
export declare function serveFile(config: Config | UnauthorizedConfig, filePath: string): (req: Request, res: Response, next: Function) => Promise<void>;
|
|
20
|
+
export declare function serveFile(config: Config | UnauthorizedConfig, filePath: string, onMissing?: MissingFileHandler): (req: Request, res: Response, next: Function) => Promise<void>;
|
|
21
|
+
export {};
|
package/out/util/static-files.js
CHANGED
|
@@ -23,7 +23,7 @@ const path_1 = __importDefault(require("path"));
|
|
|
23
23
|
/**
|
|
24
24
|
* Proxy Workers Assets response to Express response
|
|
25
25
|
*/
|
|
26
|
-
function proxyAssetsResponse(fetcher, assetPath, baseUrl, req, res, next) {
|
|
26
|
+
function proxyAssetsResponse(fetcher, assetPath, baseUrl, req, res, next, onMissing) {
|
|
27
27
|
return __awaiter(this, void 0, void 0, function* () {
|
|
28
28
|
try {
|
|
29
29
|
if (assetPath === '/') {
|
|
@@ -31,6 +31,9 @@ function proxyAssetsResponse(fetcher, assetPath, baseUrl, req, res, next) {
|
|
|
31
31
|
}
|
|
32
32
|
const assetUrl = new URL(assetPath, baseUrl);
|
|
33
33
|
const response = yield fetcher.fetch(assetUrl.toString());
|
|
34
|
+
if (response.status === 404 && onMissing) {
|
|
35
|
+
return onMissing(req, res);
|
|
36
|
+
}
|
|
34
37
|
if (!response.ok) {
|
|
35
38
|
return next();
|
|
36
39
|
}
|
|
@@ -68,19 +71,38 @@ function serveStatic(config, staticPath) {
|
|
|
68
71
|
* Serve single file (e.g., logo.png, icons/star.svg) with dual compatibility
|
|
69
72
|
* @param config - App configuration
|
|
70
73
|
* @param filePath - Path to file
|
|
74
|
+
* @param onMissing - Called instead of the default handling when the file does not exist
|
|
71
75
|
*/
|
|
72
|
-
function serveFile(config, filePath) {
|
|
76
|
+
function serveFile(config, filePath, onMissing) {
|
|
73
77
|
return (req, res, next) => __awaiter(this, void 0, void 0, function* () {
|
|
74
78
|
var _a;
|
|
75
79
|
if ((_a = config.assetsConfig) === null || _a === void 0 ? void 0 : _a.fetcher) {
|
|
76
80
|
const assetsFetcher = config.assetsConfig.fetcher;
|
|
77
81
|
const assetPath = filePath.startsWith('/') ? filePath : `/${filePath}`;
|
|
78
|
-
yield proxyAssetsResponse(assetsFetcher, assetPath, config.baseUrl, req, res, next);
|
|
82
|
+
yield proxyAssetsResponse(assetsFetcher, assetPath, config.baseUrl, req, res, next, onMissing);
|
|
79
83
|
}
|
|
80
84
|
else {
|
|
81
85
|
const rootDir = path_1.default.dirname(__dirname);
|
|
82
86
|
const absolutePath = path_1.default.isAbsolute(filePath) ? filePath : path_1.default.join(rootDir, filePath);
|
|
83
|
-
|
|
87
|
+
if (!onMissing) {
|
|
88
|
+
res.sendFile(absolutePath);
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
// Mirrors Express default sendFile error handling, except for a missing file
|
|
92
|
+
res.sendFile(absolutePath, (err) => {
|
|
93
|
+
if (!err) {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
if (err.code === 'ENOENT' || err.code === 'ENOTDIR') {
|
|
97
|
+
return onMissing(req, res);
|
|
98
|
+
}
|
|
99
|
+
if (err.code === 'EISDIR') {
|
|
100
|
+
return next();
|
|
101
|
+
}
|
|
102
|
+
if (err.code !== 'ECONNABORTED' && err.syscall !== 'write') {
|
|
103
|
+
next(err);
|
|
104
|
+
}
|
|
105
|
+
});
|
|
84
106
|
}
|
|
85
107
|
});
|
|
86
108
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@crowdin/app-project-module",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.4.0",
|
|
4
4
|
"description": "Module that generates for you all common endpoints for serving standalone Crowdin App",
|
|
5
5
|
"main": "out/index.js",
|
|
6
6
|
"types": "out/index.d.ts",
|
|
@@ -47,6 +47,7 @@
|
|
|
47
47
|
"lint": "eslint --fix \"{src,tests,ui}/**/*.{js,ts,tsx}\"",
|
|
48
48
|
"prettier": "prettier --config .prettierrc {src,ui}/**/*.{ts,tsx} --write",
|
|
49
49
|
"lint-ci": "eslint \"{src,tests,ui}/**/*.{js,ts,tsx}\"",
|
|
50
|
+
"typecheck-tests": "tsc -p tsconfig.test.json",
|
|
50
51
|
"test-coverage": "jest --ci --reporters=jest-junit --reporters=default --coverage --coverageReporters=cobertura --coverageReporters=html",
|
|
51
52
|
"test": "jest",
|
|
52
53
|
"copy-migrations": "cp -R src/storage/drizzle/sqlite/migrations out/storage/drizzle/sqlite/migrations && cp -R src/storage/drizzle/postgresql/migrations out/storage/drizzle/postgresql/migrations",
|