@platforma-sdk/tengo-builder 1.16.0 → 1.17.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.
Files changed (40) hide show
  1. package/dist/commands/build.d.ts.map +1 -1
  2. package/dist/compiler/compiler.d.ts.map +1 -1
  3. package/dist/compiler/compileroptions.d.ts +6 -0
  4. package/dist/compiler/compileroptions.d.ts.map +1 -0
  5. package/dist/compiler/main.d.ts +0 -1
  6. package/dist/compiler/main.d.ts.map +1 -1
  7. package/dist/compiler/package.d.ts +6 -0
  8. package/dist/compiler/package.d.ts.map +1 -1
  9. package/dist/compiler/source.d.ts +9 -4
  10. package/dist/compiler/source.d.ts.map +1 -1
  11. package/dist/compiler/template.d.ts +5 -0
  12. package/dist/compiler/template.d.ts.map +1 -1
  13. package/dist/compiler/test.artifacts.d.ts +9 -0
  14. package/dist/compiler/test.artifacts.d.ts.map +1 -1
  15. package/dist/compiler/util.d.ts +3 -0
  16. package/dist/compiler/util.d.ts.map +1 -1
  17. package/dist/index.js +28 -28
  18. package/dist/index.js.map +1 -1
  19. package/dist/index.mjs +572 -513
  20. package/dist/index.mjs.map +1 -1
  21. package/package.json +6 -6
  22. package/src/commands/build.ts +2 -1
  23. package/src/commands/check.ts +1 -1
  24. package/src/commands/dump/all.ts +1 -1
  25. package/src/commands/dump/assets.ts +1 -1
  26. package/src/commands/dump/libs.ts +1 -1
  27. package/src/commands/dump/software.ts +1 -1
  28. package/src/commands/dump/templates.ts +1 -1
  29. package/src/commands/dump/tests.ts +1 -1
  30. package/src/commands/test.ts +1 -1
  31. package/src/compiler/compiler.test.ts +51 -20
  32. package/src/compiler/compiler.ts +10 -4
  33. package/src/compiler/compileroptions.ts +51 -0
  34. package/src/compiler/main.ts +4 -19
  35. package/src/compiler/package.ts +21 -13
  36. package/src/compiler/source.test.ts +18 -4
  37. package/src/compiler/source.ts +94 -20
  38. package/src/compiler/template.ts +6 -0
  39. package/src/compiler/test.artifacts.ts +66 -8
  40. package/src/compiler/util.ts +21 -0
@@ -1,5 +1,12 @@
1
1
  import { readFileSync } from 'node:fs';
2
- import { TypedArtifactName, FullArtifactName, ArtifactType, CompileMode } from './package';
2
+ import winston from 'winston';
3
+ import {
4
+ TypedArtifactName,
5
+ FullArtifactName,
6
+ ArtifactType,
7
+ CompileMode,
8
+ CompilerOption
9
+ } from './package';
3
10
  import { ArtifactMap, createArtifactNameSet } from './artifactset';
4
11
 
5
12
  // matches any valid name in tengo. Don't forget to use '\b' when needed to limit the boundaries!
@@ -30,6 +37,9 @@ const newImportAssetRE = (moduleName: string) => {
30
37
  return functionCallRE(moduleName, 'importAsset');
31
38
  };
32
39
 
40
+ const emptyLineRE = /^\s*$/;
41
+ const compilerOptionRE = /^\/\/tengo:/;
42
+ const wrongCompilerOptionRE = /^\s*\/\/\s+tengo:/;
33
43
  const singlelineCommentRE = /^\s*(\/\/)|(\/\*.*\*\/)/;
34
44
  const multilineCommentStartRE = /^\s*\/\*/;
35
45
  const multilineCommentEndRE = /\*\//;
@@ -39,6 +49,30 @@ const importNameRE = new RegExp(
39
49
  );
40
50
  const dependencyRE = /(?<pkgName>[^"]+)?:(?<depID>[^"]+)/; // use it to parse <moduleName> from importPattern or <templateName> акщь getTemplateID
41
51
 
52
+ /**
53
+ * Parse compiler option string representation
54
+ * Compiler option line is a comment starting with '//tengo:', say
55
+ * //tengo:hash_override tralala
56
+ *
57
+ * The common compiler option syntax is:
58
+ * //tengo:<option name> [<option arg1> [<option arg 2> [...]]]
59
+ */
60
+ const parseComplierOption = (opt: string): CompilerOption => {
61
+ const parts = opt.split(' ');
62
+ const namePart = parts[0].split(':');
63
+ if (namePart.length != 2) {
64
+ throw new Error(
65
+ "compiler option format is wrong: expect to have option name after 'tengo:' prefix, like 'tengo:MyOption'"
66
+ );
67
+ }
68
+ const optName = namePart[1];
69
+
70
+ return {
71
+ name: optName,
72
+ args: parts.slice(1)
73
+ };
74
+ };
75
+
42
76
  export class ArtifactSource {
43
77
  constructor(
44
78
  /** The mode this artifact was built (dev or dist) */
@@ -50,42 +84,49 @@ export class ArtifactSource {
50
84
  /** Path to source file where artifact came from */
51
85
  public readonly srcName: string,
52
86
  /** List of dependencies */
53
- public readonly dependencies: TypedArtifactName[]
87
+ public readonly dependencies: TypedArtifactName[],
88
+ /** Additional compiler options detected in source code */
89
+ public readonly compilerOptions: CompilerOption[]
54
90
  ) {}
55
91
  }
56
92
 
57
93
  export function parseSourceFile(
94
+ logger: winston.Logger,
58
95
  mode: CompileMode,
59
96
  srcFile: string,
60
97
  fullSourceName: FullArtifactName,
61
98
  normalize: boolean
62
99
  ): ArtifactSource {
63
100
  const src = readFileSync(srcFile).toString();
64
- const { deps, normalized } = parseSourceData(src, fullSourceName, normalize);
101
+ const { deps, normalized, opts } = parseSourceData(logger, src, fullSourceName, normalize);
65
102
 
66
- return new ArtifactSource(mode, fullSourceName, normalized, srcFile, deps.array);
103
+ return new ArtifactSource(mode, fullSourceName, normalized, srcFile, deps.array, opts);
67
104
  }
68
105
 
69
106
  export function parseSource(
107
+ logger: winston.Logger,
70
108
  mode: CompileMode,
71
109
  src: string,
72
110
  fullSourceName: FullArtifactName,
73
111
  normalize: boolean
74
112
  ): ArtifactSource {
75
- const { deps, normalized } = parseSourceData(src, fullSourceName, normalize);
113
+ const { deps, normalized, opts } = parseSourceData(logger, src, fullSourceName, normalize);
76
114
 
77
- return new ArtifactSource(mode, fullSourceName, normalized, '', deps.array);
115
+ return new ArtifactSource(mode, fullSourceName, normalized, '', deps.array, opts);
78
116
  }
79
117
 
80
118
  function parseSourceData(
119
+ logger: winston.Logger,
81
120
  src: string,
82
121
  fullSourceName: FullArtifactName,
83
122
  globalizeImports: boolean
84
123
  ): {
85
124
  normalized: string;
86
125
  deps: ArtifactMap<TypedArtifactName>;
126
+ opts: CompilerOption[];
87
127
  } {
88
128
  const dependencySet = createArtifactNameSet();
129
+ const optionList: CompilerOption[] = [];
89
130
 
90
131
  // iterating over lines
91
132
  const lines = src.split('\n');
@@ -97,15 +138,17 @@ function parseSourceData(
97
138
  const processedLines: string[] = [];
98
139
  let parserContext: sourceParserContext = {
99
140
  isInCommentBlock: false,
100
- tplDepREs: new Map<string, [ArtifactType, RegExp][]>()
141
+ canDetectOptions: true,
142
+ tplDepREs: new Map<string, [ArtifactType, RegExp][]>(),
143
+ lineNo: 0
101
144
  };
102
145
 
103
- let lineNo = 0;
104
146
  for (const line of lines) {
105
- lineNo++;
147
+ parserContext.lineNo++;
106
148
 
107
149
  try {
108
150
  const result = parseSingleSourceLine(
151
+ logger,
109
152
  line,
110
153
  parserContext,
111
154
  fullSourceName.pkg,
@@ -117,48 +160,79 @@ function parseSourceData(
117
160
  if (result.artifact) {
118
161
  dependencySet.add(result.artifact);
119
162
  }
163
+ if (result.option) {
164
+ optionList.push(result.option);
165
+ }
120
166
  } catch (error: any) {
121
- throw new Error(`[line ${lineNo}]: ${error.message}\n\t${line}`);
167
+ throw new Error(`[line ${parserContext.lineNo}]: ${error.message}\n\t${line}`);
122
168
  }
123
169
  }
124
170
 
125
171
  return {
126
172
  normalized: processedLines.join('\n'),
127
- deps: dependencySet
173
+ deps: dependencySet,
174
+ opts: optionList
128
175
  };
129
176
  }
130
177
 
131
178
  interface sourceParserContext {
132
179
  isInCommentBlock: boolean;
180
+ canDetectOptions: boolean;
133
181
  tplDepREs: Map<string, [ArtifactType, RegExp][]>;
182
+ lineNo: number;
134
183
  }
135
184
 
136
185
  function parseSingleSourceLine(
186
+ logger: winston.Logger,
137
187
  line: string,
138
188
  context: sourceParserContext,
139
189
  localPackageName: string,
140
- globalizeImports: boolean
190
+ globalizeImports?: boolean
141
191
  ): {
142
192
  line: string;
143
193
  context: sourceParserContext;
144
194
  artifact: TypedArtifactName | undefined;
195
+ option: CompilerOption | undefined;
145
196
  } {
146
197
  if (context.isInCommentBlock) {
147
198
  if (multilineCommentEndRE.exec(line)) {
148
199
  context.isInCommentBlock = false;
149
200
  }
150
- return { line, context, artifact: undefined };
201
+ return { line, context, artifact: undefined, option: undefined };
202
+ }
203
+
204
+ if (compilerOptionRE.exec(line)) {
205
+ if (!context.canDetectOptions) {
206
+ logger.warn(
207
+ `[line ${context.lineNo}]: compiler option '//tengo:' was detected, but it cannot be applied as compiler options can be set only at the file header, before any code line'`
208
+ );
209
+ return { line, context, artifact: undefined, option: undefined };
210
+ }
211
+ return { line, context, artifact: undefined, option: parseComplierOption(line) };
212
+ }
213
+
214
+ if (wrongCompilerOptionRE.exec(line) && context.canDetectOptions) {
215
+ logger.warn(
216
+ `[line ${context.lineNo}]: text simillar to compiler option ('//tengo:...') was detected, but it has wrong format. Leave it as is, if you did not mean to use a line as compiler option. Or format it to '//tengo:<option>' otherwise (no spaces between '//' and 'tengo', no spaces between ':' and option name)`
217
+ );
218
+ return { line, context, artifact: undefined, option: undefined };
151
219
  }
152
220
 
153
221
  if (singlelineCommentRE.exec(line)) {
154
- return { line, context, artifact: undefined };
222
+ return { line, context, artifact: undefined, option: undefined };
155
223
  }
156
224
 
157
225
  if (multilineCommentStartRE.exec(line)) {
158
226
  context.isInCommentBlock = true;
159
- return { line, context, artifact: undefined };
227
+ return { line, context, artifact: undefined, option: undefined };
228
+ }
229
+
230
+ if (emptyLineRE.exec(line)) {
231
+ return { line, context, artifact: undefined, option: undefined };
160
232
  }
161
233
 
234
+ context.canDetectOptions = false;
235
+
162
236
  const importInstruction = importRE.exec(line);
163
237
 
164
238
  if (importInstruction) {
@@ -171,7 +245,7 @@ function parseSingleSourceLine(
171
245
  ['software', newGetSoftwareInfoRE(iInfo.alias)]
172
246
  ]);
173
247
  }
174
- return { line, context, artifact: undefined };
248
+ return { line, context, artifact: undefined, option: undefined };
175
249
  }
176
250
 
177
251
  if (
@@ -208,14 +282,14 @@ function parseSingleSourceLine(
208
282
  const artifact = parseArtifactName(iInfo.module, 'library', localPackageName);
209
283
  if (!artifact) {
210
284
  // not a Platforma Tengo library import
211
- return { line, context, artifact: undefined };
285
+ return { line, context, artifact: undefined, option: undefined };
212
286
  }
213
287
 
214
288
  if (globalizeImports) {
215
289
  line = line.replace(importInstruction[0], ` := import("${artifact.pkg}:${artifact.id}")`);
216
290
  }
217
291
 
218
- return { line, context, artifact };
292
+ return { line, context, artifact, option: undefined };
219
293
  }
220
294
 
221
295
  if (context.tplDepREs.size > 0) {
@@ -241,12 +315,12 @@ function parseSingleSourceLine(
241
315
  line = line.replace(fnCall, `${fnName}("${artifact.pkg}:${artifact.id}")`);
242
316
  }
243
317
 
244
- return { line, context, artifact };
318
+ return { line, context, artifact, option: undefined };
245
319
  }
246
320
  }
247
321
  }
248
322
 
249
- return { line, context, artifact: undefined };
323
+ return { line, context, artifact: undefined, option: undefined };
250
324
  }
251
325
 
252
326
  interface ImportInfo {
@@ -44,6 +44,12 @@ export interface TemplateData {
44
44
  /** i.e. 1.2.3 */
45
45
  version: string;
46
46
 
47
+ /**
48
+ * Custom hash token of the template for deduplication purposes. Can be set with 'hash_override' compiler option.
49
+ * Dangerous! Remember: great power comes with great responsibility.
50
+ */
51
+ hashOverride?: string;
52
+
47
53
  /** i.e. @milaboratory/some-package:some-lib -> normalized library source code */
48
54
  libs: Record<string, TemplateLibData>;
49
55
  /** i.e. @milaboratory/some-package:some-lib -> to nested template data */
@@ -93,6 +93,18 @@ export {
93
93
  }
94
94
  `;
95
95
 
96
+ export const testLocalLib3Name: FullArtifactName = {
97
+ type: 'library',
98
+ pkg: 'current-package',
99
+ id: 'local-library-3',
100
+ version: '6.6.6'
101
+ };
102
+ export const testLocalLib3Src = `
103
+ export {
104
+ "some": "value"
105
+ }
106
+ `;
107
+
96
108
  export const testLocalTpl1Name: FullArtifactName = {
97
109
  type: 'template',
98
110
  pkg: 'current-package',
@@ -120,19 +132,50 @@ export const testLocalTpl2SrcNormalized = `
120
132
  lib := import("package1:other-lib-1")
121
133
  `;
122
134
 
135
+ export const testLocalTpl3Name: FullArtifactName = {
136
+ type: 'template',
137
+ pkg: 'current-package',
138
+ id: 'local-template-3',
139
+ version: '1.2.3'
140
+ };
141
+
142
+ export const testLocalTpl3Src = `
143
+ //tengo:hash_override CE0F6EDF-D97C-44E7-B16B-D661D4C799C1
144
+
145
+ a := "some instruction"
146
+ lib := import(":local-library-3")
147
+ `;
148
+
149
+ export const testLocalTpl3SrcWrongOverride = `
150
+ //tengo:hash_override broken-hash-override
151
+
152
+ a := "some instruction"
153
+ lib := import(":local-library-3")
154
+ `;
155
+
123
156
  export const testLocalLib1: TestArtifactSource = {
124
157
  fullName: testLocalLib1Name,
125
- src: testLocalLib1Src,
158
+ src: testLocalLib1Src
159
+ };
160
+
161
+ export const testLocalLib3: TestArtifactSource = {
162
+ fullName: testLocalLib3Name,
163
+ src: testLocalLib3Src
126
164
  };
127
165
 
128
166
  export const testLocalTpl1: TestArtifactSource = {
129
167
  fullName: testLocalTpl1Name,
130
- src: testLocalTpl1Src,
168
+ src: testLocalTpl1Src
131
169
  };
132
170
 
133
171
  export const testLocalTpl2: TestArtifactSource = {
134
172
  fullName: testLocalTpl2Name,
135
- src: testLocalTpl2Src,
173
+ src: testLocalTpl2Src
174
+ };
175
+
176
+ export const testLocalTpl3: TestArtifactSource = {
177
+ fullName: testLocalTpl3Name,
178
+ src: testLocalTpl3Src
136
179
  };
137
180
 
138
181
  export const testLocalPackage = [testLocalTpl1, testLocalLib1, testLocalTpl2];
@@ -183,21 +226,36 @@ export const testPackage1Tpl3Src = `
183
226
  lib := import("package1:other-lib-1")
184
227
  `;
185
228
 
186
- export const testPackage1Tpl3CompiledBase64 = 'H4sIAAAAAAAAE22PQQqDMBREr/KZVQsxELsL9CZ/E+VjQ2MiJpUWyd2LglCo2xlm3syK4LsMu2Jy/dMNYmwqD5mb4LvGbHp0o8Ce2wp57mHBUd5TmgutHImIGDmNwrDEWFx4iWFwrByhsMicfYqwMLrVN9Sq/hhFxim4Is3tBxF8R3fy4wa68OkgxnVnHPntWFUon2mvD7pIHFJz2HppzwZ9AanB7OAUAQAA';
229
+ export const testPackage1Tpl3CompiledBase64 =
230
+ 'H4sIAAAAAAAAE22PQQqDMBREr/KZVQsxELsL9CZ/E+VjQ2MiJpUWyd2LglCo2xlm3syK4LsMu2Jy/dMNYmwqD5mb4LvGbHp0o8Ce2wp57mHBUd5TmgutHImIGDmNwrDEWFx4iWFwrByhsMicfYqwMLrVN9Sq/hhFxim4Is3tBxF8R3fy4wa68OkgxnVnHPntWFUon2mvD7pIHFJz2HppzwZ9AanB7OAUAQAA';
187
231
 
188
232
  export const testPackage1Lib1: TestArtifactSource = {
189
233
  fullName: testPackage1Lib1Name,
190
- src: testPackage1Lib1Src,
234
+ src: testPackage1Lib1Src
191
235
  };
192
236
 
193
237
  export const testPackage1Lib2: TestArtifactSource = {
194
238
  fullName: testPackage1Lib2Name,
195
- src: testPackage1Lib2Src,
239
+ src: testPackage1Lib2Src
196
240
  };
197
241
 
198
242
  export const testPackage1Tpl3: TestArtifactSource = {
199
243
  fullName: testPackage1Tpl3Name,
200
- src: testPackage1Tpl3Src,
244
+ src: testPackage1Tpl3Src
201
245
  };
202
246
 
203
- export const testPackage1 = [testPackage1Lib1, testPackage1Lib2, testPackage1Tpl3];
247
+ export const testPackage1: TestArtifactSource[] = [
248
+ testPackage1Lib1,
249
+ testPackage1Lib2,
250
+ testPackage1Tpl3
251
+ ];
252
+
253
+ export const testPackage2: TestArtifactSource[] = [testLocalLib3, testLocalTpl3];
254
+
255
+ export const testPackage2BrokenHash: TestArtifactSource[] = [
256
+ testLocalLib3,
257
+ {
258
+ fullName: testLocalTpl3Name,
259
+ src: testLocalTpl3SrcWrongOverride
260
+ }
261
+ ];
@@ -1,10 +1,26 @@
1
1
  import * as fs from 'node:fs';
2
2
  import * as path from 'node:path';
3
+ import * as winston from 'winston';
3
4
 
4
5
  export function assertNever(x: never): never {
5
6
  throw new Error('Unexpected object: ' + x);
6
7
  }
7
8
 
9
+ export function createLogger(level: string = 'debug'): winston.Logger {
10
+ return winston.createLogger({
11
+ level: level,
12
+ format: winston.format.printf(({ level, message }) => {
13
+ return `${level.padStart(6, ' ')}: ${message}`;
14
+ }),
15
+ transports: [
16
+ new winston.transports.Console({
17
+ stderrLevels: ['error', 'warn', 'info', 'debug'],
18
+ handleExceptions: true
19
+ })
20
+ ]
21
+ });
22
+ }
23
+
8
24
  export function findNodeModules(): string {
9
25
  let currentDir = process.cwd();
10
26
 
@@ -36,3 +52,8 @@ export function pathType(path: string): PathType {
36
52
  else throw err;
37
53
  }
38
54
  }
55
+
56
+ export function isUUID(uuid: string): boolean {
57
+ const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
58
+ return uuidRegex.test(uuid.toLowerCase());
59
+ }