codify-plugin-lib 1.0.153 → 1.0.155
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.
- package/dist/plan/plan.js +1 -1
- package/dist/resource/resource-controller.js +14 -4
- package/dist/resource/resource-settings.js +5 -5
- package/dist/resource/resource.d.ts +8 -1
- package/dist/utils/utils.d.ts +2 -0
- package/dist/utils/utils.js +15 -0
- package/package.json +1 -1
- package/src/plan/plan.ts +1 -1
- package/src/resource/resource-controller.test.ts +47 -0
- package/src/resource/resource-controller.ts +16 -5
- package/src/resource/resource-settings.ts +5 -5
- package/src/resource/resource.ts +9 -1
- package/src/utils/utils.test.ts +23 -1
- package/src/utils/utils.ts +19 -0
package/dist/plan/plan.js
CHANGED
|
@@ -159,7 +159,7 @@ export class Plan {
|
|
|
159
159
|
const { matcher: parameterMatcher, id } = settings;
|
|
160
160
|
const matcher = (desired, currentArray) => {
|
|
161
161
|
const matched = currentArray.filter((c) => parameterMatcher(desired, c));
|
|
162
|
-
if (matched.length >
|
|
162
|
+
if (matched.length > 1) {
|
|
163
163
|
console.log(`Resource: ${id} did not uniquely match resources when allow multiple is set to true`);
|
|
164
164
|
}
|
|
165
165
|
return matched[0];
|
|
@@ -104,6 +104,11 @@ export class ResourceController {
|
|
|
104
104
|
}
|
|
105
105
|
async plan(core, desired, state, isStateful = false) {
|
|
106
106
|
this.validatePlanInputs(core, desired, state, isStateful);
|
|
107
|
+
const context = {
|
|
108
|
+
commandType: 'plan',
|
|
109
|
+
isStateful,
|
|
110
|
+
originalDesiredConfig: structuredClone(desired),
|
|
111
|
+
};
|
|
107
112
|
this.addDefaultValues(desired);
|
|
108
113
|
await this.applyTransformParameters(desired);
|
|
109
114
|
this.addDefaultValues(state);
|
|
@@ -112,7 +117,7 @@ export class ResourceController {
|
|
|
112
117
|
const parsedConfig = new ConfigParser(desired, state, this.parsedSettings.statefulParameters);
|
|
113
118
|
const { allParameters, allNonStatefulParameters, allStatefulParameters, } = parsedConfig;
|
|
114
119
|
// Refresh resource parameters. This refreshes the parameters that configure the resource itself
|
|
115
|
-
const currentArray = await this.refreshNonStatefulParameters(allNonStatefulParameters);
|
|
120
|
+
const currentArray = await this.refreshNonStatefulParameters(allNonStatefulParameters, context);
|
|
116
121
|
// Short circuit here. If the resource is non-existent, there's no point checking stateful parameters
|
|
117
122
|
if (currentArray === null
|
|
118
123
|
|| currentArray === undefined
|
|
@@ -180,6 +185,11 @@ export class ResourceController {
|
|
|
180
185
|
if (this.settings.importAndDestroy?.preventImport) {
|
|
181
186
|
throw new Error(`Type: ${this.typeId} cannot be imported`);
|
|
182
187
|
}
|
|
188
|
+
const context = {
|
|
189
|
+
commandType: 'import',
|
|
190
|
+
isStateful: true,
|
|
191
|
+
originalDesiredConfig: structuredClone(parameters),
|
|
192
|
+
};
|
|
183
193
|
this.addDefaultValues(parameters);
|
|
184
194
|
await this.applyTransformParameters(parameters);
|
|
185
195
|
// Use refresh parameters if specified, otherwise try to refresh as many parameters as possible here
|
|
@@ -197,7 +207,7 @@ export class ResourceController {
|
|
|
197
207
|
// Parse data from the user supplied config
|
|
198
208
|
const parsedConfig = new ConfigParser(parametersToRefresh, null, this.parsedSettings.statefulParameters);
|
|
199
209
|
const { allParameters, allNonStatefulParameters, allStatefulParameters, } = parsedConfig;
|
|
200
|
-
const currentParametersArray = await this.refreshNonStatefulParameters(allNonStatefulParameters);
|
|
210
|
+
const currentParametersArray = await this.refreshNonStatefulParameters(allNonStatefulParameters, context);
|
|
201
211
|
if (currentParametersArray === null
|
|
202
212
|
|| currentParametersArray === undefined
|
|
203
213
|
|| currentParametersArray.filter(Boolean).length === 0) {
|
|
@@ -312,8 +322,8 @@ ${JSON.stringify(refresh, null, 2)}
|
|
|
312
322
|
}
|
|
313
323
|
}
|
|
314
324
|
}
|
|
315
|
-
async refreshNonStatefulParameters(resourceParameters) {
|
|
316
|
-
const result = await this.resource.refresh(resourceParameters);
|
|
325
|
+
async refreshNonStatefulParameters(resourceParameters, context) {
|
|
326
|
+
const result = await this.resource.refresh(resourceParameters, context);
|
|
317
327
|
const currentParametersArray = Array.isArray(result) || result === null
|
|
318
328
|
? result
|
|
319
329
|
: [result];
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import isObjectsEqual from 'lodash.isequal';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import { areArraysEqual,
|
|
3
|
+
import { addVariablesToPath, areArraysEqual, resolvePathWithVariables, tildify, untildify } from '../utils/utils.js';
|
|
4
4
|
const ParameterEqualsDefaults = {
|
|
5
5
|
'boolean': (a, b) => Boolean(a) === Boolean(b),
|
|
6
6
|
'directory': (a, b) => {
|
|
7
7
|
const notCaseSensitive = process.platform === 'darwin';
|
|
8
|
-
const transformedA = path.resolve(
|
|
9
|
-
const transformedB = path.resolve(
|
|
8
|
+
const transformedA = path.resolve(resolvePathWithVariables(untildify(notCaseSensitive ? String(a).toLowerCase() : String(a))));
|
|
9
|
+
const transformedB = path.resolve(resolvePathWithVariables(untildify(notCaseSensitive ? String(b).toLowerCase() : String(b))));
|
|
10
10
|
return transformedA === transformedB;
|
|
11
11
|
},
|
|
12
12
|
'number': (a, b) => Number(a) === Number(b),
|
|
@@ -53,8 +53,8 @@ export function resolveFnFromEqualsFnOrString(fnOrString) {
|
|
|
53
53
|
}
|
|
54
54
|
const ParameterTransformationDefaults = {
|
|
55
55
|
'directory': {
|
|
56
|
-
to: (a) => path.resolve(
|
|
57
|
-
from: (a) => tildify(String(a)),
|
|
56
|
+
to: (a) => path.resolve(resolvePathWithVariables((untildify(String(a))))),
|
|
57
|
+
from: (a) => addVariablesToPath(tildify(String(a))),
|
|
58
58
|
},
|
|
59
59
|
'string': {
|
|
60
60
|
to: String,
|
|
@@ -2,6 +2,11 @@ import { StringIndexedObject } from 'codify-schemas';
|
|
|
2
2
|
import { ParameterChange } from '../plan/change-set.js';
|
|
3
3
|
import { CreatePlan, DestroyPlan, ModifyPlan } from '../plan/plan-types.js';
|
|
4
4
|
import { ResourceSettings } from './resource-settings.js';
|
|
5
|
+
export interface RefreshContext<T extends StringIndexedObject> {
|
|
6
|
+
isStateful: boolean;
|
|
7
|
+
commandType: 'destroy' | 'import' | 'plan';
|
|
8
|
+
originalDesiredConfig: Partial<T> | null;
|
|
9
|
+
}
|
|
5
10
|
/**
|
|
6
11
|
* A resource represents an object on the system (application, CLI tool, or setting)
|
|
7
12
|
* that has state and can be created and destroyed. Examples of resources include CLI tools
|
|
@@ -65,10 +70,12 @@ export declare abstract class Resource<T extends StringIndexedObject> {
|
|
|
65
70
|
* of the desired config. In stateful mode, this will be parameters of the state config + the desired
|
|
66
71
|
* config of any new parameters.
|
|
67
72
|
*
|
|
73
|
+
* @param context Context surrounding the request
|
|
74
|
+
*
|
|
68
75
|
* @return A config or an array of configs representing the status of the resource on the
|
|
69
76
|
* system currently
|
|
70
77
|
*/
|
|
71
|
-
abstract refresh(parameters: Partial<T>): Promise<Array<Partial<T>> | Partial<T> | null>;
|
|
78
|
+
abstract refresh(parameters: Partial<T>, context: RefreshContext<T>): Promise<Array<Partial<T>> | Partial<T> | null>;
|
|
72
79
|
/**
|
|
73
80
|
* Create the resource (install) based on the parameters passed in. Only the desired parameters will
|
|
74
81
|
* be non-null because in a CREATE plan, the current value is null.
|
package/dist/utils/utils.d.ts
CHANGED
|
@@ -7,5 +7,7 @@ export declare function splitUserConfig<T extends StringIndexedObject>(config: R
|
|
|
7
7
|
export declare function setsEqual(set1: Set<unknown>, set2: Set<unknown>): boolean;
|
|
8
8
|
export declare function untildify(pathWithTilde: string): string;
|
|
9
9
|
export declare function tildify(pathWithTilde: string): string;
|
|
10
|
+
export declare function resolvePathWithVariables(pathWithVariables: string): string;
|
|
11
|
+
export declare function addVariablesToPath(pathWithoutVariables: string): string;
|
|
10
12
|
export declare function unhome(pathWithHome: string): string;
|
|
11
13
|
export declare function areArraysEqual(isElementEqual: ((desired: unknown, current: unknown) => boolean) | undefined, desired: unknown, current: unknown): boolean;
|
package/dist/utils/utils.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import os from 'node:os';
|
|
2
|
+
import path from 'node:path';
|
|
2
3
|
export function isDebug() {
|
|
3
4
|
return process.env.DEBUG != null && process.env.DEBUG.includes('codify'); // TODO: replace with debug library
|
|
4
5
|
}
|
|
@@ -25,6 +26,20 @@ export function untildify(pathWithTilde) {
|
|
|
25
26
|
export function tildify(pathWithTilde) {
|
|
26
27
|
return homeDirectory ? pathWithTilde.replace(homeDirectory, '~') : pathWithTilde;
|
|
27
28
|
}
|
|
29
|
+
export function resolvePathWithVariables(pathWithVariables) {
|
|
30
|
+
// @ts-expect-error Ignore this for now
|
|
31
|
+
return pathWithVariables.replace(/\$([A-Z_]+[A-Z0-9_]*)|\${([A-Z0-9_]*)}/ig, (_, a, b) => process.env[a || b]);
|
|
32
|
+
}
|
|
33
|
+
export function addVariablesToPath(pathWithoutVariables) {
|
|
34
|
+
let result = pathWithoutVariables;
|
|
35
|
+
for (const [key, value] of Object.entries(process.env)) {
|
|
36
|
+
if (!value || !path.isAbsolute(value) || value === '/') {
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
result = result.replaceAll(value, `$${key}`);
|
|
40
|
+
}
|
|
41
|
+
return result;
|
|
42
|
+
}
|
|
28
43
|
export function unhome(pathWithHome) {
|
|
29
44
|
return pathWithHome.includes('$HOME') ? pathWithHome.replaceAll('$HOME', os.homedir()) : pathWithHome;
|
|
30
45
|
}
|
package/package.json
CHANGED
package/src/plan/plan.ts
CHANGED
|
@@ -263,7 +263,7 @@ export class Plan<T extends StringIndexedObject> {
|
|
|
263
263
|
const { matcher: parameterMatcher, id } = settings;
|
|
264
264
|
const matcher = (desired: Partial<T>, currentArray: Partial<T>[]): Partial<T> | undefined => {
|
|
265
265
|
const matched = currentArray.filter((c) => parameterMatcher(desired, c))
|
|
266
|
-
if (matched.length >
|
|
266
|
+
if (matched.length > 1) {
|
|
267
267
|
console.log(`Resource: ${id} did not uniquely match resources when allow multiple is set to true`)
|
|
268
268
|
}
|
|
269
269
|
|
|
@@ -746,4 +746,51 @@ describe('Resource tests', () => {
|
|
|
746
746
|
}
|
|
747
747
|
})
|
|
748
748
|
})
|
|
749
|
+
|
|
750
|
+
it('Can plan with settings', async () => {
|
|
751
|
+
const resource = new class extends TestResource {
|
|
752
|
+
getSettings(): ResourceSettings<any> {
|
|
753
|
+
return {
|
|
754
|
+
id: 'path',
|
|
755
|
+
parameterSettings: {
|
|
756
|
+
path: { type: 'string', isEqual: 'directory' },
|
|
757
|
+
paths: { canModify: true, type: 'array', isElementEqual: 'directory' },
|
|
758
|
+
prepend: { default: false, setting: true },
|
|
759
|
+
declarationsOnly: { default: false, setting: true },
|
|
760
|
+
},
|
|
761
|
+
importAndDestroy: {
|
|
762
|
+
refreshKeys: ['paths', 'declarationsOnly'],
|
|
763
|
+
defaultRefreshValues: {
|
|
764
|
+
paths: [],
|
|
765
|
+
declarationsOnly: true,
|
|
766
|
+
}
|
|
767
|
+
},
|
|
768
|
+
allowMultiple: {
|
|
769
|
+
matcher: (desired, current) => {
|
|
770
|
+
// console.log('Matcher');
|
|
771
|
+
// console.log(desired);
|
|
772
|
+
// console.log(current);
|
|
773
|
+
|
|
774
|
+
if (desired.path) {
|
|
775
|
+
return desired.path === current.path;
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
const currentPaths = new Set(current.paths)
|
|
779
|
+
return desired.paths?.some((p) => currentPaths.has(p));
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
async refresh(parameters: Partial<TestConfig>): Promise<Partial<TestConfig> | null> {
|
|
786
|
+
return { path: '$HOME/.bun/bin', prepend: false, declarationsOnly: false }
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
const controller = new ResourceController(resource);
|
|
791
|
+
const plan = await controller.plan({ type: 'path' }, { path: '$HOME/.bun/bin' }, null, false);
|
|
792
|
+
|
|
793
|
+
expect(plan.requiresChanges()).to.be.false;
|
|
794
|
+
console.log(JSON.stringify(plan, null, 2));
|
|
795
|
+
})
|
|
749
796
|
});
|
|
@@ -13,7 +13,7 @@ import { Plan } from '../plan/plan.js';
|
|
|
13
13
|
import { CreatePlan, DestroyPlan, ModifyPlan } from '../plan/plan-types.js';
|
|
14
14
|
import { ConfigParser } from './config-parser.js';
|
|
15
15
|
import { ParsedResourceSettings } from './parsed-resource-settings.js';
|
|
16
|
-
import { Resource } from './resource.js';
|
|
16
|
+
import { RefreshContext, Resource } from './resource.js';
|
|
17
17
|
import { ResourceSettings } from './resource-settings.js';
|
|
18
18
|
|
|
19
19
|
export class ResourceController<T extends StringIndexedObject> {
|
|
@@ -151,6 +151,11 @@ export class ResourceController<T extends StringIndexedObject> {
|
|
|
151
151
|
isStateful = false,
|
|
152
152
|
): Promise<Plan<T>> {
|
|
153
153
|
this.validatePlanInputs(core, desired, state, isStateful);
|
|
154
|
+
const context: RefreshContext<T> = {
|
|
155
|
+
commandType: 'plan',
|
|
156
|
+
isStateful,
|
|
157
|
+
originalDesiredConfig: structuredClone(desired),
|
|
158
|
+
};
|
|
154
159
|
|
|
155
160
|
this.addDefaultValues(desired);
|
|
156
161
|
await this.applyTransformParameters(desired);
|
|
@@ -167,7 +172,7 @@ export class ResourceController<T extends StringIndexedObject> {
|
|
|
167
172
|
} = parsedConfig;
|
|
168
173
|
|
|
169
174
|
// Refresh resource parameters. This refreshes the parameters that configure the resource itself
|
|
170
|
-
const currentArray = await this.refreshNonStatefulParameters(allNonStatefulParameters);
|
|
175
|
+
const currentArray = await this.refreshNonStatefulParameters(allNonStatefulParameters, context);
|
|
171
176
|
|
|
172
177
|
// Short circuit here. If the resource is non-existent, there's no point checking stateful parameters
|
|
173
178
|
if (currentArray === null
|
|
@@ -259,6 +264,12 @@ export class ResourceController<T extends StringIndexedObject> {
|
|
|
259
264
|
throw new Error(`Type: ${this.typeId} cannot be imported`);
|
|
260
265
|
}
|
|
261
266
|
|
|
267
|
+
const context: RefreshContext<T> = {
|
|
268
|
+
commandType: 'import',
|
|
269
|
+
isStateful: true,
|
|
270
|
+
originalDesiredConfig: structuredClone(parameters),
|
|
271
|
+
};
|
|
272
|
+
|
|
262
273
|
this.addDefaultValues(parameters);
|
|
263
274
|
await this.applyTransformParameters(parameters);
|
|
264
275
|
|
|
@@ -287,7 +298,7 @@ export class ResourceController<T extends StringIndexedObject> {
|
|
|
287
298
|
allStatefulParameters,
|
|
288
299
|
} = parsedConfig;
|
|
289
300
|
|
|
290
|
-
const currentParametersArray = await this.refreshNonStatefulParameters(allNonStatefulParameters);
|
|
301
|
+
const currentParametersArray = await this.refreshNonStatefulParameters(allNonStatefulParameters, context);
|
|
291
302
|
|
|
292
303
|
if (currentParametersArray === null
|
|
293
304
|
|| currentParametersArray === undefined
|
|
@@ -434,8 +445,8 @@ ${JSON.stringify(refresh, null, 2)}
|
|
|
434
445
|
|
|
435
446
|
}
|
|
436
447
|
|
|
437
|
-
private async refreshNonStatefulParameters(resourceParameters: Partial<T>): Promise<Array<Partial<T>> | null> {
|
|
438
|
-
const result = await this.resource.refresh(resourceParameters);
|
|
448
|
+
private async refreshNonStatefulParameters(resourceParameters: Partial<T>, context: RefreshContext<T>): Promise<Array<Partial<T>> | null> {
|
|
449
|
+
const result = await this.resource.refresh(resourceParameters, context);
|
|
439
450
|
|
|
440
451
|
const currentParametersArray = Array.isArray(result) || result === null
|
|
441
452
|
? result
|
|
@@ -4,7 +4,7 @@ import isObjectsEqual from 'lodash.isequal'
|
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
|
|
6
6
|
import { ArrayStatefulParameter, StatefulParameter } from '../stateful-parameter/stateful-parameter.js';
|
|
7
|
-
import { areArraysEqual,
|
|
7
|
+
import { addVariablesToPath, areArraysEqual, resolvePathWithVariables, tildify, untildify } from '../utils/utils.js';
|
|
8
8
|
|
|
9
9
|
export interface InputTransformation {
|
|
10
10
|
to: (input: any) => Promise<any> | any;
|
|
@@ -304,8 +304,8 @@ const ParameterEqualsDefaults: Partial<Record<ParameterSettingType, (a: unknown,
|
|
|
304
304
|
'boolean': (a: unknown, b: unknown) => Boolean(a) === Boolean(b),
|
|
305
305
|
'directory': (a: unknown, b: unknown) => {
|
|
306
306
|
const notCaseSensitive = process.platform === 'darwin';
|
|
307
|
-
const transformedA = path.resolve(
|
|
308
|
-
const transformedB = path.resolve(
|
|
307
|
+
const transformedA = path.resolve(resolvePathWithVariables(untildify(notCaseSensitive ? String(a).toLowerCase() : String(a))))
|
|
308
|
+
const transformedB = path.resolve(resolvePathWithVariables(untildify(notCaseSensitive ? String(b).toLowerCase() : String(b))))
|
|
309
309
|
|
|
310
310
|
return transformedA === transformedB;
|
|
311
311
|
},
|
|
@@ -368,8 +368,8 @@ export function resolveFnFromEqualsFnOrString(
|
|
|
368
368
|
|
|
369
369
|
const ParameterTransformationDefaults: Partial<Record<ParameterSettingType, InputTransformation>> = {
|
|
370
370
|
'directory': {
|
|
371
|
-
to: (a: unknown) => path.resolve(
|
|
372
|
-
from: (a: unknown) => tildify(String(a)),
|
|
371
|
+
to: (a: unknown) => path.resolve(resolvePathWithVariables((untildify(String(a))))),
|
|
372
|
+
from: (a: unknown) => addVariablesToPath(tildify(String(a))),
|
|
373
373
|
},
|
|
374
374
|
'string': {
|
|
375
375
|
to: String,
|
package/src/resource/resource.ts
CHANGED
|
@@ -4,6 +4,12 @@ import { ParameterChange } from '../plan/change-set.js';
|
|
|
4
4
|
import { CreatePlan, DestroyPlan, ModifyPlan } from '../plan/plan-types.js';
|
|
5
5
|
import { ResourceSettings } from './resource-settings.js';
|
|
6
6
|
|
|
7
|
+
export interface RefreshContext<T extends StringIndexedObject> {
|
|
8
|
+
isStateful: boolean;
|
|
9
|
+
commandType: 'destroy' | 'import' | 'plan';
|
|
10
|
+
originalDesiredConfig: Partial<T> | null;
|
|
11
|
+
}
|
|
12
|
+
|
|
7
13
|
/**
|
|
8
14
|
* A resource represents an object on the system (application, CLI tool, or setting)
|
|
9
15
|
* that has state and can be created and destroyed. Examples of resources include CLI tools
|
|
@@ -73,10 +79,12 @@ export abstract class Resource<T extends StringIndexedObject> {
|
|
|
73
79
|
* of the desired config. In stateful mode, this will be parameters of the state config + the desired
|
|
74
80
|
* config of any new parameters.
|
|
75
81
|
*
|
|
82
|
+
* @param context Context surrounding the request
|
|
83
|
+
*
|
|
76
84
|
* @return A config or an array of configs representing the status of the resource on the
|
|
77
85
|
* system currently
|
|
78
86
|
*/
|
|
79
|
-
abstract refresh(parameters: Partial<T>): Promise<Array<Partial<T>> | Partial<T> | null>;
|
|
87
|
+
abstract refresh(parameters: Partial<T>, context: RefreshContext<T>): Promise<Array<Partial<T>> | Partial<T> | null>;
|
|
80
88
|
|
|
81
89
|
/**
|
|
82
90
|
* Create the resource (install) based on the parameters passed in. Only the desired parameters will
|
package/src/utils/utils.test.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest';
|
|
2
|
-
import { splitUserConfig } from './utils.js';
|
|
2
|
+
import { addVariablesToPath, resolvePathWithVariables, splitUserConfig } from './utils.js';
|
|
3
|
+
import os from 'node:os';
|
|
3
4
|
|
|
4
5
|
describe('Utils tests', () => {
|
|
5
6
|
it('Can split a config correctly', () => {
|
|
@@ -26,4 +27,25 @@ describe('Utils tests', () => {
|
|
|
26
27
|
propD: 'propD',
|
|
27
28
|
})
|
|
28
29
|
})
|
|
30
|
+
|
|
31
|
+
it('Can remove variables from a path', () => {
|
|
32
|
+
const testPath1 = '$HOME/my/path';
|
|
33
|
+
const result1 = resolvePathWithVariables(testPath1);
|
|
34
|
+
|
|
35
|
+
const home = os.homedir();
|
|
36
|
+
expect(result1).to.eq(home + '/my/path');
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
const testPath2 = '/var$HOME/my/path';
|
|
40
|
+
const result2 = resolvePathWithVariables(testPath2);
|
|
41
|
+
expect(result2).to.eq('/var' + home + '/my/path');
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
it('Can add variables to a path', () => {
|
|
45
|
+
const testPath1 = os.homedir() + '/my/path';
|
|
46
|
+
const result1 = addVariablesToPath(testPath1);
|
|
47
|
+
|
|
48
|
+
const home = os.homedir();
|
|
49
|
+
expect(result1).to.eq('$HOME/my/path');
|
|
50
|
+
})
|
|
29
51
|
})
|
package/src/utils/utils.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ResourceConfig, StringIndexedObject } from 'codify-schemas';
|
|
2
2
|
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
3
4
|
|
|
4
5
|
export function isDebug(): boolean {
|
|
5
6
|
return process.env.DEBUG != null && process.env.DEBUG.includes('codify'); // TODO: replace with debug library
|
|
@@ -37,6 +38,24 @@ export function tildify(pathWithTilde: string) {
|
|
|
37
38
|
return homeDirectory ? pathWithTilde.replace(homeDirectory, '~') : pathWithTilde;
|
|
38
39
|
}
|
|
39
40
|
|
|
41
|
+
export function resolvePathWithVariables(pathWithVariables: string) {
|
|
42
|
+
// @ts-expect-error Ignore this for now
|
|
43
|
+
return pathWithVariables.replace(/\$([A-Z_]+[A-Z0-9_]*)|\${([A-Z0-9_]*)}/ig, (_, a, b) => process.env[a || b])
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function addVariablesToPath(pathWithoutVariables: string) {
|
|
47
|
+
let result = pathWithoutVariables;
|
|
48
|
+
for (const [key, value] of Object.entries(process.env)) {
|
|
49
|
+
if (!value || !path.isAbsolute(value) || value === '/') {
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
result = result.replaceAll(value, `$${key}`)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return result;
|
|
57
|
+
}
|
|
58
|
+
|
|
40
59
|
export function unhome(pathWithHome: string): string {
|
|
41
60
|
return pathWithHome.includes('$HOME') ? pathWithHome.replaceAll('$HOME', os.homedir()) : pathWithHome;
|
|
42
61
|
}
|