@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.
@@ -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;
@@ -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) {
@@ -266,10 +266,7 @@ function resolveIntegrationAccess(clientId) {
266
266
  const ownerIds = [];
267
267
  let projectIntegrationCredentials = [];
268
268
  if (!integrationCredentials) {
269
- projectIntegrationCredentials = (yield (0, storage_1.getStorage)().getAllIntegrationCredentials(organization)).filter((item) => {
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) {
@@ -12,8 +12,6 @@ export interface Cron {
12
12
  */
13
13
  export declare class NodeCron implements Cron {
14
14
  private cron;
15
- private pendingSchedules;
16
- private initialized;
17
15
  constructor();
18
16
  schedule(expression: string, task: () => Promise<void>): void;
19
17
  }
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
- this.pendingSchedules = [];
46
- this.initialized = false;
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
- this.initialized = true;
51
- // Schedule all pending tasks
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
- if (this.initialized && this.cron) {
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
+ }
@@ -1,5 +1,6 @@
1
1
  import { AxiosError } from 'axios';
2
- import { Config, CrowdinClientRequest, CrowdinContextInfo, UnauthorizedConfig } from '../types';
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;
@@ -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
- logsFormatter.resetContext();
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
- logsFormatter.resetContext();
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.9.1",
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": "npm run build-css && npm run build-main && npm run build-ui",
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 && npm run copy-migrations",
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": "npm run prettier && npm run lint",
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": "^2.4.0",
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
  }