@crowdin/app-project-module 2.9.1 → 2.10.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.
@@ -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
@@ -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.10.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}\"",
@@ -58,10 +58,11 @@
58
58
  ],
59
59
  "dependencies": {
60
60
  "@crowdin/crowdin-api-client": "^1.56.1",
61
- "@crowdin/logs-formatter": "^2.4.0",
61
+ "@crowdin/logs-formatter": "^3.0.0",
62
62
  "@godaddy/terminus": "^4.12.1",
63
63
  "ajv": "^8.20.0",
64
64
  "amqplib": "^2.0.1",
65
+ "axios": "^1.18.1",
65
66
  "better-sqlite3": "^13.0.2",
66
67
  "cors": "^2.8.6",
67
68
  "drizzle-orm": "^0.45.2",
@@ -97,6 +98,7 @@
97
98
  "@cloudflare/workers-types": "^4.20260228.0",
98
99
  "@eslint/eslintrc": "^3.3.3",
99
100
  "@eslint/js": "^10.0.1",
101
+ "@jest/globals": "^30.4.1",
100
102
  "@monaco-editor/react": "^4.7.0",
101
103
  "@rjsf/core": "^6.6.2",
102
104
  "@rjsf/shadcn": "^6.6.2",
@@ -111,6 +113,7 @@
111
113
  "@types/better-sqlite3": "^7.6.13",
112
114
  "@types/cors": "^2.8.19",
113
115
  "@types/express": "^5.0.3",
116
+ "@types/express-serve-static-core": "^5.1.2",
114
117
  "@types/jest": "^30.0.0",
115
118
  "@types/jsonwebtoken": "^9.0.10",
116
119
  "@types/minimatch": "^5.1.2",
@@ -118,6 +121,7 @@
118
121
  "@types/pg": "^8.16.0",
119
122
  "@types/react": "^19.2.18",
120
123
  "@types/react-dom": "^19.2.4",
124
+ "@types/serve-static": "^2.2.0",
121
125
  "@types/swagger-jsdoc": "^6.0.4",
122
126
  "@typescript-eslint/eslint-plugin": "^8.56.0",
123
127
  "@typescript-eslint/parser": "^8.56.0",
@@ -150,5 +154,6 @@
150
154
  "bugs": {
151
155
  "url": "https://github.com/crowdin/app-project-module/issues"
152
156
  },
153
- "homepage": "https://crowdin.github.io/app-project-module"
157
+ "homepage": "https://crowdin.github.io/app-project-module",
158
+ "packageManager": "pnpm@11.20.0+sha512.9a6f330a95b66446ea088faf1521405a8a01f07fde7124cc9958dfed52d4bb436737e65b08f85f37b46fcba375092558ac51262b816844b22f63406ed166bfee"
154
159
  }