@zokugun/artifact 0.6.0 → 0.6.2

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 (36) hide show
  1. package/lib/configs/install/read-install-config.js +10 -12
  2. package/lib/configs/install/write-install-config.d.ts +1 -1
  3. package/lib/configs/install/write-install-config.js +7 -3
  4. package/lib/configs/package/read-package-config.js +53 -58
  5. package/lib/configs/utils/merge-upsert-property.d.ts +2 -2
  6. package/lib/configs/utils/merge-upsert-property.js +28 -18
  7. package/lib/configs/utils/normalize-file-always.d.ts +2 -2
  8. package/lib/configs/utils/normalize-file-always.js +2 -1
  9. package/lib/configs/utils/normalize-file-uninstall.d.ts +2 -2
  10. package/lib/configs/utils/normalize-file-uninstall.js +2 -1
  11. package/lib/configs/utils/normalize-file-upsert.d.ts +2 -2
  12. package/lib/configs/utils/normalize-file-upsert.js +2 -1
  13. package/lib/journeys/commitlint/index.js +4 -4
  14. package/lib/journeys/fixpack/index.js +4 -4
  15. package/lib/steps/apply-formatting.d.ts +1 -1
  16. package/lib/steps/apply-formatting.js +35 -14
  17. package/lib/steps/configure-install-file-actions.js +23 -13
  18. package/lib/steps/configure-uninstall-file-actions.js +11 -7
  19. package/lib/steps/configure-update-file-actions.js +18 -22
  20. package/lib/steps/insert-final-new-line.d.ts +1 -1
  21. package/lib/steps/merge-text-files.js +6 -1
  22. package/lib/steps/read-files.js +7 -2
  23. package/lib/steps/replace-templates.js +0 -2
  24. package/lib/steps/transform-untouched-files.js +4 -13
  25. package/lib/steps/unmerge-text-files.js +1 -0
  26. package/lib/types/config.d.ts +18 -18
  27. package/lib/types/context.d.ts +8 -2
  28. package/lib/types/context.js +4 -0
  29. package/lib/types/format.d.ts +4 -0
  30. package/lib/utils/detect-indent.d.ts +2 -5
  31. package/lib/utils/detect-indent.js +3 -0
  32. package/lib/utils/template.d.ts +2 -1
  33. package/lib/utils/template.js +18 -6
  34. package/package.json +124 -123
  35. package/lib/types/text-file.d.ts +0 -7
  36. package/lib/types/text-file.js +0 -2
@@ -10,31 +10,34 @@ async function configureInstallFileActions(context) {
10
10
  if (!install) {
11
11
  return xtry_1.OK;
12
12
  }
13
- const overwrites = [];
13
+ const existingActions = [];
14
14
  const filters = {};
15
15
  const routes = {};
16
16
  const transformations = {};
17
- for (const [file, fileUpdate] of Object.entries(install)) {
18
- const { filter, ifExists, rename, route, transforms } = fileUpdate;
17
+ for (const file of install) {
18
+ const { filter, ifExists, pattern, rename, route, transforms } = file;
19
19
  if (rename) {
20
20
  context.renamedPatterns.push({
21
- from: file,
21
+ from: pattern,
22
22
  to: rename,
23
23
  });
24
24
  continue;
25
25
  }
26
- if (ifExists === 'overwrite') {
27
- overwrites.push(file);
26
+ if (ifExists === 'force-merge') {
27
+ existingActions.push({ pattern, action: 'merge' });
28
+ }
29
+ else if (ifExists === 'overwrite') {
30
+ existingActions.push({ pattern, action: 'overwrite' });
28
31
  }
29
32
  else if (ifExists === 'remove') {
30
- context.removedPatterns.push(file);
33
+ context.removedPatterns.push(pattern);
31
34
  continue;
32
35
  }
33
36
  else if (ifExists === 'skip') {
34
37
  continue;
35
38
  }
36
39
  if (filter) {
37
- filters[file] = filter;
40
+ filters[pattern] = filter;
38
41
  }
39
42
  if (route) {
40
43
  const { alias } = route;
@@ -43,23 +46,30 @@ async function configureInstallFileActions(context) {
43
46
  return travel;
44
47
  }
45
48
  if (alias) {
46
- routes[file] = {
49
+ routes[pattern] = {
47
50
  alias,
48
51
  travel: travel.value,
49
52
  };
50
53
  }
51
54
  else {
52
- routes[file] = {
55
+ routes[pattern] = {
53
56
  travel: travel.value,
54
57
  };
55
58
  }
56
59
  }
57
60
  if (transforms) {
58
- transformations[file] = transforms;
61
+ transformations[pattern] = transforms;
59
62
  }
60
63
  }
61
- if (overwrites.length > 0) {
62
- context.onExisting = (file) => (0, micromatch_1.isMatch)(file, overwrites) ? 'overwrite' : 'merge';
64
+ if (existingActions.length > 0) {
65
+ context.onExisting = (file) => {
66
+ for (const { pattern, action } of existingActions) {
67
+ if ((0, micromatch_1.isMatch)(file, pattern)) {
68
+ return action;
69
+ }
70
+ }
71
+ return 'merge';
72
+ };
63
73
  }
64
74
  if ((0, is_it_type_1.isNonEmptyRecord)(filters)) {
65
75
  context.filters = (file) => {
@@ -9,6 +9,8 @@ const async_1 = __importDefault(require("@zokugun/fs-extra-plus/async"));
9
9
  const is_it_type_1 = require("@zokugun/is-it-type");
10
10
  const xtry_1 = require("@zokugun/xtry");
11
11
  const micromatch_1 = require("micromatch");
12
+ const detect_indent_js_1 = require("../utils/detect-indent.js");
13
+ const has_final_new_line_js_1 = require("../utils/has-final-new-line.js");
12
14
  async function configureUninstallFileActions(context) {
13
15
  const { uninstall } = context.incomingConfig;
14
16
  if (!uninstall) {
@@ -16,27 +18,29 @@ async function configureUninstallFileActions(context) {
16
18
  }
17
19
  const cwd = node_path_1.default.join(context.incomingPath, 'configs');
18
20
  const transformations = {};
19
- for (const [file, fileUpdate] of Object.entries(uninstall)) {
20
- const { ifExists, transforms } = fileUpdate;
21
+ for (const file of uninstall) {
22
+ const { ifExists, pattern, transforms } = file;
21
23
  if (ifExists === 'remove') {
22
- context.removedPatterns.push(file);
24
+ context.removedPatterns.push(pattern);
23
25
  }
24
26
  else if (ifExists === 'unmerge') {
25
- const filePath = node_path_1.default.join(cwd, file);
27
+ const filePath = node_path_1.default.join(cwd, pattern);
26
28
  const result = await async_1.default.readFile(filePath, 'utf8');
27
29
  if (result.fails) {
28
30
  return (0, xtry_1.err)((0, xtry_1.stringifyError)(result.error));
29
31
  }
30
32
  const data = result.value;
31
- const finalNewLine = data.endsWith('\n');
33
+ const finalNewLine = (0, has_final_new_line_js_1.hasFinalNewLine)(data);
34
+ const indent = (0, detect_indent_js_1.detectIndent)(data);
32
35
  context.textFiles.push({
33
- name: file,
36
+ name: pattern,
34
37
  data,
35
38
  finalNewLine,
39
+ indent,
36
40
  });
37
41
  }
38
42
  if (transforms) {
39
- transformations[file] = transforms;
43
+ transformations[pattern] = transforms;
40
44
  }
41
45
  }
42
46
  if ((0, is_it_type_1.isNonEmptyRecord)(transformations)) {
@@ -12,42 +12,38 @@ async function configureUpdateFileActions(context) {
12
12
  context.onMissing = () => 'skip';
13
13
  }
14
14
  else {
15
- const existingActions = {
16
- merge: [],
17
- overwrite: [],
18
- skip: [],
19
- };
15
+ const existingActions = [];
20
16
  const skipMissings = [];
21
17
  const filters = {};
22
18
  const routes = {};
23
19
  const transformations = {};
24
- for (const [file, fileUpdate] of Object.entries(update)) {
25
- const { filter, ifExists, ifMissing, rename, route, transforms } = fileUpdate;
20
+ for (const file of update) {
21
+ const { filter, ifExists, ifMissing, pattern, rename, route, transforms } = file;
26
22
  if (rename) {
27
23
  context.renamedPatterns.push({
28
- from: file,
24
+ from: pattern,
29
25
  to: rename,
30
26
  });
31
27
  continue;
32
28
  }
33
29
  if (ifMissing === 'skip') {
34
- skipMissings.push(file);
30
+ skipMissings.push(pattern);
35
31
  }
36
32
  if (ifExists === 'force-merge') {
37
- existingActions.merge.push(file);
38
- }
39
- else if (ifExists === 'skip') {
40
- existingActions.skip.push(file);
33
+ existingActions.push({ pattern, action: 'merge' });
41
34
  }
42
35
  else if (ifExists === 'overwrite') {
43
- existingActions.overwrite.push(file);
36
+ existingActions.push({ pattern, action: 'overwrite' });
44
37
  }
45
38
  else if (ifExists === 'remove') {
46
- context.removedPatterns.push(file);
39
+ context.removedPatterns.push(pattern);
47
40
  continue;
48
41
  }
42
+ else if (ifExists === 'skip') {
43
+ existingActions.push({ pattern, action: 'skip' });
44
+ }
49
45
  if (filter) {
50
- filters[file] = filter;
46
+ filters[pattern] = filter;
51
47
  }
52
48
  if (route) {
53
49
  const { alias } = route;
@@ -56,28 +52,28 @@ async function configureUpdateFileActions(context) {
56
52
  return travel;
57
53
  }
58
54
  if (alias) {
59
- routes[file] = {
55
+ routes[pattern] = {
60
56
  alias,
61
57
  travel: travel.value,
62
58
  };
63
59
  }
64
60
  else {
65
- routes[file] = {
61
+ routes[pattern] = {
66
62
  travel: travel.value,
67
63
  };
68
64
  }
69
65
  }
70
66
  if (transforms) {
71
- transformations[file] = transforms;
67
+ transformations[pattern] = transforms;
72
68
  }
73
69
  }
74
70
  if (skipMissings.length > 0) {
75
71
  context.onMissing = (file) => (0, micromatch_1.isMatch)(file, skipMissings) ? 'skip' : 'continue';
76
72
  }
77
- if (existingActions.overwrite.length > 0 || existingActions.skip.length > 0) {
73
+ if (existingActions.length > 0) {
78
74
  context.onExisting = (file) => {
79
- for (const [action, files] of Object.entries(existingActions)) {
80
- if ((0, micromatch_1.isMatch)(file, files)) {
75
+ for (const { pattern, action } of existingActions) {
76
+ if ((0, micromatch_1.isMatch)(file, pattern)) {
81
77
  return action;
82
78
  }
83
79
  }
@@ -1,5 +1,5 @@
1
1
  import { type AsyncDResult } from '@zokugun/xtry';
2
- import { type TextFile } from '../types/text-file.js';
2
+ import { type TextFile } from '../types/context.js';
3
3
  export declare function insertFinalNewLine({ mergedTextFiles, transformedFiles }: {
4
4
  mergedTextFiles: TextFile[];
5
5
  transformedFiles?: TextFile[];
@@ -9,6 +9,8 @@ const cli_utils_1 = require("@zokugun/cli-utils");
9
9
  const async_1 = __importDefault(require("@zokugun/fs-extra-plus/async"));
10
10
  const xtry_1 = require("@zokugun/xtry");
11
11
  const index_js_1 = require("../journeys/index.js");
12
+ const detect_indent_js_1 = require("../utils/detect-indent.js");
13
+ const has_final_new_line_js_1 = require("../utils/has-final-new-line.js");
12
14
  async function mergeTextFiles({ targetPath, textFiles, mergedTextFiles, onExisting, onMissing, filters, routes, transforms, options }) {
13
15
  for (const file of textFiles) {
14
16
  if (options.verbose) {
@@ -61,6 +63,8 @@ async function mergeTextFiles({ targetPath, textFiles, mergedTextFiles, onExisti
61
63
  if (current.fails) {
62
64
  return (0, xtry_1.err)((0, xtry_1.stringifyError)(current.error));
63
65
  }
66
+ const finalNewLine = (0, has_final_new_line_js_1.hasFinalNewLine)(current.value);
67
+ const indent = (0, detect_indent_js_1.detectIndent)(current.value);
64
68
  const data = await journey.travel({
65
69
  current: current.value,
66
70
  incoming: file.data,
@@ -70,7 +74,8 @@ async function mergeTextFiles({ targetPath, textFiles, mergedTextFiles, onExisti
70
74
  mergedTextFiles.push({
71
75
  name: fileName,
72
76
  data,
73
- finalNewLine: file.finalNewLine,
77
+ finalNewLine,
78
+ indent,
74
79
  mode: file.mode,
75
80
  });
76
81
  if (options.verbose) {
@@ -10,6 +10,8 @@ const async_1 = __importDefault(require("@zokugun/fs-extra-plus/async"));
10
10
  const xtry_1 = require("@zokugun/xtry");
11
11
  const globby_1 = __importDefault(require("globby"));
12
12
  const istextorbinary_1 = require("istextorbinary");
13
+ const detect_indent_js_1 = require("../utils/detect-indent.js");
14
+ const has_final_new_line_js_1 = require("../utils/has-final-new-line.js");
13
15
  const read_buffer_js_1 = require("../utils/read-buffer.js");
14
16
  async function readFiles({ incomingPath, textFiles, binaryFiles, options }) {
15
17
  const cwd = path_1.default.join(incomingPath, 'configs');
@@ -26,7 +28,8 @@ async function readFiles({ incomingPath, textFiles, binaryFiles, options }) {
26
28
  return (0, xtry_1.err)((0, xtry_1.stringifyError)(result.error));
27
29
  }
28
30
  const data = result.value;
29
- const finalNewLine = data.endsWith('\n');
31
+ const finalNewLine = (0, has_final_new_line_js_1.hasFinalNewLine)(data);
32
+ const indent = (0, detect_indent_js_1.detectIndent)(data);
30
33
  if (data.startsWith('#!')) {
31
34
  // the text file might be executable
32
35
  const result = await async_1.default.stat(filePath);
@@ -37,8 +40,9 @@ async function readFiles({ incomingPath, textFiles, binaryFiles, options }) {
37
40
  textFiles.push({
38
41
  name: file,
39
42
  data,
40
- mode,
41
43
  finalNewLine,
44
+ indent,
45
+ mode,
42
46
  });
43
47
  if (options.verbose) {
44
48
  cli_utils_1.logger.debug(`${file} is a shebang file`);
@@ -49,6 +53,7 @@ async function readFiles({ incomingPath, textFiles, binaryFiles, options }) {
49
53
  name: file,
50
54
  data,
51
55
  finalNewLine,
56
+ indent,
52
57
  });
53
58
  if (options.verbose) {
54
59
  cli_utils_1.logger.debug(`${file} is a text file`);
@@ -7,8 +7,6 @@ async function replaceTemplates({ textFiles, binaryFiles, targetPath, config, in
7
7
  const variables = {
8
8
  ...incomingConfig?.variables,
9
9
  ...config?.variables,
10
- ...incomingConfig?.constants,
11
- ...config?.constants,
12
10
  };
13
11
  const engine = new template_js_1.TemplateEngine(targetPath, variables);
14
12
  for (const file of textFiles) {
@@ -12,7 +12,6 @@ const xtry_1 = require("@zokugun/xtry");
12
12
  const globby_1 = __importDefault(require("globby"));
13
13
  const index_js_1 = require("../journeys/index.js");
14
14
  const detect_indent_js_1 = require("../utils/detect-indent.js");
15
- const get_format_js_1 = require("../utils/get-format.js");
16
15
  const has_final_new_line_js_1 = require("../utils/has-final-new-line.js");
17
16
  async function transformUntouchedFiles({ formats, options, routes, targetPath, textFiles, transformedFiles, transforms }) {
18
17
  if (transforms.length === 0) {
@@ -44,7 +43,8 @@ async function transformUntouchedFiles({ formats, options, routes, targetPath, t
44
43
  return (0, xtry_1.err)((0, xtry_1.stringifyError)(result.error));
45
44
  }
46
45
  const data = result.value;
47
- const finalNewLine = data.endsWith('\n');
46
+ const finalNewLine = (0, has_final_new_line_js_1.hasFinalNewLine)(data);
47
+ const indent = (0, detect_indent_js_1.detectIndent)(data);
48
48
  const transformed = await journey.travel({
49
49
  current: data,
50
50
  incoming: undefined,
@@ -53,21 +53,12 @@ async function transformUntouchedFiles({ formats, options, routes, targetPath, t
53
53
  transformedFiles.push({
54
54
  name: file,
55
55
  data: transformed,
56
- finalNewLine: finalNewLine,
56
+ finalNewLine,
57
+ indent,
57
58
  });
58
59
  if (options.verbose) {
59
60
  cli_utils_1.logger.debug(`${file} has been transformed`);
60
61
  }
61
- const format = (0, get_format_js_1.getFormat)(file, formats);
62
- if (!format) {
63
- const indent = (0, detect_indent_js_1.detectIndent)(data);
64
- formats.push({
65
- glob: file,
66
- indentStyle: indent.style,
67
- indentSize: indent.size,
68
- insertFinalNewline: (0, has_final_new_line_js_1.hasFinalNewLine)(data),
69
- });
70
- }
71
62
  }
72
63
  return xtry_1.OK;
73
64
  }
@@ -47,6 +47,7 @@ async function unmergeTextFiles({ targetPath, textFiles, mergedTextFiles, option
47
47
  name: file.name,
48
48
  data,
49
49
  finalNewLine: file.finalNewLine,
50
+ indent: file.indent,
50
51
  mode: file.mode,
51
52
  });
52
53
  if (options.verbose) {
@@ -1,3 +1,5 @@
1
+ import { type Primitive } from '@zokugun/is-it-type';
2
+ import { type Indent } from './format.js';
1
3
  export type Request = {
2
4
  name: string;
3
5
  variant?: string;
@@ -7,13 +9,12 @@ export type PackageManifest = {
7
9
  version: string;
8
10
  };
9
11
  export type PackageConfig = {
10
- constants: Record<string, string>;
11
12
  extends?: string;
12
- install: Record<string, FileInstall>;
13
+ install: InstallFileConfig[];
13
14
  orphan: boolean;
14
- uninstall: Record<string, FileUninstall>;
15
- update: false | Record<string, FileUpdate>;
16
- variables: Record<string, string>;
15
+ uninstall: UninstallFileConfig[];
16
+ update: false | UpdateFileConfig[];
17
+ variables: Record<string, Primitive>;
17
18
  variants: Record<string, string>;
18
19
  };
19
20
  export type Artifact = {
@@ -26,27 +27,25 @@ export type ArtifactResult = Artifact & {
26
27
  };
27
28
  export type InstallConfig = {
28
29
  artifacts: Record<string, Artifact>;
29
- constants: Record<string, string>;
30
- install: Record<string, FileInstall>;
31
- update: false | Record<string, FileUpdate>;
32
- variables: Record<string, string>;
30
+ install: Record<string, InstallFileConfig>;
31
+ update: false | Record<string, UpdateFileConfig>;
32
+ variables: Record<string, Primitive>;
33
33
  };
34
- export type FileAlways = {
35
- ifExists: 'force-merge' | 'merge' | 'overwrite' | 'remove' | 'skip';
34
+ export type FileConfig<E> = {
35
+ ifExists: E;
36
+ pattern: string;
36
37
  transforms: FileTransform[];
37
38
  };
38
- export type FileUpsert = FileAlways & {
39
+ export type AlwaysFileConfig = FileConfig<'force-merge' | 'merge' | 'overwrite' | 'remove' | 'skip'>;
40
+ export type InstallFileConfig = UpsertFileConfig;
41
+ export type UninstallFileConfig = FileConfig<'remove' | 'skip' | 'unmerge'>;
42
+ export type UpdateFileConfig = UpsertFileConfig;
43
+ export type UpsertFileConfig = AlwaysFileConfig & {
39
44
  filter?: string[];
40
45
  ifMissing: 'merge' | 'skip';
41
46
  rename?: string;
42
47
  route?: Record<string, any>;
43
48
  };
44
- export type FileInstall = FileUpsert & {};
45
- export type FileUninstall = {
46
- ifExists: 'remove' | 'skip' | 'unmerge';
47
- transforms: FileTransform[];
48
- };
49
- export type FileUpdate = FileUpsert & {};
50
49
  export type FileTransform = {
51
50
  description?: string;
52
51
  jq: string;
@@ -55,4 +54,5 @@ export type InstallConfigStats = {
55
54
  name: string;
56
55
  type: string;
57
56
  finalNewLine: boolean;
57
+ indent?: Indent;
58
58
  };
@@ -1,8 +1,7 @@
1
1
  import { type AsyncDResult } from '@zokugun/xtry';
2
2
  import { type BinaryFile } from './binary-file.js';
3
3
  import { type Request, type InstallConfig, type PackageConfig, type ArtifactResult, type PackageManifest, type FileTransform } from './config.js';
4
- import { type Format } from './format.js';
5
- import { type TextFile } from './text-file.js';
4
+ import { type Indent, type Format } from './format.js';
6
5
  import { type Journey } from './travel.js';
7
6
  export type ExistingAction = 'merge' | 'overwrite' | 'skip';
8
7
  export type MissingAction = 'continue' | 'skip';
@@ -54,3 +53,10 @@ export type Block = {
54
53
  branch?: string;
55
54
  incomingPath: string;
56
55
  };
56
+ export type TextFile = {
57
+ data: string;
58
+ finalNewLine: boolean;
59
+ indent?: Indent;
60
+ mode?: number;
61
+ name: string;
62
+ };
@@ -1,2 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ // export type OutputTextFile = {
4
+ // data: string;
5
+ // name: string;
6
+ // };
@@ -8,3 +8,7 @@ export type Format = {
8
8
  indentSize: number;
9
9
  insertFinalNewline: boolean;
10
10
  };
11
+ export type Indent = {
12
+ style: IndentStyle;
13
+ size: number;
14
+ };
@@ -1,5 +1,2 @@
1
- import { IndentStyle } from '../types/format.js';
2
- export declare function detectIndent(text: string): {
3
- style: IndentStyle;
4
- size: number;
5
- };
1
+ import { type Indent } from '../types/format.js';
2
+ export declare function detectIndent(text: string): Indent | undefined;
@@ -27,6 +27,9 @@ function detectIndent(text) {
27
27
  size: 1,
28
28
  };
29
29
  }
30
+ if (indentations.size === 0) {
31
+ return undefined;
32
+ }
30
33
  const sorted = [...indentations].sort((a, b) => a - b);
31
34
  const diffs = [];
32
35
  for (let i = 1; i < sorted.length; i++) {
@@ -1,10 +1,11 @@
1
+ import { type Primitive } from '@zokugun/is-it-type';
1
2
  import { type DResult } from '@zokugun/xtry';
2
3
  export declare class TemplateEngine {
3
4
  private readonly basePath;
4
5
  private readonly fileCache;
5
6
  private readonly variables;
6
7
  private readonly variableCache;
7
- constructor(basePath: string, variables?: Record<string, string>);
8
+ constructor(basePath: string, variables?: Record<string, Primitive>);
8
9
  render(template: string): DResult<string>;
9
10
  private getValueByPath;
10
11
  private parseFile;
@@ -40,6 +40,7 @@ exports.TemplateEngine = void 0;
40
40
  exports.unescapeCode = unescapeCode;
41
41
  const path_1 = __importDefault(require("path"));
42
42
  const sync_1 = __importDefault(require("@zokugun/fs-extra-plus/sync"));
43
+ const is_it_type_1 = require("@zokugun/is-it-type");
43
44
  const xtry_1 = require("@zokugun/xtry");
44
45
  const dayjs_1 = __importDefault(require("dayjs"));
45
46
  const utc_1 = __importDefault(require("dayjs/plugin/utc"));
@@ -164,16 +165,27 @@ class TemplateEngine {
164
165
  if (this.variableCache.has(name)) {
165
166
  return (0, xtry_1.ok)(this.variableCache.get(name));
166
167
  }
167
- const expression = this.variables[name];
168
- if (typeof expression !== 'string' || expression.trim().length === 0) {
168
+ const value = this.variables[name];
169
+ if (!(0, is_it_type_1.isPrimitive)(value)) {
169
170
  return (0, xtry_1.err)(`Invalid variable: ${name}.`);
170
171
  }
171
- const result = this.resolveExpression(expression);
172
- if (result.fails) {
172
+ const expression = String(value).trim();
173
+ if (expression.length === 0) {
174
+ return (0, xtry_1.err)(`Invalid variable: ${name}.`);
175
+ }
176
+ if (expression.at(0) === '=') {
177
+ const result = this.resolveExpression(expression.slice(1));
178
+ if (result.fails) {
179
+ return result;
180
+ }
181
+ this.variableCache.set(name, result.value);
173
182
  return result;
174
183
  }
175
- this.variableCache.set(name, result.value);
176
- return result;
184
+ else {
185
+ const value = expression.at(0) === '\\' && expression.at(1) === '=' ? expression.slice(1) : expression;
186
+ this.variableCache.set(name, value);
187
+ return (0, xtry_1.ok)(value);
188
+ }
177
189
  } // }}}
178
190
  splitPlaceholder(placeholder) {
179
191
  const matches = PLACEHOLDER_REGEX.exec(placeholder);