@crowdin/app-project-module 2.9.1 → 2.11.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/index.d.ts +3 -0
- package/out/index.js +22 -10
- package/out/modules/integration/util/context.d.ts +11 -0
- package/out/modules/integration/util/context.js +17 -0
- package/out/modules/integration/util/cron.js +70 -63
- package/out/modules/integration/util/job.js +88 -80
- package/out/modules/integration/util/webhooks.d.ts +11 -0
- package/out/modules/integration/util/webhooks.js +35 -56
- package/out/static/css/styles.css +2 -2
- package/out/static/ui/about.bundle.js.map +1 -1
- package/out/static/ui/error.bundle.js.map +1 -1
- package/out/static/ui/form.bundle.js +1028 -232
- package/out/static/ui/form.bundle.js.map +1 -1
- package/out/static/ui/install.bundle.js.map +1 -1
- package/out/static/ui/login.bundle.js.map +1 -1
- package/out/static/ui/main.bundle.js.map +1 -1
- package/out/static/ui/oauth.bundle.js.map +1 -1
- package/out/static/ui/subscription.bundle.js.map +1 -1
- package/out/storage/drizzle/index.d.ts +1 -0
- package/out/storage/drizzle/index.js +32 -0
- package/out/storage/types.d.ts +1 -0
- package/out/types.d.ts +15 -1
- package/out/types.js +1 -0
- package/out/util/connection.js +1 -4
- package/out/util/cron.d.ts +0 -2
- package/out/util/cron.js +18 -56
- package/out/util/crowdin-storage.d.ts +7 -0
- package/out/util/crowdin-storage.js +78 -0
- package/out/util/logger.d.ts +23 -1
- package/out/util/logger.js +36 -38
- package/package.json +12 -6
|
@@ -28,6 +28,7 @@ export declare class DrizzleStorage implements Storage {
|
|
|
28
28
|
updateIntegrationManagers(id: string, managers: string): Promise<void>;
|
|
29
29
|
getIntegrationCredentials(id: string): Promise<IntegrationCredentials | undefined>;
|
|
30
30
|
getAllIntegrationCredentials(crowdinId: string): Promise<IntegrationCredentials[]>;
|
|
31
|
+
getIntegrationCredentialsByProject(organization: string, projectId: number): Promise<IntegrationCredentials[]>;
|
|
31
32
|
deleteIntegrationCredentials(id: string): Promise<void>;
|
|
32
33
|
deleteAllIntegrationCredentials(crowdinId: string): Promise<void>;
|
|
33
34
|
saveMetadata(id: string, metadata: any, crowdinId: string): Promise<void>;
|
|
@@ -13,6 +13,7 @@ exports.DrizzleStorage = void 0;
|
|
|
13
13
|
const crypto_1 = require("crypto");
|
|
14
14
|
const drizzle_orm_1 = require("drizzle-orm");
|
|
15
15
|
const types_1 = require("../../modules/integration/util/types");
|
|
16
|
+
const token_1 = require("../../util/app-functions/token");
|
|
16
17
|
const d1_1 = require("./d1");
|
|
17
18
|
const types_2 = require("./d1/types");
|
|
18
19
|
const postgresql_1 = require("./postgresql");
|
|
@@ -274,6 +275,37 @@ class DrizzleStorage {
|
|
|
274
275
|
}));
|
|
275
276
|
});
|
|
276
277
|
}
|
|
278
|
+
getIntegrationCredentialsByProject(organization, projectId) {
|
|
279
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
280
|
+
yield this.migrationPromise;
|
|
281
|
+
// id format: "{organization}__{projectId}__{userId}"
|
|
282
|
+
// parseCrowdinId is the authoritative parser; used as a post-filter in both paths.
|
|
283
|
+
const filterByProjectId = (item) => {
|
|
284
|
+
const { organization: itemOrganization, projectId: itemProjectId } = (0, token_1.parseCrowdinId)(item.id);
|
|
285
|
+
return itemOrganization === organization && itemProjectId === projectId;
|
|
286
|
+
};
|
|
287
|
+
// Org names with underscores make the LIKE pre-filter unreliable (SQL treats _ as a
|
|
288
|
+
// single-char wildcard), so fall back to a full org-level load + JS filter.
|
|
289
|
+
if (organization.includes('_')) {
|
|
290
|
+
const all = yield this.getAllIntegrationCredentials(organization);
|
|
291
|
+
return all.filter(filterByProjectId);
|
|
292
|
+
}
|
|
293
|
+
// LIKE narrows the DB scan to rows whose id starts with the right org and projectId prefix.
|
|
294
|
+
const res = yield this.db.query.integrationCredentials.findMany({
|
|
295
|
+
where: (0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(this.schema.integrationCredentials.crowdin_id, organization), (0, drizzle_orm_1.like)(this.schema.integrationCredentials.id, `${organization}__${projectId}__%`)),
|
|
296
|
+
});
|
|
297
|
+
return (res
|
|
298
|
+
.map((item) => ({
|
|
299
|
+
id: item.id,
|
|
300
|
+
credentials: item.credentials,
|
|
301
|
+
crowdinId: item.crowdin_id,
|
|
302
|
+
managers: item.managers,
|
|
303
|
+
}))
|
|
304
|
+
// SQL LIKE treats _ as a single-char wildcard, so "org__1__%" also matches "org__10__456".
|
|
305
|
+
// parseCrowdinId gives the exact match LIKE cannot guarantee.
|
|
306
|
+
.filter(filterByProjectId));
|
|
307
|
+
});
|
|
308
|
+
}
|
|
277
309
|
deleteIntegrationCredentials(id) {
|
|
278
310
|
return __awaiter(this, void 0, void 0, function* () {
|
|
279
311
|
yield this.migrationPromise;
|
package/out/storage/types.d.ts
CHANGED
|
@@ -28,6 +28,7 @@ export interface Storage {
|
|
|
28
28
|
updateIntegrationManagers(id: string, managers: string): Promise<void>;
|
|
29
29
|
getIntegrationCredentials(id: string): Promise<IntegrationCredentials | undefined>;
|
|
30
30
|
getAllIntegrationCredentials(crowdinId: string): Promise<IntegrationCredentials[]>;
|
|
31
|
+
getIntegrationCredentialsByProject(organization: string, projectId: number): Promise<IntegrationCredentials[]>;
|
|
31
32
|
deleteIntegrationCredentials(id: string): Promise<void>;
|
|
32
33
|
deleteAllIntegrationCredentials(crowdinId: string): Promise<void>;
|
|
33
34
|
saveMetadata(id: string, metadata: any, crowdinId: string): Promise<void>;
|
package/out/types.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import Crowdin from '@crowdin/crowdin-api-client';
|
|
2
|
+
import type { AppStorage } from '@crowdin/apps-storage';
|
|
2
3
|
import { Request } from 'express';
|
|
3
4
|
import { ContextContent } from './modules/context-menu/types';
|
|
4
5
|
import { CustomMTLogic } from './modules/custom-mt/types';
|
|
@@ -393,7 +394,8 @@ export declare enum Scope {
|
|
|
393
394
|
AI_PROVIDERS = "ai.provider",
|
|
394
395
|
AI_PROMPTS = "ai.prompt",
|
|
395
396
|
AI_PROXIES = "ai.proxy",
|
|
396
|
-
APPLICATIONS = "application"
|
|
397
|
+
APPLICATIONS = "application",
|
|
398
|
+
APPLICATION_STORAGE = "application.storage"
|
|
397
399
|
}
|
|
398
400
|
export interface CrowdinClientRequest extends Request {
|
|
399
401
|
crowdinApiClient: Crowdin;
|
|
@@ -478,6 +480,18 @@ export interface CrowdinAppUtilities extends CrowdinMetadataStore {
|
|
|
478
480
|
}) => (req: any, res: any, next: any) => void;
|
|
479
481
|
storage: Storage;
|
|
480
482
|
cron: Cron;
|
|
483
|
+
/**
|
|
484
|
+
* Crowdin-hosted storage for this app installation (the `kv` key-value space).
|
|
485
|
+
* Unlike `storage` (the app's own database), this data lives on the Crowdin side
|
|
486
|
+
* and is shared across the installation. Requires the `crowdin_app` authentication
|
|
487
|
+
* type (the default) - the storage endpoints accept only the application's own
|
|
488
|
+
* token, so every other authentication type is rejected. `kv.user.*` records are
|
|
489
|
+
* private to the identity behind the stored credentials.
|
|
490
|
+
* Optionally enumerate the app's module keys in the type parameter
|
|
491
|
+
* (`crowdinStorage<'main' | 'reports'>(...)`) to get the full
|
|
492
|
+
* `module:{moduleKey}:` prefixes suggested in key positions.
|
|
493
|
+
*/
|
|
494
|
+
crowdinStorage: <M extends string = never>(crowdinId: string) => Promise<AppStorage<M>>;
|
|
481
495
|
}
|
|
482
496
|
export interface CrowdinMetadataStore {
|
|
483
497
|
saveMetadata: (options: {
|
package/out/types.js
CHANGED
|
@@ -36,6 +36,7 @@ var Scope;
|
|
|
36
36
|
Scope["AI_PROMPTS"] = "ai.prompt";
|
|
37
37
|
Scope["AI_PROXIES"] = "ai.proxy";
|
|
38
38
|
Scope["APPLICATIONS"] = "application";
|
|
39
|
+
Scope["APPLICATION_STORAGE"] = "application.storage";
|
|
39
40
|
})(Scope || (exports.Scope = Scope = {}));
|
|
40
41
|
var AccountType;
|
|
41
42
|
(function (AccountType) {
|
package/out/util/connection.js
CHANGED
|
@@ -266,10 +266,7 @@ function resolveIntegrationAccess(clientId) {
|
|
|
266
266
|
const ownerIds = [];
|
|
267
267
|
let projectIntegrationCredentials = [];
|
|
268
268
|
if (!integrationCredentials) {
|
|
269
|
-
projectIntegrationCredentials =
|
|
270
|
-
const { organization: itemOrganization, projectId: itemProjectId } = (0, token_1.parseCrowdinId)(item.id);
|
|
271
|
-
return itemOrganization === organization && itemProjectId === projectId;
|
|
272
|
-
});
|
|
269
|
+
projectIntegrationCredentials = yield (0, storage_1.getStorage)().getIntegrationCredentialsByProject(organization, projectId);
|
|
273
270
|
for (const credentials of projectIntegrationCredentials) {
|
|
274
271
|
ownerIds.push((0, token_1.parseCrowdinId)(credentials.id).userId);
|
|
275
272
|
if (credentials.managers) {
|
package/out/util/cron.d.ts
CHANGED
package/out/util/cron.js
CHANGED
|
@@ -1,72 +1,25 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
-
var ownKeys = function(o) {
|
|
20
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
-
var ar = [];
|
|
22
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
-
return ar;
|
|
24
|
-
};
|
|
25
|
-
return ownKeys(o);
|
|
26
|
-
};
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
35
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
3
|
exports.NodeCron = void 0;
|
|
37
4
|
exports.initialize = initialize;
|
|
38
5
|
exports.getCron = getCron;
|
|
6
|
+
const logs_formatter_1 = require("@crowdin/logs-formatter");
|
|
39
7
|
const logger_1 = require("./logger");
|
|
40
8
|
/**
|
|
41
9
|
* Default Node.js cron handler that uses node-cron
|
|
42
10
|
*/
|
|
43
11
|
class NodeCron {
|
|
44
12
|
constructor() {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
// Dynamically import node-cron
|
|
48
|
-
Promise.resolve().then(() => __importStar(require('node-cron'))).then((nodeCron) => {
|
|
13
|
+
try {
|
|
14
|
+
const nodeCron = require('node-cron');
|
|
49
15
|
this.cron = nodeCron.default || nodeCron;
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
this.pendingSchedules.forEach(({ expression, task }) => {
|
|
53
|
-
this.cron.schedule(expression, task);
|
|
54
|
-
});
|
|
55
|
-
this.pendingSchedules = [];
|
|
56
|
-
})
|
|
57
|
-
.catch((error) => {
|
|
58
|
-
console.error('Failed to load node-cron:', error);
|
|
16
|
+
}
|
|
17
|
+
catch (error) {
|
|
59
18
|
throw new Error('node-cron is required for the default cron. Please install it or provide a custom cron in config.');
|
|
60
|
-
}
|
|
19
|
+
}
|
|
61
20
|
}
|
|
62
21
|
schedule(expression, task) {
|
|
63
|
-
|
|
64
|
-
this.cron.schedule(expression, task);
|
|
65
|
-
}
|
|
66
|
-
else {
|
|
67
|
-
// Queue the task until node-cron is loaded
|
|
68
|
-
this.pendingSchedules.push({ expression, task });
|
|
69
|
-
}
|
|
22
|
+
this.cron.schedule(expression, task);
|
|
70
23
|
}
|
|
71
24
|
}
|
|
72
25
|
exports.NodeCron = NodeCron;
|
|
@@ -78,13 +31,22 @@ let cron;
|
|
|
78
31
|
function initialize(config) {
|
|
79
32
|
if (config.cron) {
|
|
80
33
|
(0, logger_1.log)('Using custom cron implementation');
|
|
81
|
-
cron = config.cron;
|
|
34
|
+
cron = withContextScope(config.cron);
|
|
82
35
|
}
|
|
83
36
|
else {
|
|
84
37
|
(0, logger_1.log)('Using default NodeCron implementation');
|
|
85
|
-
cron = new NodeCron();
|
|
38
|
+
cron = withContextScope(new NodeCron());
|
|
86
39
|
}
|
|
87
40
|
}
|
|
41
|
+
/**
|
|
42
|
+
* Gives every scheduled run its own log context scope, isolating it from requests and other runs.
|
|
43
|
+
* A run that itself fans out across tenants opens per-branch scopes with `runWithContext`.
|
|
44
|
+
*/
|
|
45
|
+
function withContextScope(cron) {
|
|
46
|
+
return {
|
|
47
|
+
schedule: (expression, task) => cron.schedule(expression, () => (0, logs_formatter_1.runWithContext)({}, task)),
|
|
48
|
+
};
|
|
49
|
+
}
|
|
88
50
|
/**
|
|
89
51
|
* Get the initialized cron instance
|
|
90
52
|
* @returns The cron instance
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type AppStorage, type StorageTransport } from '@crowdin/apps-storage';
|
|
2
|
+
import { Config } from '../types';
|
|
3
|
+
export declare function createCrowdinStorageTransport({ config, crowdinId, }: {
|
|
4
|
+
config: Config;
|
|
5
|
+
crowdinId: string;
|
|
6
|
+
}): StorageTransport;
|
|
7
|
+
export declare function createCrowdinStorage<M extends string = never>(config: Config, crowdinId: string): Promise<AppStorage<M>>;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.createCrowdinStorageTransport = createCrowdinStorageTransport;
|
|
16
|
+
exports.createCrowdinStorage = createCrowdinStorage;
|
|
17
|
+
const axios_1 = __importDefault(require("axios"));
|
|
18
|
+
const apps_storage_1 = require("@crowdin/apps-storage");
|
|
19
|
+
const storage_1 = require("../storage");
|
|
20
|
+
const types_1 = require("../types");
|
|
21
|
+
const connection_1 = require("./connection");
|
|
22
|
+
function loadCredentials(crowdinId) {
|
|
23
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
24
|
+
const credentials = yield (0, storage_1.getStorage)().getCrowdinCredentials(crowdinId);
|
|
25
|
+
if (!credentials) {
|
|
26
|
+
throw new Error('Failed to find Crowdin credentials');
|
|
27
|
+
}
|
|
28
|
+
return credentials;
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
function createCrowdinStorageTransport({ config, crowdinId, }) {
|
|
32
|
+
const send = (request, token, apiUrl) => __awaiter(this, void 0, void 0, function* () {
|
|
33
|
+
const url = `${apiUrl}/applications/${encodeURIComponent(config.identifier)}/storage/${request.path}`;
|
|
34
|
+
return axios_1.default.request({
|
|
35
|
+
method: request.method,
|
|
36
|
+
url,
|
|
37
|
+
params: request.query,
|
|
38
|
+
data: request.body,
|
|
39
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
40
|
+
validateStatus: () => true,
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
return (request) => __awaiter(this, void 0, void 0, function* () {
|
|
44
|
+
const initial = yield (0, connection_1.prepareCrowdinClient)({
|
|
45
|
+
config,
|
|
46
|
+
credentials: yield loadCredentials(crowdinId),
|
|
47
|
+
autoRenew: true,
|
|
48
|
+
});
|
|
49
|
+
let response = yield send(request, initial.token, initial.client.url);
|
|
50
|
+
if (response.status === 401) {
|
|
51
|
+
const refreshed = yield (0, connection_1.prepareCrowdinClient)({
|
|
52
|
+
config,
|
|
53
|
+
credentials: yield loadCredentials(crowdinId),
|
|
54
|
+
autoRenew: true,
|
|
55
|
+
});
|
|
56
|
+
response = yield send(request, refreshed.token, refreshed.client.url);
|
|
57
|
+
}
|
|
58
|
+
return { status: response.status, body: response.data };
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
function createCrowdinStorage(config, crowdinId) {
|
|
62
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
63
|
+
const authenticationType = config.authenticationType || types_1.AuthenticationType.APP;
|
|
64
|
+
if (authenticationType !== types_1.AuthenticationType.APP) {
|
|
65
|
+
throw new Error(`Crowdin storage is not available for ${authenticationType} authentication because the storage endpoints accept only the application's own token`);
|
|
66
|
+
}
|
|
67
|
+
yield loadCredentials(crowdinId);
|
|
68
|
+
return (0, apps_storage_1.createStorage)({
|
|
69
|
+
transport: createCrowdinStorageTransport({ config, crowdinId }),
|
|
70
|
+
getUserId: () => __awaiter(this, void 0, void 0, function* () {
|
|
71
|
+
const credentials = yield loadCredentials(crowdinId);
|
|
72
|
+
return credentials.userId !== null && credentials.userId !== undefined
|
|
73
|
+
? String(credentials.userId)
|
|
74
|
+
: undefined;
|
|
75
|
+
}),
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
}
|
package/out/util/logger.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { AxiosError } from 'axios';
|
|
2
|
-
import {
|
|
2
|
+
import { Context } from '@crowdin/logs-formatter';
|
|
3
|
+
import { Config, CrowdinClientRequest, CrowdinContextInfo, CrowdinCredentials, UnauthorizedConfig } from '../types';
|
|
3
4
|
import { IntegrationRequest } from '../modules/integration/types';
|
|
4
5
|
import { Request } from 'express';
|
|
5
6
|
export type LogFunction = (message: string) => void;
|
|
@@ -11,6 +12,27 @@ export type LogContext = {
|
|
|
11
12
|
};
|
|
12
13
|
export declare function initialize(config: Config | UnauthorizedConfig): void;
|
|
13
14
|
export declare function prepareContext(context: CrowdinContextInfo): LogContext;
|
|
15
|
+
/**
|
|
16
|
+
* Maps a framework object to the log context the formatter's record schema expects.
|
|
17
|
+
* A field the source has no value for is omitted, not `''` - the record would emit `''` verbatim.
|
|
18
|
+
* `appIdentifier` is not mapped: it is set once at startup via the formatter's `setup()`.
|
|
19
|
+
* @param source - A `CrowdinContextInfo` yields the full tenant. `CrowdinCredentials` carry no
|
|
20
|
+
* project, so that source yields the organization-level tenant - the shape the record's
|
|
21
|
+
* organization/user fallbacks read.
|
|
22
|
+
* @returns The mapped context. The `Context` return type is load-bearing: the formatter's
|
|
23
|
+
* `setContext()` takes `any` and drops a wrong shape silently.
|
|
24
|
+
*/
|
|
25
|
+
export declare function logContext(source: CrowdinContextInfo): Context;
|
|
26
|
+
export declare function logContext(source: CrowdinCredentials): Context;
|
|
27
|
+
/**
|
|
28
|
+
* Stamps the active scope's log context with the tenant of a Crowdin context - the one place a
|
|
29
|
+
* Crowdin context reaches the formatter. Guarded, so a disabled formatter is never touched (its
|
|
30
|
+
* out-of-scope warning included). Outside any scope the formatter ignores the write and warns once
|
|
31
|
+
* per call site; open one first with `runWithContext` when the work is not a request.
|
|
32
|
+
* @param context - The Crowdin context of the unit of work being logged.
|
|
33
|
+
* @returns The mapped log context, for callers that carry it onward.
|
|
34
|
+
*/
|
|
35
|
+
export declare function setLogCrowdinContext(context: CrowdinContextInfo): Context;
|
|
14
36
|
export declare function withContext(context: CrowdinContextInfo): LogFunction;
|
|
15
37
|
export declare function withContextError(context: CrowdinContextInfo): LogFunction;
|
|
16
38
|
export declare function log(message: string, context?: CrowdinContextInfo): void;
|
package/out/util/logger.js
CHANGED
|
@@ -45,6 +45,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
45
45
|
exports.AppModuleAggregateError = exports.AppUserModuleError = exports.AppModuleError = void 0;
|
|
46
46
|
exports.initialize = initialize;
|
|
47
47
|
exports.prepareContext = prepareContext;
|
|
48
|
+
exports.logContext = logContext;
|
|
49
|
+
exports.setLogCrowdinContext = setLogCrowdinContext;
|
|
48
50
|
exports.withContext = withContext;
|
|
49
51
|
exports.withContextError = withContextError;
|
|
50
52
|
exports.log = log;
|
|
@@ -58,9 +60,11 @@ const logsFormatter = __importStar(require("@crowdin/logs-formatter"));
|
|
|
58
60
|
const storage_1 = require("../storage");
|
|
59
61
|
let logConfig;
|
|
60
62
|
let onError;
|
|
63
|
+
let formatterDisabled = false;
|
|
61
64
|
function initialize(config) {
|
|
62
65
|
logConfig = config.logger;
|
|
63
66
|
onError = config.onError;
|
|
67
|
+
formatterDisabled = Boolean(config.disableLogsFormatter);
|
|
64
68
|
}
|
|
65
69
|
function prepareContext(context) {
|
|
66
70
|
return {
|
|
@@ -69,6 +73,36 @@ function prepareContext(context) {
|
|
|
69
73
|
projectId: context.jwtPayload.context.project_id,
|
|
70
74
|
};
|
|
71
75
|
}
|
|
76
|
+
function logContext(source) {
|
|
77
|
+
if (!('jwtPayload' in source)) {
|
|
78
|
+
return {
|
|
79
|
+
organization: Object.assign({ id: source.organizationId }, (source.domain && { domain: source.domain })),
|
|
80
|
+
user: { id: source.userId },
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
const payload = source.jwtPayload.context;
|
|
84
|
+
return {
|
|
85
|
+
project: Object.assign(Object.assign({ id: payload.project_id }, (payload.project_identifier && { identifier: payload.project_identifier })), { organization_id: payload.organization_id, user_id: payload.user_id }),
|
|
86
|
+
user: Object.assign({ id: payload.user_id }, (payload.user_login && { login: payload.user_login })),
|
|
87
|
+
// the cast: the formatter's type insists on `domain`, and an absent one must stay absent
|
|
88
|
+
organization: Object.assign({ id: payload.organization_id }, (payload.organization_domain && { domain: payload.organization_domain })),
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Stamps the active scope's log context with the tenant of a Crowdin context - the one place a
|
|
93
|
+
* Crowdin context reaches the formatter. Guarded, so a disabled formatter is never touched (its
|
|
94
|
+
* out-of-scope warning included). Outside any scope the formatter ignores the write and warns once
|
|
95
|
+
* per call site; open one first with `runWithContext` when the work is not a request.
|
|
96
|
+
* @param context - The Crowdin context of the unit of work being logged.
|
|
97
|
+
* @returns The mapped log context, for callers that carry it onward.
|
|
98
|
+
*/
|
|
99
|
+
function setLogCrowdinContext(context) {
|
|
100
|
+
const mapped = logContext(context);
|
|
101
|
+
if (!formatterDisabled) {
|
|
102
|
+
logsFormatter.setContext(mapped);
|
|
103
|
+
}
|
|
104
|
+
return mapped;
|
|
105
|
+
}
|
|
72
106
|
function withContext(context) {
|
|
73
107
|
return (message) => log(message, context);
|
|
74
108
|
}
|
|
@@ -76,7 +110,6 @@ function withContextError(context) {
|
|
|
76
110
|
return (e) => logError(e, context);
|
|
77
111
|
}
|
|
78
112
|
function log(message, context) {
|
|
79
|
-
var _a, _b, _c;
|
|
80
113
|
if (logConfig === null || logConfig === void 0 ? void 0 : logConfig.enabled) {
|
|
81
114
|
if (logConfig.log) {
|
|
82
115
|
logConfig.log({ message, context });
|
|
@@ -84,24 +117,7 @@ function log(message, context) {
|
|
|
84
117
|
else {
|
|
85
118
|
let prefix = `[${new Date().toISOString()}]`;
|
|
86
119
|
if (context) {
|
|
87
|
-
|
|
88
|
-
logsFormatter.setContext({
|
|
89
|
-
appIdentifier: context.appIdentifier || '',
|
|
90
|
-
project: {
|
|
91
|
-
id: context.jwtPayload.context.project_id,
|
|
92
|
-
identifier: (_a = context.jwtPayload.context.project_identifier) !== null && _a !== void 0 ? _a : '',
|
|
93
|
-
organization_id: context.jwtPayload.context.organization_id,
|
|
94
|
-
user_id: context.jwtPayload.context.user_id,
|
|
95
|
-
},
|
|
96
|
-
user: {
|
|
97
|
-
id: context.jwtPayload.context.user_id,
|
|
98
|
-
login: (_b = context.jwtPayload.context.user_login) !== null && _b !== void 0 ? _b : '',
|
|
99
|
-
},
|
|
100
|
-
organization: {
|
|
101
|
-
id: context.jwtPayload.context.organization_id,
|
|
102
|
-
domain: (_c = context.jwtPayload.context.organization_domain) !== null && _c !== void 0 ? _c : '',
|
|
103
|
-
},
|
|
104
|
-
});
|
|
120
|
+
setLogCrowdinContext(context);
|
|
105
121
|
prefix += ` Context [${JSON.stringify(prepareContext(context))}]`;
|
|
106
122
|
}
|
|
107
123
|
// eslint-disable-next-line no-console
|
|
@@ -110,30 +126,12 @@ function log(message, context) {
|
|
|
110
126
|
}
|
|
111
127
|
}
|
|
112
128
|
function logError(e, context) {
|
|
113
|
-
var _a, _b, _c;
|
|
114
129
|
if (onError) {
|
|
115
130
|
onError({ error: e, context });
|
|
116
131
|
}
|
|
117
132
|
else {
|
|
118
133
|
if (context) {
|
|
119
|
-
|
|
120
|
-
logsFormatter.setContext({
|
|
121
|
-
appIdentifier: context.appIdentifier || '',
|
|
122
|
-
project: {
|
|
123
|
-
id: context.jwtPayload.context.project_id,
|
|
124
|
-
identifier: (_a = context.jwtPayload.context.project_identifier) !== null && _a !== void 0 ? _a : '',
|
|
125
|
-
organization_id: context.jwtPayload.context.organization_id,
|
|
126
|
-
user_id: context.jwtPayload.context.user_id,
|
|
127
|
-
},
|
|
128
|
-
user: {
|
|
129
|
-
id: context.jwtPayload.context.user_id,
|
|
130
|
-
login: (_b = context.jwtPayload.context.user_login) !== null && _b !== void 0 ? _b : '',
|
|
131
|
-
},
|
|
132
|
-
organization: {
|
|
133
|
-
id: context.jwtPayload.context.organization_id,
|
|
134
|
-
domain: (_c = context.jwtPayload.context.organization_domain) !== null && _c !== void 0 ? _c : '',
|
|
135
|
-
},
|
|
136
|
-
});
|
|
134
|
+
setLogCrowdinContext(context);
|
|
137
135
|
}
|
|
138
136
|
if (e instanceof AppModuleAggregateError) {
|
|
139
137
|
if (e.errors && e.errors.length > 0) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@crowdin/app-project-module",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.11.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",
|
|
@@ -38,12 +38,12 @@
|
|
|
38
38
|
},
|
|
39
39
|
"scripts": {
|
|
40
40
|
"sandbox": "vite --config sandbox/vite.config.ts",
|
|
41
|
-
"build": "
|
|
41
|
+
"build": "pnpm run build-css && pnpm run build-main && pnpm run build-ui",
|
|
42
42
|
"build-css": "tailwindcss -i ./ui/styles/tailwind.css -o ./static/css/styles.css --minify",
|
|
43
|
-
"build-main": "tsc -p ./ && cp -R static out && cp logo.png out &&
|
|
43
|
+
"build-main": "tsc -p ./ && cp -R static out && cp logo.png out && pnpm run copy-migrations",
|
|
44
44
|
"build-ui": "rollup -c rollup-ui.config.mjs",
|
|
45
45
|
"watch-main": "tsc --watch -p ./ && cp -R static out && cp logo.png out",
|
|
46
|
-
"format": "
|
|
46
|
+
"format": "pnpm run prettier && pnpm run lint",
|
|
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}/**/*.{js,ts}\"",
|
|
@@ -57,11 +57,13 @@
|
|
|
57
57
|
"out/**/*"
|
|
58
58
|
],
|
|
59
59
|
"dependencies": {
|
|
60
|
+
"@crowdin/apps-storage": "0.x",
|
|
60
61
|
"@crowdin/crowdin-api-client": "^1.56.1",
|
|
61
|
-
"@crowdin/logs-formatter": "^
|
|
62
|
+
"@crowdin/logs-formatter": "^3.0.0",
|
|
62
63
|
"@godaddy/terminus": "^4.12.1",
|
|
63
64
|
"ajv": "^8.20.0",
|
|
64
65
|
"amqplib": "^2.0.1",
|
|
66
|
+
"axios": "^1.18.1",
|
|
65
67
|
"better-sqlite3": "^13.0.2",
|
|
66
68
|
"cors": "^2.8.6",
|
|
67
69
|
"drizzle-orm": "^0.45.2",
|
|
@@ -97,6 +99,7 @@
|
|
|
97
99
|
"@cloudflare/workers-types": "^4.20260228.0",
|
|
98
100
|
"@eslint/eslintrc": "^3.3.3",
|
|
99
101
|
"@eslint/js": "^10.0.1",
|
|
102
|
+
"@jest/globals": "^30.4.1",
|
|
100
103
|
"@monaco-editor/react": "^4.7.0",
|
|
101
104
|
"@rjsf/core": "^6.6.2",
|
|
102
105
|
"@rjsf/shadcn": "^6.6.2",
|
|
@@ -111,6 +114,7 @@
|
|
|
111
114
|
"@types/better-sqlite3": "^7.6.13",
|
|
112
115
|
"@types/cors": "^2.8.19",
|
|
113
116
|
"@types/express": "^5.0.3",
|
|
117
|
+
"@types/express-serve-static-core": "^5.1.2",
|
|
114
118
|
"@types/jest": "^30.0.0",
|
|
115
119
|
"@types/jsonwebtoken": "^9.0.10",
|
|
116
120
|
"@types/minimatch": "^5.1.2",
|
|
@@ -118,6 +122,7 @@
|
|
|
118
122
|
"@types/pg": "^8.16.0",
|
|
119
123
|
"@types/react": "^19.2.18",
|
|
120
124
|
"@types/react-dom": "^19.2.4",
|
|
125
|
+
"@types/serve-static": "^2.2.0",
|
|
121
126
|
"@types/swagger-jsdoc": "^6.0.4",
|
|
122
127
|
"@typescript-eslint/eslint-plugin": "^8.56.0",
|
|
123
128
|
"@typescript-eslint/parser": "^8.56.0",
|
|
@@ -150,5 +155,6 @@
|
|
|
150
155
|
"bugs": {
|
|
151
156
|
"url": "https://github.com/crowdin/app-project-module/issues"
|
|
152
157
|
},
|
|
153
|
-
"homepage": "https://crowdin.github.io/app-project-module"
|
|
158
|
+
"homepage": "https://crowdin.github.io/app-project-module",
|
|
159
|
+
"packageManager": "pnpm@11.20.0+sha512.9a6f330a95b66446ea088faf1521405a8a01f07fde7124cc9958dfed52d4bb436737e65b08f85f37b46fcba375092558ac51262b816844b22f63406ed166bfee"
|
|
154
160
|
}
|