@platforma-sdk/tengo-builder 1.19.2 → 2.0.1

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.
@@ -37,11 +37,11 @@ export function createArtifactNameSet(): ArtifactMap<TypedArtifactName> {
37
37
  }
38
38
 
39
39
  export class ArtifactStore<T> {
40
- private readonly dev: ArtifactMap<T>;
40
+ // private readonly dev: ArtifactMap<T>;
41
41
  private readonly dist: ArtifactMap<T>;
42
42
 
43
- constructor(private readonly nameExtractor: (obj: T) => TypedArtifactName) {
44
- this.dev = new ArtifactMap<T>(nameExtractor);
43
+ constructor(nameExtractor: (obj: T) => TypedArtifactName) {
44
+ // this.dev = new ArtifactMap<T>(nameExtractor);
45
45
  this.dist = new ArtifactMap<T>(nameExtractor);
46
46
  }
47
47
 
@@ -9,6 +9,7 @@ import {
9
9
  typedArtifactNameToString,
10
10
  artifactNameToString,
11
11
  formatArtefactNameAndVersion, typedArtifactNamesEquals,
12
+ fullNameEquals,
12
13
  } from './package';
13
14
  import { ArtifactStore } from './artifactset';
14
15
  import { assertNever } from './util';
@@ -127,7 +128,7 @@ export class TengoTemplateCompiler {
127
128
 
128
129
  addLib(lib: ArtifactSource) {
129
130
  const libFromMap = this.libs.add(lib.compileMode, lib, false);
130
- if (libFromMap)
131
+ if (libFromMap && !fullNameEquals(lib.fullName, libFromMap.fullName))
131
132
  throw new Error(
132
133
  `compiler already contain such library: adding = ${fullNameToString(lib.fullName)}, contains = ${fullNameToString(libFromMap.fullName)}`,
133
134
  );
@@ -152,7 +153,7 @@ export class TengoTemplateCompiler {
152
153
 
153
154
  addSoftware(software: ArtifactSource) {
154
155
  const swFromMap = this.software.add(software.compileMode, software, false);
155
- if (swFromMap)
156
+ if (swFromMap && !fullNameEquals(software.fullName, swFromMap.fullName))
156
157
  throw new Error(
157
158
  `compiler already contain info for software: adding = ${fullNameToString(software.fullName)}, contains = ${fullNameToString(swFromMap.fullName)}`,
158
159
  );
@@ -178,7 +179,7 @@ export class TengoTemplateCompiler {
178
179
 
179
180
  addAsset(asset: ArtifactSource) {
180
181
  const assetFromMap = this.assets.add(asset.compileMode, asset, false);
181
- if (assetFromMap)
182
+ if (assetFromMap && !fullNameEquals(asset.fullName, assetFromMap.fullName))
182
183
  throw new Error(
183
184
  `compiler already contain info for asset: adding = ${fullNameToString(asset.fullName)}, contains = ${fullNameToString(assetFromMap.fullName)}`,
184
185
  );
@@ -204,7 +205,7 @@ export class TengoTemplateCompiler {
204
205
 
205
206
  addTemplate(tpl: Template) {
206
207
  const tplFromMap = this.templates.add(tpl.compileMode, tpl, false);
207
- if (tplFromMap)
208
+ if (tplFromMap && !fullNameEquals(tpl.fullName, tplFromMap.fullName))
208
209
  throw new Error(
209
210
  `compiler already contain such template: adding = ${fullNameToString(tpl.fullName)}, contains = ${fullNameToString(tplFromMap.fullName)}`,
210
211
  );
@@ -301,7 +302,7 @@ export class TengoTemplateCompiler {
301
302
  for (const dep of unsatisfied) {
302
303
  errorMessage += ` - ${typedArtifactNameToString(dep)}\n`;
303
304
  }
304
- unprocessed.push({ src, err: Error(errorMessage) });
305
+ unprocessed.push({ src, err: new Error(errorMessage) });
305
306
 
306
307
  continue;
307
308
  }
@@ -2,25 +2,45 @@
2
2
 
3
3
  import * as path from 'node:path';
4
4
  import * as fs from 'node:fs';
5
- import { findNodeModules, pathType } from './util';
5
+ import { pathType } from './util';
6
6
  import type { TemplatesAndLibs } from './compiler';
7
7
  import { TengoTemplateCompiler } from './compiler';
8
8
  import type {
9
9
  CompileMode,
10
10
  FullArtifactName } from './package';
11
11
  import {
12
- artifactNameToString,
13
12
  fullNameToString,
14
13
  typedArtifactNameToString,
15
14
  } from './package';
16
15
  import { ArtifactSource, parseSourceFile } from './source';
17
16
  import { Template } from './template';
18
17
  import type winston from 'winston';
18
+ import { tryResolve, tryResolveOrError } from '@milaboratories/resolve-helper';
19
+
20
+ interface PackageId {
21
+ /** Package name from package.json */
22
+ readonly name: string;
23
+ /** Package version from package.json */
24
+ readonly version: string;
25
+ }
26
+
27
+ interface PackageInfo extends PackageId {
28
+ /** Package type from package.json */
29
+ readonly type: string | undefined;
30
+ /** Path to package root */
31
+ readonly root: string;
32
+ /** Context of package info */
33
+ readonly context: PackageInfoContext;
34
+ /** Dependencies */
35
+ readonly dependencies: PackageInfo[];
36
+ }
19
37
 
20
38
  interface PackageJson {
21
39
  name: string;
22
40
  version: string;
23
- type: string;
41
+ type: string | undefined;
42
+ dependencies: Record<string, string>;
43
+ devDependencies: Record<string, string>;
24
44
  }
25
45
 
26
46
  const compiledTplSuffix = '.plj.gz';
@@ -39,8 +59,78 @@ const srcSoftwareSuffix = '.sw.json';
39
59
  const srcAssetSuffix = '.as.json';
40
60
  const compilableSuffixes = [srcLibSuffix, srcTplSuffix, srcSoftwareSuffix, srcAssetSuffix];
41
61
 
42
- export function getPackageInfo(): PackageJson {
43
- const packageInfo: PackageJson = JSON.parse(fs.readFileSync('package.json').toString()) as PackageJson;
62
+ function resolvePackageJsonPath(root: string, packageName?: string): string | undefined {
63
+ if (!path.isAbsolute(root))
64
+ throw new Error(`Root path must be absolute: ${root}`);
65
+ if (!packageName) {
66
+ const p = path.join(root, 'package.json');
67
+ if (pathType(p) === 'file')
68
+ return p;
69
+ throw new Error(`Can't resolve package.json in ${root}`);
70
+ }
71
+ let resolved = tryResolve(root, packageName);
72
+ if (resolved) {
73
+ let depth = 0;
74
+ do {
75
+ const p = path.join(resolved, 'package.json');
76
+ if (pathType(p) === 'file')
77
+ return p;
78
+ depth++;
79
+ resolved = path.dirname(resolved);
80
+ } while (depth < 7 && path.basename(resolved) !== 'node_modules');
81
+ }
82
+ const resolved2 = tryResolveOrError(root, `${packageName}/package.json`);
83
+ if (resolved2.result === undefined) {
84
+ if (resolved2.err === 'ERR_PACKAGE_PATH_NOT_EXPORTED')
85
+ // tolerating not-exported package.json for dev dependencies
86
+ return undefined;
87
+ throw new Error(`Can't resolve package.json for package ${packageName ?? '.'} relative to ${root}`);
88
+ }
89
+ return resolved2.result;
90
+ }
91
+
92
+ type PackageInfoContext = 'root' | 'dependency' | 'devDependency';
93
+
94
+ /**
95
+ * Get package info from package.json and all dependencies.
96
+ * @param root - Root directory of the package.
97
+ * @param cion
98
+ * @returns Package info.
99
+ */
100
+ export function getPackageInfo(root: string, logger: winston.Logger, context: PackageInfoContext = 'root'): PackageInfo {
101
+ const packageJsonPath = resolvePackageJsonPath(root);
102
+ if (!packageJsonPath)
103
+ throw new Error(`Can't resolve package.json for root package ${root}`);
104
+ const { name, version, type, dependencies, devDependencies }: PackageJson = JSON.parse(fs.readFileSync(packageJsonPath).toString()) as PackageJson;
105
+
106
+ // resolving dependencies
107
+ const depInfos: PackageInfo[] = [];
108
+
109
+ if (dependencies && context !== 'devDependency') {
110
+ for (const dep of Object.keys(dependencies)) {
111
+ const depPackageJson = resolvePackageJsonPath(root, dep);
112
+ if (depPackageJson === undefined)
113
+ throw new Error(`Can't resolve package.json for dependency ${dep} of ${root}`);
114
+ const depRoot = path.dirname(depPackageJson);
115
+ depInfos.push(getPackageInfo(depRoot, logger, 'dependency'));
116
+ }
117
+ }
118
+
119
+ if (devDependencies && context === 'root') {
120
+ for (const dep of Object.keys(devDependencies)) {
121
+ const depPackageJson = resolvePackageJsonPath(root, dep);
122
+ if (depPackageJson === undefined) {
123
+ logger.warn(`Can't resolve package.json for dev dependency ${dep} of ${root}`);
124
+ // tolerating not-exported package.json for dev dependencies
125
+ continue;
126
+ }
127
+ const depRoot = path.dirname(depPackageJson);
128
+ depInfos.push(getPackageInfo(depRoot, logger, 'devDependency'));
129
+ }
130
+ }
131
+
132
+ const packageInfo: PackageInfo = { name, version, type, dependencies: depInfos, root, context };
133
+
44
134
  return packageInfo;
45
135
  }
46
136
 
@@ -63,32 +153,20 @@ function resolveAssetsDst(mode: CompileMode, root: string) {
63
153
  function loadDependencies(
64
154
  logger: winston.Logger,
65
155
  compiler: TengoTemplateCompiler,
66
- packageInfo: PackageJson,
67
- searchIn: string,
68
- isLink: boolean = false,
156
+ packageInfo: PackageInfo,
69
157
  ): void {
70
- const packageJsonPath = path.resolve(searchIn, 'package.json');
71
-
72
- if (pathType(packageJsonPath) !== 'file') {
73
- // We're not in package root. Recursively iterate over all folders looking for packages.
74
-
75
- for (const f of fs.readdirSync(searchIn)) {
76
- const isLink = pathType(path.join(searchIn, f)) === 'link';
77
- const file = path.resolve(searchIn, f);
78
- const type = pathType(file);
79
- if (type === 'dir') {
80
- loadDependencies(logger, compiler, packageInfo, file, isLink);
81
- }
82
- }
158
+ for (const dep of packageInfo.dependencies)
159
+ loadDependencies(logger, compiler, dep);
83
160
 
161
+ if (packageInfo.context === 'root')
162
+ // we are not reading compiled files for root package
84
163
  return;
85
- }
86
164
 
87
165
  // we are in package folder
88
- const libDistFolder = resolveLibsDst('dist', searchIn);
89
- const tplDistFolder = resolveTemplatesDst('dist', searchIn);
90
- const softwareDistFolder = resolveSoftwareDst('dist', searchIn);
91
- const assetDistFolder = resolveAssetsDst('dist', searchIn);
166
+ const libDistFolder = resolveLibsDst('dist', packageInfo.root);
167
+ const tplDistFolder = resolveTemplatesDst('dist', packageInfo.root);
168
+ const softwareDistFolder = resolveSoftwareDst('dist', packageInfo.root);
169
+ const assetDistFolder = resolveAssetsDst('dist', packageInfo.root);
92
170
 
93
171
  const libDistExists = pathType(libDistFolder) === 'dir';
94
172
  const tplDistExists = pathType(tplDistFolder) === 'dir';
@@ -99,37 +177,28 @@ function loadDependencies(
99
177
  // if neither of tengo-specific folders detected, skipping package
100
178
  return;
101
179
 
102
- // we are in tengo dependency folder
103
- const packageJson: PackageJson = JSON.parse(fs.readFileSync(packageJsonPath).toString()) as PackageJson;
104
-
105
- // in a workspace we will find ourselves in node_modules, ignoring
106
- if (packageJson.name === packageInfo.name) return;
107
-
108
- if (pathType(path.resolve(searchIn, 'node_modules')) === 'dir' && isLink)
109
- throw new Error(
110
- `nested node_modules is a sign of library dependencies version incompatibility in ${searchIn}`,
111
- );
180
+ const packageId = { name: packageInfo.name, version: packageInfo.version };
112
181
 
113
182
  if (libDistExists) {
114
- loadLibsFromDir(logger, packageJson, 'dist', libDistFolder, compiler);
183
+ loadLibsFromDir(logger, packageId, 'dist', libDistFolder, compiler);
115
184
  }
116
185
 
117
186
  if (tplDistExists) {
118
- loadTemplatesFromDir(logger, packageJson, 'dist', tplDistFolder, compiler);
187
+ loadTemplatesFromDir(logger, packageId, 'dist', tplDistFolder, compiler);
119
188
  }
120
189
 
121
190
  if (softwareDistExists) {
122
- loadSoftwareFromDir(logger, packageJson, 'dist', softwareDistFolder, compiler);
191
+ loadSoftwareFromDir(logger, packageId, 'dist', softwareDistFolder, compiler);
123
192
  }
124
193
 
125
194
  if (assetDistExists) {
126
- loadAssetsFromDir(logger, packageJson, 'dist', assetDistFolder, compiler);
195
+ loadAssetsFromDir(logger, packageId, 'dist', assetDistFolder, compiler);
127
196
  }
128
197
  }
129
198
 
130
199
  function loadLibsFromDir(
131
200
  logger: winston.Logger,
132
- packageJson: PackageJson,
201
+ packageId: PackageId,
133
202
  mode: CompileMode,
134
203
  folder: string,
135
204
  compiler: TengoTemplateCompiler,
@@ -139,9 +208,9 @@ function loadLibsFromDir(
139
208
  if (!f.endsWith(compiledLibSuffix)) throw new Error(`unexpected file in 'lib' folder: ${file}`);
140
209
  const fullName: FullArtifactName = {
141
210
  type: 'library',
142
- pkg: packageJson.name,
211
+ pkg: packageId.name,
143
212
  id: f.slice(0, f.length - compiledLibSuffix.length),
144
- version: packageJson.version,
213
+ version: packageId.version,
145
214
  };
146
215
  const src = parseSourceFile(logger, mode, file, fullName, true);
147
216
  compiler.addLib(src);
@@ -155,7 +224,7 @@ function loadLibsFromDir(
155
224
 
156
225
  function loadTemplatesFromDir(
157
226
  logger: winston.Logger,
158
- packageJson: PackageJson,
227
+ packageId: PackageId,
159
228
  mode: CompileMode,
160
229
  folder: string,
161
230
  compiler: TengoTemplateCompiler,
@@ -166,9 +235,9 @@ function loadTemplatesFromDir(
166
235
  if (!f.endsWith(compiledTplSuffix)) throw new Error(`unexpected file in 'tpl' folder: ${file}`);
167
236
  const fullName: FullArtifactName = {
168
237
  type: 'template',
169
- pkg: packageJson.name,
238
+ pkg: packageId.name,
170
239
  id: f.slice(0, f.length - compiledTplSuffix.length),
171
- version: packageJson.version,
240
+ version: packageId.version,
172
241
  };
173
242
  const tpl = new Template(mode, fullName, { content: fs.readFileSync(file) });
174
243
  compiler.addTemplate(tpl);
@@ -178,7 +247,7 @@ function loadTemplatesFromDir(
178
247
 
179
248
  function loadSoftwareFromDir(
180
249
  logger: winston.Logger,
181
- packageJson: PackageJson,
250
+ packageId: PackageId,
182
251
  mode: CompileMode,
183
252
  folder: string,
184
253
  compiler: TengoTemplateCompiler,
@@ -189,9 +258,9 @@ function loadSoftwareFromDir(
189
258
  throw new Error(`unexpected file in 'software' folder: ${file}`);
190
259
  const fullName: FullArtifactName = {
191
260
  type: 'software',
192
- pkg: packageJson.name,
261
+ pkg: packageId.name,
193
262
  id: f.slice(0, f.length - compiledSoftwareSuffix.length),
194
- version: packageJson.version,
263
+ version: packageId.version,
195
264
  };
196
265
 
197
266
  const software = new ArtifactSource(mode, fullName, fs.readFileSync(file).toString(), file, [], []);
@@ -203,7 +272,7 @@ function loadSoftwareFromDir(
203
272
 
204
273
  function loadAssetsFromDir(
205
274
  logger: winston.Logger,
206
- packageJson: PackageJson,
275
+ packageId: PackageId,
207
276
  mode: CompileMode,
208
277
  folder: string,
209
278
  compiler: TengoTemplateCompiler,
@@ -214,9 +283,9 @@ function loadAssetsFromDir(
214
283
  throw new Error(`unexpected file in 'asset' folder: ${file}`);
215
284
  const fullName: FullArtifactName = {
216
285
  type: 'asset',
217
- pkg: packageJson.name,
286
+ pkg: packageId.name,
218
287
  id: f.slice(0, f.length - compiledAssetSuffix.length),
219
- version: packageJson.version,
288
+ version: packageId.version,
220
289
  };
221
290
 
222
291
  const asset = new ArtifactSource(mode, fullName, fs.readFileSync(file).toString(), file, [], []);
@@ -228,7 +297,7 @@ function loadAssetsFromDir(
228
297
 
229
298
  export function parseSources(
230
299
  logger: winston.Logger,
231
- packageInfo: PackageJson,
300
+ packageId: PackageId,
232
301
  mode: CompileMode,
233
302
  root: string,
234
303
  subdir: string,
@@ -240,16 +309,16 @@ export function parseSources(
240
309
  const fullPath = path.join(root, inRootPath); // full path to item from CWD (or abs path, if <root> is abs path)
241
310
 
242
311
  if (pathType(fullPath) === 'dir') {
243
- const nested = parseSources(logger, packageInfo, mode, root, inRootPath);
312
+ const nested = parseSources(logger, packageId, mode, root, inRootPath);
244
313
  sources.push(...nested);
245
314
  continue;
246
315
  }
247
316
 
248
- const artifactName
249
- = f === 'index.lib.tengo' ? `${path.dirname(inRootPath)}.lib.tengo` : inRootPath;
317
+ const artifactName = f === 'index.lib.tengo' ? `${path.dirname(inRootPath)}.lib.tengo` : inRootPath;
250
318
 
251
- const fullName = fullNameFromFileName(packageInfo, artifactName.replaceAll(path.sep, '.'));
319
+ const fullName = fullNameFromFileName(packageId, artifactName.replaceAll(path.sep, '.'));
252
320
  if (!fullName) {
321
+ logger.info(`Skipping unknown file type: ${artifactName}`);
253
322
  continue; // skip unknown file types
254
323
  }
255
324
 
@@ -276,22 +345,22 @@ export function parseSources(
276
345
 
277
346
  export function newCompiler(
278
347
  logger: winston.Logger,
279
- packageInfo: PackageJson,
348
+ packageInfo: PackageInfo,
280
349
  mode: CompileMode,
281
350
  ): TengoTemplateCompiler {
282
351
  const compiler = new TengoTemplateCompiler(mode);
283
352
 
284
353
  // collect all data (templates, libs and software) from dependency tree
285
- loadDependencies(logger, compiler, packageInfo, findNodeModules());
354
+ loadDependencies(logger, compiler, packageInfo);
286
355
 
287
356
  return compiler;
288
357
  }
289
358
 
290
359
  function fullNameFromFileName(
291
- packageJson: PackageJson,
360
+ packageId: PackageId,
292
361
  artifactName: string,
293
362
  ): FullArtifactName | null {
294
- const pkgAndVersion = { pkg: packageJson.name, version: packageJson.version };
363
+ const pkgAndVersion = { pkg: packageId.name, version: packageId.version };
295
364
  if (artifactName.endsWith(srcLibSuffix)) {
296
365
  return {
297
366
  ...pkgAndVersion,
@@ -336,7 +405,7 @@ function fullNameFromFileName(
336
405
  }
337
406
 
338
407
  export function compile(logger: winston.Logger, mode: CompileMode): TemplatesAndLibs {
339
- const packageInfo = getPackageInfo();
408
+ const packageInfo = getPackageInfo(process.cwd(), logger);
340
409
  const compiler = newCompiler(logger, packageInfo, mode);
341
410
  const sources = parseSources(logger, packageInfo, mode, 'src', '');
342
411
 
@@ -72,6 +72,13 @@ export function typedArtifactNamesEquals(
72
72
  return name1.type == name2.type && name1.pkg == name2.pkg && name1.id == name2.id;
73
73
  }
74
74
 
75
+ export function fullNameEquals(
76
+ name1: FullArtifactName,
77
+ name2: FullArtifactName,
78
+ ): boolean {
79
+ return name1.type == name2.type && name1.pkg == name2.pkg && name1.id == name2.id && name1.version == name2.version;
80
+ }
81
+
75
82
  /** used to format artefact name while generating output files */
76
83
  export function artifactNameToString(name: ArtifactName): string {
77
84
  return `${name.pkg}:${name.id}`;
@@ -10,9 +10,13 @@ export function assertNever(x: never): never {
10
10
  export function createLogger(level: string = 'debug'): winston.Logger {
11
11
  return winston.createLogger({
12
12
  level: level,
13
- format: winston.format.printf(({ level, message }) => {
14
- return `${level.padStart(6, ' ')}: ${message as string}`;
15
- }),
13
+ format: winston.format.combine(
14
+ winston.format.errors({ stack: true }),
15
+ winston.format.printf(({ level, message, stack }) => {
16
+ const baseMessage = `${level.padStart(6, ' ')}: ${message as string}`;
17
+ return stack ? `${baseMessage}\n${stack as string}` : baseMessage;
18
+ }),
19
+ ),
16
20
  transports: [
17
21
  new winston.transports.Console({
18
22
  stderrLevels: ['error', 'warn', 'info', 'debug'],
@@ -51,6 +55,7 @@ export function pathType(path: string): PathType {
51
55
  } catch (error: unknown) {
52
56
  const err = error as NodeJS.ErrnoException;
53
57
  if (err.code == 'ENOENT') return 'absent';
58
+ if (err.code == 'ENOTDIR') return 'absent';
54
59
  else throw err;
55
60
  }
56
61
  }
@@ -7,7 +7,7 @@ export function dumpAll(
7
7
  logger: winston.Logger,
8
8
  stream: NodeJS.WritableStream,
9
9
  ): void {
10
- const packageInfo = getPackageInfo();
10
+ const packageInfo = getPackageInfo(process.cwd(), logger);
11
11
 
12
12
  const sources = parseSources(logger, packageInfo, 'dist', 'src', '');
13
13
 
@@ -113,7 +113,7 @@ export function dumpLibs(
113
113
  dumpDeps: boolean,
114
114
  stream: NodeJS.WritableStream,
115
115
  ): void {
116
- const packageInfo = getPackageInfo();
116
+ const packageInfo = getPackageInfo(process.cwd(), logger);
117
117
 
118
118
  const sources = parseSources(logger, packageInfo, 'dist', 'src', '');
119
119
 
@@ -144,7 +144,7 @@ function dumpArtifacts(
144
144
  stream: NodeJS.WritableStream,
145
145
  aType: ArtifactType,
146
146
  ): void {
147
- const packageInfo = getPackageInfo();
147
+ const packageInfo = getPackageInfo(process.cwd(), logger);
148
148
 
149
149
  const sources = parseSources(logger, packageInfo, 'dist', 'src', '');
150
150