@postxl/generator 0.74.1 → 1.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.
- package/LICENSE +50 -0
- package/README.md +79 -1
- package/dist/generator-manager.class.d.ts +59 -0
- package/dist/generator-manager.class.js +221 -0
- package/dist/generator.class.d.ts +90 -0
- package/dist/generator.class.js +32 -0
- package/dist/generator.context.d.ts +174 -0
- package/dist/generator.context.js +125 -0
- package/dist/helpers/branded.types.d.ts +149 -0
- package/dist/helpers/branded.types.js +111 -0
- package/dist/helpers/config-builder.class.d.ts +27 -0
- package/dist/helpers/config-builder.class.js +54 -0
- package/dist/helpers/import-generator.class.d.ts +70 -0
- package/dist/helpers/import-generator.class.js +166 -0
- package/dist/helpers/importable.types.d.ts +52 -0
- package/dist/helpers/importable.types.js +15 -0
- package/dist/helpers/index-generator.class.d.ts +10 -0
- package/dist/helpers/index-generator.class.js +46 -0
- package/dist/helpers/index.d.ts +8 -0
- package/dist/helpers/index.js +24 -0
- package/dist/helpers/package-json.generator.d.ts +56 -0
- package/dist/helpers/package-json.generator.js +36 -0
- package/dist/helpers/tsconfig.generator.d.ts +1 -0
- package/dist/helpers/tsconfig.generator.js +14 -0
- package/dist/helpers/verify-context.d.ts +4 -0
- package/dist/helpers/verify-context.js +23 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +21 -0
- package/dist/utils/checksum.d.ts +10 -0
- package/dist/utils/checksum.js +132 -0
- package/dist/utils/fs-utils.d.ts +34 -0
- package/dist/utils/fs-utils.js +126 -0
- package/dist/utils/index.d.ts +10 -0
- package/dist/utils/index.js +26 -0
- package/dist/utils/jsdoc.d.ts +12 -0
- package/dist/utils/jsdoc.js +37 -0
- package/dist/utils/lint.d.ts +46 -0
- package/dist/utils/lint.js +154 -0
- package/dist/utils/lockfile.d.ts +7 -0
- package/dist/utils/lockfile.js +80 -0
- package/dist/utils/logger.class.d.ts +25 -0
- package/dist/utils/logger.class.js +55 -0
- package/dist/utils/merge-conflict.d.ts +55 -0
- package/dist/utils/merge-conflict.js +264 -0
- package/dist/utils/path.d.ts +52 -0
- package/dist/utils/path.js +183 -0
- package/dist/utils/prettier-config.d.ts +2 -0
- package/dist/utils/prettier-config.js +13 -0
- package/dist/utils/prettier.d.ts +5 -0
- package/dist/utils/prettier.js +67 -0
- package/dist/utils/prettier.skiptest.d.ts +1 -0
- package/dist/utils/prettier.skiptest.js +22 -0
- package/dist/utils/promise.d.ts +2 -0
- package/dist/utils/promise.js +10 -0
- package/dist/utils/string-functions.d.ts +9 -0
- package/dist/utils/string-functions.js +23 -0
- package/dist/utils/sync-log-result.d.ts +9 -0
- package/dist/utils/sync-log-result.js +90 -0
- package/dist/utils/sync.d.ts +143 -0
- package/dist/utils/sync.js +325 -0
- package/dist/utils/template.d.ts +66 -0
- package/dist/utils/template.js +159 -0
- package/dist/utils/vfs.class.d.ts +115 -0
- package/dist/utils/vfs.class.js +239 -0
- package/dist/utils/zip.d.ts +13 -0
- package/dist/utils/zip.js +40 -0
- package/package.json +57 -34
- package/dist/generator.d.ts +0 -13
- package/dist/generator.js +0 -455
- package/dist/generators/enums/react.generator.d.ts +0 -10
- package/dist/generators/enums/react.generator.js +0 -110
- package/dist/generators/enums/types.generator.d.ts +0 -10
- package/dist/generators/enums/types.generator.js +0 -39
- package/dist/generators/indices/data/module.generator.d.ts +0 -9
- package/dist/generators/indices/data/module.generator.js +0 -60
- package/dist/generators/indices/data/service.generator.d.ts +0 -9
- package/dist/generators/indices/data/service.generator.js +0 -249
- package/dist/generators/indices/data/types.generator.d.ts +0 -9
- package/dist/generators/indices/data/types.generator.js +0 -49
- package/dist/generators/indices/dispatcher-service.generator.d.ts +0 -9
- package/dist/generators/indices/dispatcher-service.generator.js +0 -107
- package/dist/generators/indices/export/class.generator.d.ts +0 -9
- package/dist/generators/indices/export/class.generator.js +0 -140
- package/dist/generators/indices/export/encoder.generator.d.ts +0 -9
- package/dist/generators/indices/export/encoder.generator.js +0 -50
- package/dist/generators/indices/import/convert-functions.generator.d.ts +0 -9
- package/dist/generators/indices/import/convert-functions.generator.js +0 -509
- package/dist/generators/indices/import/decoder.generator.d.ts +0 -9
- package/dist/generators/indices/import/decoder.generator.js +0 -40
- package/dist/generators/indices/import/service.generator.d.ts +0 -9
- package/dist/generators/indices/import/service.generator.js +0 -573
- package/dist/generators/indices/import/types.generator.d.ts +0 -9
- package/dist/generators/indices/import/types.generator.js +0 -242
- package/dist/generators/indices/repositories.generator.d.ts +0 -9
- package/dist/generators/indices/repositories.generator.js +0 -25
- package/dist/generators/indices/routes.generator.d.ts +0 -9
- package/dist/generators/indices/routes.generator.js +0 -29
- package/dist/generators/indices/seed-migration.generator.d.ts +0 -9
- package/dist/generators/indices/seed-migration.generator.js +0 -36
- package/dist/generators/indices/seed-template.generator.d.ts +0 -9
- package/dist/generators/indices/seed-template.generator.js +0 -80
- package/dist/generators/indices/testids.generator.d.ts +0 -7
- package/dist/generators/indices/testids.generator.js +0 -71
- package/dist/generators/indices/types.generator.d.ts +0 -10
- package/dist/generators/indices/types.generator.js +0 -35
- package/dist/generators/indices/update/actiontypes.generator.d.ts +0 -9
- package/dist/generators/indices/update/actiontypes.generator.js +0 -49
- package/dist/generators/indices/update/module.generator.d.ts +0 -9
- package/dist/generators/indices/update/module.generator.js +0 -41
- package/dist/generators/indices/update/service.generator.d.ts +0 -9
- package/dist/generators/indices/update/service.generator.js +0 -34
- package/dist/generators/indices/view/module.generator.d.ts +0 -9
- package/dist/generators/indices/view/module.generator.js +0 -39
- package/dist/generators/indices/view/service.generator.d.ts +0 -9
- package/dist/generators/indices/view/service.generator.js +0 -34
- package/dist/generators/models/admin.page.generator.d.ts +0 -7
- package/dist/generators/models/admin.page.generator.js +0 -74
- package/dist/generators/models/export/encoder.generator.d.ts +0 -9
- package/dist/generators/models/export/encoder.generator.js +0 -51
- package/dist/generators/models/import/decoder.generator.d.ts +0 -9
- package/dist/generators/models/import/decoder.generator.js +0 -148
- package/dist/generators/models/react/context.generator.d.ts +0 -9
- package/dist/generators/models/react/context.generator.js +0 -71
- package/dist/generators/models/react/index.d.ts +0 -10
- package/dist/generators/models/react/index.js +0 -31
- package/dist/generators/models/react/library.generator.d.ts +0 -10
- package/dist/generators/models/react/library.generator.js +0 -94
- package/dist/generators/models/react/lookup.generator.d.ts +0 -9
- package/dist/generators/models/react/lookup.generator.js +0 -175
- package/dist/generators/models/react/modals.generator.d.ts +0 -23
- package/dist/generators/models/react/modals.generator.js +0 -710
- package/dist/generators/models/repository.generator.d.ts +0 -9
- package/dist/generators/models/repository.generator.js +0 -955
- package/dist/generators/models/route.generator.d.ts +0 -9
- package/dist/generators/models/route.generator.js +0 -92
- package/dist/generators/models/seed.generator.d.ts +0 -21
- package/dist/generators/models/seed.generator.js +0 -285
- package/dist/generators/models/stub.generator.d.ts +0 -9
- package/dist/generators/models/stub.generator.js +0 -92
- package/dist/generators/models/types.generator.d.ts +0 -9
- package/dist/generators/models/types.generator.js +0 -125
- package/dist/generators/models/update/service.generator.d.ts +0 -10
- package/dist/generators/models/update/service.generator.js +0 -302
- package/dist/generators/models/view/service.generator.d.ts +0 -10
- package/dist/generators/models/view/service.generator.js +0 -239
- package/dist/lib/attributes.d.ts +0 -114
- package/dist/lib/attributes.js +0 -2
- package/dist/lib/exports.d.ts +0 -45
- package/dist/lib/exports.js +0 -90
- package/dist/lib/imports.d.ts +0 -65
- package/dist/lib/imports.js +0 -114
- package/dist/lib/meta.d.ts +0 -1191
- package/dist/lib/meta.js +0 -434
- package/dist/lib/schema/fields.d.ts +0 -46
- package/dist/lib/schema/fields.js +0 -62
- package/dist/lib/schema/schema.d.ts +0 -466
- package/dist/lib/schema/schema.js +0 -18
- package/dist/lib/schema/types.d.ts +0 -201
- package/dist/lib/schema/types.js +0 -112
- package/dist/lib/serializer.d.ts +0 -15
- package/dist/lib/serializer.js +0 -24
- package/dist/lib/test-id-collector.d.ts +0 -42
- package/dist/lib/test-id-collector.js +0 -53
- package/dist/lib/types.d.ts +0 -7
- package/dist/lib/types.js +0 -13
- package/dist/lib/typescript.d.ts +0 -5
- package/dist/lib/typescript.js +0 -22
- package/dist/lib/utils/ast.d.ts +0 -29
- package/dist/lib/utils/ast.js +0 -23
- package/dist/lib/utils/error.d.ts +0 -17
- package/dist/lib/utils/error.js +0 -52
- package/dist/lib/utils/file.d.ts +0 -10
- package/dist/lib/utils/file.js +0 -56
- package/dist/lib/utils/jsdoc.d.ts +0 -9
- package/dist/lib/utils/jsdoc.js +0 -37
- package/dist/lib/utils/logger.d.ts +0 -17
- package/dist/lib/utils/logger.js +0 -12
- package/dist/lib/utils/string.d.ts +0 -40
- package/dist/lib/utils/string.js +0 -187
- package/dist/lib/utils/types.d.ts +0 -12
- package/dist/lib/utils/types.js +0 -2
- package/dist/lib/zod.d.ts +0 -8
- package/dist/lib/zod.js +0 -60
- package/dist/prisma/attributes.d.ts +0 -21
- package/dist/prisma/attributes.js +0 -175
- package/dist/prisma/client-path.d.ts +0 -7
- package/dist/prisma/client-path.js +0 -29
- package/dist/prisma/parse.d.ts +0 -12
- package/dist/prisma/parse.js +0 -452
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* The virtual file system represents a file system that can be manipulated in memory.
|
|
4
|
+
*
|
|
5
|
+
* This sync extension allows a 3-way sync between the file system and the disk.
|
|
6
|
+
*
|
|
7
|
+
* The 3 sources are:
|
|
8
|
+
* 1. The data written by code/the generators to the VFS
|
|
9
|
+
* 2. The hash sums from prior generations (indicating which files were changed)
|
|
10
|
+
* 3. The actual disk files
|
|
11
|
+
*
|
|
12
|
+
* The overall usage pattern is:
|
|
13
|
+
* 1. The VFS initializes with a lock file - and reads the hash sums from the disk
|
|
14
|
+
* 2. The code/generators write to the VFS
|
|
15
|
+
* 3. The VFS "writes" to disk, performing this 3-way sync with the below algorithm
|
|
16
|
+
*
|
|
17
|
+
*
|
|
18
|
+
* ### Algorithm
|
|
19
|
+
*
|
|
20
|
+
* For each file in the VFS we have 3 different states - the file in memory (`virtual`), the hash sum from prior generations from the lock file (`lock`), and the file from disk (`disk`).
|
|
21
|
+
*
|
|
22
|
+
* Each of these 3 states can be "empty" - in case the file was not generated/generated last time/does not exist on disk - or represented by a hash value (`H`).
|
|
23
|
+
*
|
|
24
|
+
* Thus, we have the following potential states for each file:
|
|
25
|
+
* - virtual: `empty` | `Hash1` (hash sum from current generation)
|
|
26
|
+
* - lock: `empty` | `Hash1` | `Hash0` (hash sum from prior generation)
|
|
27
|
+
* - disk: `empty` | `Hash1` | `Hash0` | `HashX` (different hash sum from disk)
|
|
28
|
+
*
|
|
29
|
+
* The below table shows all possible combinations of these states - and the resulting action that needs to be performed:
|
|
30
|
+
*
|
|
31
|
+
* |-------|-------|---------|---------------------------------|---------------------------|
|
|
32
|
+
* | VFS | Lock | Disk | Description | Action on disk |
|
|
33
|
+
* |-------|-------|---------|---------------------------------|---------------------------|
|
|
34
|
+
* | Hash1 | Hash1 | Hash1 | Unchanged | No action |
|
|
35
|
+
* | Hash1 | Hash1 | HashX/0 | Ejected | No action |
|
|
36
|
+
* | Hash1 | Hash1 | empty | Deleted on disk | No action |
|
|
37
|
+
* | Hash1 | Hash0 | Hash1 | Corrupt lock file? | No action |
|
|
38
|
+
* | Hash1 | Hash0 | Hash0 | Changed, not ejected | Update to VFS version |
|
|
39
|
+
* | Hash1 | Hash0 | HashX | Changed, ejected | Create "merge conflict"(2)|
|
|
40
|
+
* | Hash1 | Hash0 | empty | Changed, deleted on disk | Write file (1) |
|
|
41
|
+
* | Hash1 | empty | Hash1 | Corrupt lock file? | No action |
|
|
42
|
+
* | Hash1 | empty | HashX/0 | File ejected, corrupt lock file | Create "merge conflict"(2)|
|
|
43
|
+
* | Hash1 | empty | empty | Newly generated | Write file |
|
|
44
|
+
* |-------|-------|---------|---------------------------------|---------------------------|
|
|
45
|
+
* | empty | Hash0 | Hash0 | Unejected file | Delete file |
|
|
46
|
+
* | empty | Hash0 | HashX | Ejected file was deleted | Delete file (3) |
|
|
47
|
+
* | empty | Hash0 | empty | File was manually deleted first | No action |
|
|
48
|
+
* | empty | empty | HashX | Manual file | No action |
|
|
49
|
+
* | empty | empty | empty | Cannot occur | |
|
|
50
|
+
* |-------|-------|---------|---------------------------------|---------------------------|
|
|
51
|
+
*
|
|
52
|
+
* Notes:
|
|
53
|
+
* (1) In case the file was deleted on disk and changed between the last generation and the current generation, we will write the file to disk. The developer can decide to keep the "restored" file or not. Next time generator runs, it will not be regenerated, as lock file hashes match.
|
|
54
|
+
* (2) In case the file was ejected and changed between the last generation and the current generation, we will overwrite the file on disk - with git "merge conflict" markers. The developer must then decide how to resolve the conflicts.
|
|
55
|
+
* (3) In case the file was deleted by the generator but ejected by the developer, we will delete the file from disk. The developer can decide to revert the deletion or not. Next time generator runs, it will not be regenerated, as lock file hashes match.
|
|
56
|
+
*
|
|
57
|
+
*/
|
|
58
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
59
|
+
if (k2 === undefined) k2 = k;
|
|
60
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
61
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
62
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
63
|
+
}
|
|
64
|
+
Object.defineProperty(o, k2, desc);
|
|
65
|
+
}) : (function(o, m, k, k2) {
|
|
66
|
+
if (k2 === undefined) k2 = k;
|
|
67
|
+
o[k2] = m[k];
|
|
68
|
+
}));
|
|
69
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
70
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
71
|
+
}) : function(o, v) {
|
|
72
|
+
o["default"] = v;
|
|
73
|
+
});
|
|
74
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
75
|
+
var ownKeys = function(o) {
|
|
76
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
77
|
+
var ar = [];
|
|
78
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
79
|
+
return ar;
|
|
80
|
+
};
|
|
81
|
+
return ownKeys(o);
|
|
82
|
+
};
|
|
83
|
+
return function (mod) {
|
|
84
|
+
if (mod && mod.__esModule) return mod;
|
|
85
|
+
var result = {};
|
|
86
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
87
|
+
__setModuleDefault(result, mod);
|
|
88
|
+
return result;
|
|
89
|
+
};
|
|
90
|
+
})();
|
|
91
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
92
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
93
|
+
};
|
|
94
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
95
|
+
exports.sync = sync;
|
|
96
|
+
exports.executeAction = executeAction;
|
|
97
|
+
const promises_1 = __importDefault(require("fs/promises"));
|
|
98
|
+
const p_limit_1 = __importDefault(require("p-limit"));
|
|
99
|
+
const utils_1 = require("@postxl/utils");
|
|
100
|
+
const checksum_1 = require("./checksum");
|
|
101
|
+
const fs_utils_1 = require("./fs-utils");
|
|
102
|
+
const lockfile_1 = require("./lockfile");
|
|
103
|
+
const merge_conflict_1 = require("./merge-conflict");
|
|
104
|
+
const Path = __importStar(require("./path"));
|
|
105
|
+
/**
|
|
106
|
+
* Synchronizes the virtual file system with the disk file system.
|
|
107
|
+
*
|
|
108
|
+
* It takes into account the lock file to determine the state of each file.
|
|
109
|
+
* Depending on the state/checksum of each file in VFS, lock file and disk file system,
|
|
110
|
+
* it will perform the appropriate action (write, delete, merge conflict) to update the disk file system.
|
|
111
|
+
*
|
|
112
|
+
* It also updates the lock file with the current state of the VFS.
|
|
113
|
+
*
|
|
114
|
+
* Note: File filtering is now handled at the VFS level, so only files that were added to the VFS
|
|
115
|
+
* (respecting any file pattern filter) will be synced and tracked in the lock file.
|
|
116
|
+
*
|
|
117
|
+
* IMPORTANT: Before writing any files, this function checks if there are any files with unresolved
|
|
118
|
+
* merge conflict markers. If found, the sync will abort immediately and return an error with the
|
|
119
|
+
* list of files that need to be resolved before generation can continue.
|
|
120
|
+
*/
|
|
121
|
+
async function sync({ vfs, lockFilePath, diskFilePath, force }) {
|
|
122
|
+
const diskPathNormalized = Path.normalize(diskFilePath);
|
|
123
|
+
const files = await getFilesStates({ vfs, lockFilePath, diskFilePath });
|
|
124
|
+
// Check for unresolved merge conflicts before writing any files
|
|
125
|
+
const filesWithConflicts = findFilesWithMergeConflicts(files);
|
|
126
|
+
if (filesWithConflicts.length > 0) {
|
|
127
|
+
return {
|
|
128
|
+
success: false,
|
|
129
|
+
error: {
|
|
130
|
+
type: 'UnresolvedMergeConflictsError',
|
|
131
|
+
message: `Cannot proceed with generation: ${filesWithConflicts.length} file(s) contain unresolved merge conflicts. Please resolve these conflicts before running the generator again.`,
|
|
132
|
+
filesWithConflicts,
|
|
133
|
+
},
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
const limit = (0, p_limit_1.default)(50);
|
|
137
|
+
const tasks = [];
|
|
138
|
+
const result = {
|
|
139
|
+
errors: [],
|
|
140
|
+
files: {},
|
|
141
|
+
};
|
|
142
|
+
for (const [filePath, inputState] of files) {
|
|
143
|
+
const [action, isEjected] = determineActionState(inputState, force);
|
|
144
|
+
const task = limit(async () => {
|
|
145
|
+
const actionResult = await executeAction(Path.join(diskPathNormalized, filePath), action);
|
|
146
|
+
if (actionResult.isErr()) {
|
|
147
|
+
result.errors.push(actionResult.unwrapErr());
|
|
148
|
+
}
|
|
149
|
+
result.files[filePath] = { action, inputState, actionResult, isEjected };
|
|
150
|
+
});
|
|
151
|
+
tasks.push(task);
|
|
152
|
+
}
|
|
153
|
+
tasks.push(limit(() => (0, lockfile_1.writeLockFile)(lockFilePath, vfs)));
|
|
154
|
+
await Promise.all(tasks);
|
|
155
|
+
return { success: true, ...result };
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Finds all files in the disk that contain unresolved merge conflict markers.
|
|
159
|
+
*
|
|
160
|
+
* @param files - The files states map from getFilesStates
|
|
161
|
+
* @returns Array of file paths that contain merge conflict markers
|
|
162
|
+
*/
|
|
163
|
+
function findFilesWithMergeConflicts(files) {
|
|
164
|
+
const filesWithConflicts = [];
|
|
165
|
+
for (const [filePath, { disk }] of files) {
|
|
166
|
+
if (disk.state === 'hash' && (0, merge_conflict_1.hasMergeConflictMarkers)(disk.content)) {
|
|
167
|
+
filesWithConflicts.push(filePath);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return filesWithConflicts;
|
|
171
|
+
}
|
|
172
|
+
function getChecksum(file) {
|
|
173
|
+
switch (file.state) {
|
|
174
|
+
case 'hash':
|
|
175
|
+
return file.hash;
|
|
176
|
+
case 'empty':
|
|
177
|
+
return undefined;
|
|
178
|
+
default:
|
|
179
|
+
throw new utils_1.ExhaustiveSwitchCheck(file);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
function getStateKey({ virtual, lock, disk }) {
|
|
183
|
+
const stateKey_Virtual = virtual.state === 'hash' ? 'V:Hash1' : 'V:empty';
|
|
184
|
+
const H1 = getChecksum(virtual);
|
|
185
|
+
const stateKey_Lock = getStateKey_Lock({ lock, H1 });
|
|
186
|
+
const Hash0 = getChecksum(lock);
|
|
187
|
+
const stateKey_Disk = getStateKey_Disk({ disk, H1, Hash0 });
|
|
188
|
+
return `${stateKey_Virtual}-${stateKey_Lock}-${stateKey_Disk}`;
|
|
189
|
+
}
|
|
190
|
+
function getStateKey_Lock({ lock, H1 }) {
|
|
191
|
+
if (lock.state === 'empty') {
|
|
192
|
+
return 'L:empty';
|
|
193
|
+
}
|
|
194
|
+
if (H1 === undefined) {
|
|
195
|
+
return 'L:Hash0';
|
|
196
|
+
}
|
|
197
|
+
if (lock.hash === H1) {
|
|
198
|
+
return 'L:Hash1';
|
|
199
|
+
}
|
|
200
|
+
return 'L:Hash0';
|
|
201
|
+
}
|
|
202
|
+
function getStateKey_Disk({ disk, H1, Hash0, }) {
|
|
203
|
+
if (disk.state === 'empty') {
|
|
204
|
+
return 'D:empty';
|
|
205
|
+
}
|
|
206
|
+
if (H1 !== undefined && disk.hash === H1) {
|
|
207
|
+
return 'D:Hash1';
|
|
208
|
+
}
|
|
209
|
+
if (Hash0 !== undefined && disk.hash === Hash0) {
|
|
210
|
+
return 'D:Hash0';
|
|
211
|
+
}
|
|
212
|
+
return 'D:HashX';
|
|
213
|
+
}
|
|
214
|
+
async function getDiskFileState(diskFilePath) {
|
|
215
|
+
if (await promises_1.default.stat(diskFilePath).catch(() => null)) {
|
|
216
|
+
const result = await (0, fs_utils_1.readFile)(diskFilePath);
|
|
217
|
+
if (result.isErr()) {
|
|
218
|
+
return { state: 'empty' };
|
|
219
|
+
}
|
|
220
|
+
const content = result.unwrap();
|
|
221
|
+
const checksum = await (0, checksum_1.calculateChecksum)(content);
|
|
222
|
+
return { state: 'hash', hash: checksum, content: content };
|
|
223
|
+
}
|
|
224
|
+
return { state: 'empty' };
|
|
225
|
+
}
|
|
226
|
+
async function getFilesStates({ vfs, lockFilePath, diskFilePath, }) {
|
|
227
|
+
const lockFile = await (0, lockfile_1.readLockFile)(lockFilePath);
|
|
228
|
+
const files = new Map();
|
|
229
|
+
const diskPathNormalized = Path.normalize(diskFilePath);
|
|
230
|
+
// We first add all files from the virtual file system.
|
|
231
|
+
for (const [filePath, content] of vfs.files) {
|
|
232
|
+
const virtual = { state: 'hash', hash: await (0, checksum_1.calculateChecksum)(content), content };
|
|
233
|
+
const path = filePath;
|
|
234
|
+
const lock = lockFile?.has(path) ? { state: 'hash', hash: lockFile.get(path) } : { state: 'empty' };
|
|
235
|
+
const disk = await getDiskFileState(Path.join(diskPathNormalized, path));
|
|
236
|
+
files.set(path, { virtual, lock, disk });
|
|
237
|
+
}
|
|
238
|
+
// Then we add all files from the lock file (as the last generator run might not have created all old files).
|
|
239
|
+
// However, if the VFS has a file pattern filter, we should only consider lock file entries that match the pattern.
|
|
240
|
+
if (lockFile) {
|
|
241
|
+
const virtual = { state: 'empty' };
|
|
242
|
+
for (const [filePath, content] of lockFile) {
|
|
243
|
+
if (files.has(filePath)) {
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
246
|
+
// Skip files that don't match the VFS pattern filter (if one is set)
|
|
247
|
+
if (!vfs.matchesPattern(filePath)) {
|
|
248
|
+
continue;
|
|
249
|
+
}
|
|
250
|
+
const lock = { state: 'hash', hash: content };
|
|
251
|
+
const disk = await getDiskFileState(Path.join(diskPathNormalized, filePath));
|
|
252
|
+
files.set(filePath, { virtual, lock, disk });
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
return files;
|
|
256
|
+
}
|
|
257
|
+
const actionMap = {
|
|
258
|
+
// No change between current and last generator run:
|
|
259
|
+
'V:Hash1-L:Hash1-D:HashX': ['NoAction', true, 'Write'], // Ejected
|
|
260
|
+
'V:Hash1-L:Hash1-D:Hash1': ['NoAction', false, 'NoAction'], // Unchanged
|
|
261
|
+
'V:Hash1-L:Hash1-D:Hash0': ['NoAction', true, 'Write'], // Should not occur, but can be considered as ejected
|
|
262
|
+
'V:Hash1-L:Hash1-D:empty': ['NoAction', true, 'Write'], // Deleted on disk, we want to keep it this way
|
|
263
|
+
// Change between current and last generator run:
|
|
264
|
+
'V:Hash1-L:Hash0-D:HashX': ['MergeConflict', true, 'Write'], // File ejected (In case the file was ejected and changed between the last generation and the current generation, we will overwrite the file on disk - with git "merge conflict" markers. The developer must then decide how to resolve the conflicts.)
|
|
265
|
+
'V:Hash1-L:Hash0-D:Hash1': ['NoAction', false, 'NoAction'], // Should not occur, corrupt lock file?
|
|
266
|
+
'V:Hash1-L:Hash0-D:Hash0': ['Write', false, 'Write'], // Changed, not ejected
|
|
267
|
+
'V:Hash1-L:Hash0-D:empty': ['Write', true, 'Write'], // Changed, deleted on disk -> overwrite (in case the file was deleted on disk and changed between the last generation and the current generation, we will write the file to disk. The developer can decide to keep the "restored" file or not. Next time generator runs, it will not be regenerated, as lock file hashes match.)
|
|
268
|
+
// Newly generated, did not exist before:
|
|
269
|
+
'V:Hash1-L:empty-D:HashX': ['MergeConflict', true, 'Write'], // File ejected or was created manually (In case the file was ejected and changed between the last generation and the current generation, we will overwrite the file on disk - with git "merge conflict" markers. The developer must then decide how to resolve the conflicts.)
|
|
270
|
+
'V:Hash1-L:empty-D:Hash1': ['NoAction', false, 'NoAction'], // File was manually created first
|
|
271
|
+
'V:Hash1-L:empty-D:Hash0': ['NoAction', true, 'Write'], // Cannot occur, but kept for type completeness
|
|
272
|
+
'V:Hash1-L:empty-D:empty': ['Write', false, 'Write'], // Newly generated file
|
|
273
|
+
// Generator did not create the file:
|
|
274
|
+
'V:empty-L:Hash1-D:HashX': ['NoAction', false, 'NoAction'], // Cannot occur, but kept for type completeness
|
|
275
|
+
'V:empty-L:Hash1-D:Hash1': ['NoAction', false, 'NoAction'], // Cannot occur, but kept for type completeness
|
|
276
|
+
'V:empty-L:Hash1-D:Hash0': ['NoAction', false, 'NoAction'], // Cannot occur, but kept for type completeness
|
|
277
|
+
'V:empty-L:Hash1-D:empty': ['NoAction', false, 'NoAction'], // Cannot occur, but kept for type completeness
|
|
278
|
+
'V:empty-L:Hash0-D:HashX': ['Delete', true, 'Delete'], // Ejected file was deleted -> delete (in case the file was deleted by the generator but ejected by the developer, we will delete the file from disk. The developer can decide to revert the deletion or not. Next time generator runs, it will not be regenerated, as lock file hashes match.)
|
|
279
|
+
'V:empty-L:Hash0-D:Hash1': ['Delete', true, 'Delete'], // Indistinguishable from "empty-Hash0-HashX"
|
|
280
|
+
'V:empty-L:Hash0-D:Hash0': ['Delete', false, 'Delete'], // Unejected file
|
|
281
|
+
'V:empty-L:Hash0-D:empty': ['NoAction', false, 'NoAction'], // File was manually deleted first
|
|
282
|
+
'V:empty-L:empty-D:HashX': ['NoAction', false, 'NoAction'], // Manual file
|
|
283
|
+
'V:empty-L:empty-D:Hash1': ['NoAction', false, 'NoAction'], // Indistinguishable from "empty-empty-HashX"
|
|
284
|
+
'V:empty-L:empty-D:Hash0': ['NoAction', false, 'NoAction'], // Indistinguishable from "empty-empty-HashX"
|
|
285
|
+
'V:empty-L:empty-D:empty': ['NoAction', false, 'NoAction'], // Cannot occur
|
|
286
|
+
};
|
|
287
|
+
function determineActionState({ virtual, lock, disk }, force) {
|
|
288
|
+
const stateKey = getStateKey({ virtual, lock, disk });
|
|
289
|
+
const [defaultActionType, isEjected, forceActionType] = actionMap[stateKey];
|
|
290
|
+
const actionType = force ? forceActionType : defaultActionType;
|
|
291
|
+
if (actionType === 'Write') {
|
|
292
|
+
if (virtual.state === 'empty') {
|
|
293
|
+
throw new Error(`This should not happen assuming that the actionMap only has 'Write' actions for entries starting with Hash1, ie a defined virtual file`);
|
|
294
|
+
}
|
|
295
|
+
return [{ type: 'Write', content: virtual.content }, isEjected];
|
|
296
|
+
}
|
|
297
|
+
else if (actionType === 'MergeConflict') {
|
|
298
|
+
if (disk.state === 'empty') {
|
|
299
|
+
throw new Error(`This should not happen assuming that the actionMap only has 'Write' actions for entries starting with H1, ie a defined virtual file`);
|
|
300
|
+
}
|
|
301
|
+
if (virtual.state === 'empty') {
|
|
302
|
+
throw new Error(`This should not happen assuming that the actionMap only has 'Write' actions for entries starting with H1, ie a defined virtual file`);
|
|
303
|
+
}
|
|
304
|
+
return [{ type: 'MergeConflict', diskContent: disk.content, virtualContent: virtual.content }, isEjected];
|
|
305
|
+
}
|
|
306
|
+
return [{ type: actionType }, isEjected];
|
|
307
|
+
}
|
|
308
|
+
async function executeAction(filePath, action) {
|
|
309
|
+
switch (action.type) {
|
|
310
|
+
case 'NoAction':
|
|
311
|
+
return utils_1.Result.ok(undefined);
|
|
312
|
+
case 'Write':
|
|
313
|
+
return (0, fs_utils_1.writeFile)(filePath, action.content);
|
|
314
|
+
case 'MergeConflict':
|
|
315
|
+
return writeMergeConflictFile(filePath, action);
|
|
316
|
+
case 'Delete':
|
|
317
|
+
return (0, fs_utils_1.deleteFile)(filePath);
|
|
318
|
+
default:
|
|
319
|
+
throw new utils_1.ExhaustiveSwitchCheck(action);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
async function writeMergeConflictFile(filePath, { diskContent, virtualContent }) {
|
|
323
|
+
const content = (0, merge_conflict_1.generateMergeConflict)({ contentSource: diskContent, contentIncoming: virtualContent });
|
|
324
|
+
return (0, fs_utils_1.writeFile)(filePath, content);
|
|
325
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { Result } from '@postxl/utils';
|
|
2
|
+
/**
|
|
3
|
+
* Error thrown when a template variable is not found in the context.
|
|
4
|
+
*/
|
|
5
|
+
export declare class TemplateVariableError extends Error {
|
|
6
|
+
readonly variablePath: string;
|
|
7
|
+
readonly availableKeys: string[];
|
|
8
|
+
constructor(variablePath: string, availableKeys: string[]);
|
|
9
|
+
}
|
|
10
|
+
export type RenderTemplateOptions = {
|
|
11
|
+
/**
|
|
12
|
+
* If true, undefined variables will be replaced with an empty string.
|
|
13
|
+
* If false (default), an error will be thrown for undefined variables.
|
|
14
|
+
*/
|
|
15
|
+
allowUndefined?: boolean;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Renders a template string by replacing `<%expression%>` placeholders with values from the context.
|
|
19
|
+
*
|
|
20
|
+
* The expression supports dot notation for accessing nested properties.
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* renderTemplate('Hello <%name%>!', { name: 'World' })
|
|
24
|
+
* // returns 'Hello World!'
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* renderTemplate('Project: <%schema.slug%>', { schema: { slug: 'demo' } })
|
|
28
|
+
* // returns 'Project: demo'
|
|
29
|
+
*
|
|
30
|
+
* @param template - The template string containing `<%expression%>` placeholders
|
|
31
|
+
* @param context - The context object containing values to substitute
|
|
32
|
+
* @param options - Optional configuration
|
|
33
|
+
* @returns The rendered string with all placeholders replaced
|
|
34
|
+
* @throws {TemplateVariableError} If a variable is not found and allowUndefined is false
|
|
35
|
+
*/
|
|
36
|
+
export declare function renderTemplate(template: string, context: Record<string, unknown>, options?: RenderTemplateOptions): string;
|
|
37
|
+
type ReadTemplateError = {
|
|
38
|
+
type: 'ReadTemplateError';
|
|
39
|
+
message: string;
|
|
40
|
+
error: Error;
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* Reads a template file from disk and renders it with the given context.
|
|
44
|
+
*
|
|
45
|
+
* This is a convenience function for loading static template files and
|
|
46
|
+
* replacing placeholders in a single operation.
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* // In a generator:
|
|
50
|
+
* const content = await generateFromTemplate({
|
|
51
|
+
* file: path.resolve(__dirname, './template/scripts/docker.sh'),
|
|
52
|
+
* context: { schema: context.schema }
|
|
53
|
+
* })
|
|
54
|
+
* vfs.write('/scripts/docker.sh', content)
|
|
55
|
+
*
|
|
56
|
+
* @param params.file - Absolute path to the template file on disk
|
|
57
|
+
* @param params.context - The context object containing values to substitute
|
|
58
|
+
* @param params.options - Optional render options
|
|
59
|
+
* @returns The rendered template content
|
|
60
|
+
*/
|
|
61
|
+
export declare function generateFromTemplate({ file, context, options, }: {
|
|
62
|
+
file: string;
|
|
63
|
+
context: Record<string, unknown>;
|
|
64
|
+
options?: RenderTemplateOptions;
|
|
65
|
+
}): Promise<Result<string, ReadTemplateError>>;
|
|
66
|
+
export {};
|
|
@@ -0,0 +1,159 @@
|
|
|
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
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.TemplateVariableError = void 0;
|
|
37
|
+
exports.renderTemplate = renderTemplate;
|
|
38
|
+
exports.generateFromTemplate = generateFromTemplate;
|
|
39
|
+
const fs = __importStar(require("node:fs/promises"));
|
|
40
|
+
const utils_1 = require("@postxl/utils");
|
|
41
|
+
const Path = __importStar(require("./path"));
|
|
42
|
+
/**
|
|
43
|
+
* Template delimiter configuration.
|
|
44
|
+
* Uses ERB-style `<%...%>` syntax which doesn't interfere with:
|
|
45
|
+
* - TypeScript/JavaScript template literals (`${...}`)
|
|
46
|
+
* - JSX/TSX syntax
|
|
47
|
+
* - Markdown syntax
|
|
48
|
+
* - Bash variables (`$VAR`, `${VAR}`)
|
|
49
|
+
* - Bash conditionals (`[[ ... ]]`)
|
|
50
|
+
*/
|
|
51
|
+
const TEMPLATE_REGEX = /<%\s*([^%]+?)\s*%>/g;
|
|
52
|
+
/**
|
|
53
|
+
* Gets a nested property value from an object using dot notation.
|
|
54
|
+
*
|
|
55
|
+
* @example
|
|
56
|
+
* getNestedValue({ schema: { slug: 'demo' } }, 'schema.slug') // returns 'demo'
|
|
57
|
+
* getNestedValue({ name: 'test' }, 'name') // returns 'test'
|
|
58
|
+
*/
|
|
59
|
+
function getNestedValue(obj, path) {
|
|
60
|
+
const keys = path.split('.');
|
|
61
|
+
let current = obj;
|
|
62
|
+
for (const key of keys) {
|
|
63
|
+
if (current === null || current === undefined) {
|
|
64
|
+
return undefined;
|
|
65
|
+
}
|
|
66
|
+
if (typeof current !== 'object') {
|
|
67
|
+
return undefined;
|
|
68
|
+
}
|
|
69
|
+
current = current[key];
|
|
70
|
+
}
|
|
71
|
+
return current;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Error thrown when a template variable is not found in the context.
|
|
75
|
+
*/
|
|
76
|
+
class TemplateVariableError extends Error {
|
|
77
|
+
variablePath;
|
|
78
|
+
availableKeys;
|
|
79
|
+
constructor(variablePath, availableKeys) {
|
|
80
|
+
super(`Template variable "<%${variablePath}%>" not found in context. Available top-level keys: ${availableKeys.join(', ')}`);
|
|
81
|
+
this.variablePath = variablePath;
|
|
82
|
+
this.availableKeys = availableKeys;
|
|
83
|
+
this.name = 'TemplateVariableError';
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
exports.TemplateVariableError = TemplateVariableError;
|
|
87
|
+
/**
|
|
88
|
+
* Renders a template string by replacing `<%expression%>` placeholders with values from the context.
|
|
89
|
+
*
|
|
90
|
+
* The expression supports dot notation for accessing nested properties.
|
|
91
|
+
*
|
|
92
|
+
* @example
|
|
93
|
+
* renderTemplate('Hello <%name%>!', { name: 'World' })
|
|
94
|
+
* // returns 'Hello World!'
|
|
95
|
+
*
|
|
96
|
+
* @example
|
|
97
|
+
* renderTemplate('Project: <%schema.slug%>', { schema: { slug: 'demo' } })
|
|
98
|
+
* // returns 'Project: demo'
|
|
99
|
+
*
|
|
100
|
+
* @param template - The template string containing `<%expression%>` placeholders
|
|
101
|
+
* @param context - The context object containing values to substitute
|
|
102
|
+
* @param options - Optional configuration
|
|
103
|
+
* @returns The rendered string with all placeholders replaced
|
|
104
|
+
* @throws {TemplateVariableError} If a variable is not found and allowUndefined is false
|
|
105
|
+
*/
|
|
106
|
+
function renderTemplate(template, context, options = {}) {
|
|
107
|
+
const { allowUndefined = false } = options;
|
|
108
|
+
return template.replaceAll(TEMPLATE_REGEX, (_match, expression) => {
|
|
109
|
+
const trimmedExpr = expression.trim();
|
|
110
|
+
const value = getNestedValue(context, trimmedExpr);
|
|
111
|
+
if (value === undefined) {
|
|
112
|
+
if (allowUndefined) {
|
|
113
|
+
return '';
|
|
114
|
+
}
|
|
115
|
+
throw new TemplateVariableError(trimmedExpr, Object.keys(context));
|
|
116
|
+
}
|
|
117
|
+
// Convert value to string
|
|
118
|
+
if (typeof value === 'string') {
|
|
119
|
+
return value;
|
|
120
|
+
}
|
|
121
|
+
if (typeof value === 'number' || typeof value === 'boolean') {
|
|
122
|
+
return String(value);
|
|
123
|
+
}
|
|
124
|
+
// For objects/arrays, stringify them
|
|
125
|
+
return JSON.stringify(value);
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Reads a template file from disk and renders it with the given context.
|
|
130
|
+
*
|
|
131
|
+
* This is a convenience function for loading static template files and
|
|
132
|
+
* replacing placeholders in a single operation.
|
|
133
|
+
*
|
|
134
|
+
* @example
|
|
135
|
+
* // In a generator:
|
|
136
|
+
* const content = await generateFromTemplate({
|
|
137
|
+
* file: path.resolve(__dirname, './template/scripts/docker.sh'),
|
|
138
|
+
* context: { schema: context.schema }
|
|
139
|
+
* })
|
|
140
|
+
* vfs.write('/scripts/docker.sh', content)
|
|
141
|
+
*
|
|
142
|
+
* @param params.file - Absolute path to the template file on disk
|
|
143
|
+
* @param params.context - The context object containing values to substitute
|
|
144
|
+
* @param params.options - Optional render options
|
|
145
|
+
* @returns The rendered template content
|
|
146
|
+
*/
|
|
147
|
+
async function generateFromTemplate({ file, context, options, }) {
|
|
148
|
+
const normalizedPath = Path.normalize(file);
|
|
149
|
+
const result = await utils_1.Result.fromPromise(() => fs.readFile(normalizedPath, { encoding: 'utf-8' }), (error) => ({
|
|
150
|
+
type: 'ReadTemplateError',
|
|
151
|
+
message: `Error reading template file "${file}"`,
|
|
152
|
+
error: error,
|
|
153
|
+
}));
|
|
154
|
+
if (result.isErr()) {
|
|
155
|
+
return result;
|
|
156
|
+
}
|
|
157
|
+
const rendered = renderTemplate(result.unwrap(), context, options);
|
|
158
|
+
return utils_1.Result.ok(rendered);
|
|
159
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { FileContent } from './fs-utils';
|
|
2
|
+
import * as Path from './path';
|
|
3
|
+
/**
|
|
4
|
+
* Options for configuring the VirtualFileSystem.
|
|
5
|
+
*/
|
|
6
|
+
export type VFSOptions = {
|
|
7
|
+
/**
|
|
8
|
+
* Optional glob pattern to filter files.
|
|
9
|
+
* Examples: `**\/*.ts` or `backend/libs/**`
|
|
10
|
+
* When specified, only files matching this pattern will be stored in the VFS.
|
|
11
|
+
*/
|
|
12
|
+
filePattern?: string;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* The virtual file system (VFS) represents a file system that can be manipulated in memory.
|
|
16
|
+
*
|
|
17
|
+
* It exposes the following methods:
|
|
18
|
+
* - `write(path, content)`: Writes the given content to the given path
|
|
19
|
+
* - `read(path)`: Reads the content of the given path
|
|
20
|
+
* - `insertFromVfs(path, vfs)`: Inserts the content of the given VFS into the current VFS
|
|
21
|
+
* - `insertFromDisk(path, disk)`: Inserts the content of the given disk into the current VFS
|
|
22
|
+
* - `transform(fn, filter?)`: Transforms the content of all files in the VFS using the given function
|
|
23
|
+
*
|
|
24
|
+
* ## Implementation details
|
|
25
|
+
*
|
|
26
|
+
* All paths are relative to the root of the VFS - and are normalized to use the POSIX path separator.
|
|
27
|
+
* For this we use the `path` utils (path.normalize, path.join, path.parse). All paths are represented
|
|
28
|
+
* as branded strings (`Path.PosixPath`).
|
|
29
|
+
*
|
|
30
|
+
* The file content can either be a (UTF8) string or a Buffer.
|
|
31
|
+
*
|
|
32
|
+
* ## File Pattern Filtering
|
|
33
|
+
*
|
|
34
|
+
* The VFS can be configured with an optional file pattern filter during construction.
|
|
35
|
+
* When a pattern is set, only files matching the pattern will be stored in the VFS.
|
|
36
|
+
* This optimization ensures that linting, formatting, and lock file updates only affect
|
|
37
|
+
* the filtered files, significantly improving performance.
|
|
38
|
+
*/
|
|
39
|
+
export declare class VirtualFileSystem {
|
|
40
|
+
#private;
|
|
41
|
+
/**
|
|
42
|
+
* Constructs a new VirtualFileSystem.
|
|
43
|
+
*
|
|
44
|
+
* @param options - Optional configuration options for the VFS.
|
|
45
|
+
*/
|
|
46
|
+
constructor(options?: VFSOptions);
|
|
47
|
+
/**
|
|
48
|
+
* Returns all file names in the VFS.
|
|
49
|
+
*/
|
|
50
|
+
get fileNames(): Path.PosixPath[];
|
|
51
|
+
/**
|
|
52
|
+
* Returns all files in the VFS.
|
|
53
|
+
*/
|
|
54
|
+
get files(): Map<Path.PosixPath | string, FileContent>;
|
|
55
|
+
/**
|
|
56
|
+
* Returns the file pattern filter if one is set.
|
|
57
|
+
*/
|
|
58
|
+
get filePattern(): string | undefined;
|
|
59
|
+
/**
|
|
60
|
+
* Checks if a file path matches the VFS file pattern (if one is set).
|
|
61
|
+
* Returns true if no pattern is set, or if the path matches the pattern.
|
|
62
|
+
*/
|
|
63
|
+
matchesPattern(filePath: Path.PosixPath | string): boolean;
|
|
64
|
+
/**
|
|
65
|
+
* Writes the given content to the specified path.
|
|
66
|
+
* If a file pattern is set, only files matching the pattern will be stored.
|
|
67
|
+
*/
|
|
68
|
+
write(path: string, content: FileContent): void;
|
|
69
|
+
/**
|
|
70
|
+
* Reads the content of a file from the specified path.
|
|
71
|
+
*/
|
|
72
|
+
get(path: string): FileContent | undefined;
|
|
73
|
+
/**
|
|
74
|
+
* Reads the content of the specified folder.
|
|
75
|
+
*/
|
|
76
|
+
getFolder(path: string): VirtualFileSystem | undefined;
|
|
77
|
+
/**
|
|
78
|
+
* Inserts the content of another VFS into this VFS at the specified path.
|
|
79
|
+
*/
|
|
80
|
+
insertFromVfs({ vfs, targetPath }: {
|
|
81
|
+
vfs: VirtualFileSystem;
|
|
82
|
+
targetPath?: string;
|
|
83
|
+
}): void;
|
|
84
|
+
/**
|
|
85
|
+
* Loads the content from the folder on disk into this VFS at the specified path.
|
|
86
|
+
*/
|
|
87
|
+
loadFolder({ diskPath, targetPath, recursive, filter, }: {
|
|
88
|
+
diskPath: string;
|
|
89
|
+
targetPath?: string;
|
|
90
|
+
filter?: (path: Path.PosixPath) => boolean;
|
|
91
|
+
recursive?: boolean;
|
|
92
|
+
}): Promise<void>;
|
|
93
|
+
/**
|
|
94
|
+
* Loads the content from the file on disk into this VFS at the specified path.
|
|
95
|
+
*/
|
|
96
|
+
loadFile({ diskPath, targetPath }: {
|
|
97
|
+
diskPath: string;
|
|
98
|
+
targetPath?: string;
|
|
99
|
+
}): Promise<void>;
|
|
100
|
+
/**
|
|
101
|
+
* Transforms the content of all files using the provided function.
|
|
102
|
+
* An optional filter function can be provided to select specific files.
|
|
103
|
+
*/
|
|
104
|
+
transform(fn: (params: {
|
|
105
|
+
content: FileContent;
|
|
106
|
+
path: Path.PosixPath;
|
|
107
|
+
}) => Promise<FileContent>, { filter, onError, }?: {
|
|
108
|
+
filter?: (path: Path.PosixPath) => boolean;
|
|
109
|
+
onError?: (data: {
|
|
110
|
+
path: Path.PosixPath;
|
|
111
|
+
content: FileContent;
|
|
112
|
+
error: Error;
|
|
113
|
+
}) => void;
|
|
114
|
+
}): Promise<void>;
|
|
115
|
+
}
|