sandstone-cli 0.5.4 → 0.6.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/README.md CHANGED
@@ -19,7 +19,7 @@ $ npm install -g sandstone-cli
19
19
  $ sand COMMAND
20
20
  running command...
21
21
  $ sand (-v|--version|version)
22
- sandstone-cli/0.5.4 linux-x64 node-v16.7.0
22
+ sandstone-cli/0.6.1 linux-x64 node-v16.19.0
23
23
  $ sand --help [COMMAND]
24
24
  USAGE
25
25
  $ sand COMMAND
@@ -36,7 +36,7 @@ USAGE
36
36
 
37
37
  ## `sand build PATH CONFIG-PATH`
38
38
 
39
- Build the datapack. ⛏
39
+ Build the packs. ⛏
40
40
 
41
41
  ```
42
42
  USAGE
@@ -47,7 +47,7 @@ ARGUMENTS
47
47
  CONFIG-PATH [default: .] Path of the sandstone.config.ts folder.
48
48
 
49
49
  OPTIONS
50
- -d, --dry Do not save the datapack. Mostly useful with `verbose`.
50
+ -d, --dry Do not save the pack. Mostly useful with `verbose`.
51
51
  -h, --help show CLI help
52
52
  -p, --production Runs Sandstone in production mode. This sets process.env.SANDSTONE_ENV to "production".
53
53
  -v, --verbose Log all resulting resources: functions, advancements...
@@ -55,23 +55,23 @@ OPTIONS
55
55
  --autoReload=port Automatically reload your data pack in-game. Requires to open the world to LAN with
56
56
  cheats enabled, and to specify the port.
57
57
 
58
+ --clientPath=clientPath Path of the client folder. Override the value specified in the configuration file.
59
+
58
60
  --description=description Description of the data pack. Override the value specified in the configuration file.
59
61
 
60
62
  --formatVersion=formatVersion Pack format version. Override the value specified in the configuration file.
61
63
 
62
64
  --fullTrace Show the full stack trace on errors.
63
65
 
64
- --minecraftPath=minecraftPath Path of the .minecraft folder. Override the value specified in the configuration file.
65
-
66
66
  --name=name Name of the data pack. Override the value specified in the configuration file.
67
67
 
68
68
  --namespace=namespace The default namespace. Override the value specified in the configuration file.
69
69
 
70
- --path=path The path to save the data pack at. Override the value specified in the configuration
70
+ --root Save the data pack & resource pack in the .minecraft/datapacks &
71
+ .minecraft/resource_packs folders. Override the value specified in the configuration
71
72
  file.
72
73
 
73
- --root Save the data pack in the `.minecraft/datapacks` folder. Override the value specified
74
- in the configuration file.
74
+ --serverPath=serverPath Path of the server folder. Override the value specified in the configuration file.
75
75
 
76
76
  --strictErrors Stop data pack compilation on type errors.
77
77
 
@@ -84,7 +84,7 @@ EXAMPLES
84
84
  $ sand build --verbose --dry
85
85
  ```
86
86
 
87
- _See code: [src/commands/build.ts](https://github.com/TheMrZZ/sandstone-cli/blob/v0.5.4/src/commands/build.ts)_
87
+ _See code: [src/commands/build.ts](https://github.com/TheMrZZ/sandstone-cli/blob/v0.6.1/src/commands/build.ts)_
88
88
 
89
89
  ## `sand create PROJECT-NAME`
90
90
 
@@ -98,26 +98,27 @@ ARGUMENTS
98
98
  PROJECT-NAME Name of the project folder. This is not the name of the data pack.
99
99
 
100
100
  OPTIONS
101
- -d, --datapack-name=datapack-name The name of the data pack.
102
- -h, --help show CLI help
103
- -n, --namespace=namespace The default namespace that will be used.
104
- -p, --custom-path=custom-path The path to save the data pack at. Not compatible with --save-root and --world.
101
+ -c, --client-path=client-path The client path to write packs at.
102
+ -d, --pack-name=pack-name The name of the data pack.
103
+ -h, --help show CLI help
104
+ -n, --namespace=namespace The default namespace that will be used.
105
105
 
106
- -r, --save-root Save the data pack in the .minecraft/datapacks folder. Not compatible with --world
107
- and --custom-path.
106
+ -r, --save-root Save the data pack & resource pack in the .minecraft/datapacks &
107
+ .minecraft/resource_packs folders. Not compatible with --world.
108
108
 
109
- -w, --world=world The world to save the data pack in. Not compatible with --save-root and
110
- --custom-path.
109
+ -s, --server-path=server-path The server path to write the server-side packs at. Not compatible with --world.
111
110
 
112
- --npm Use npm.
111
+ -w, --world=world The world to save the packs in. Not compatible with --save-root or --server
113
112
 
114
- --yarn Use yarn instead of npm.
113
+ --npm Use npm.
114
+
115
+ --yarn Use yarn instead of npm.
115
116
 
116
117
  EXAMPLE
117
- $ sand create my-datapack
118
+ $ sand create my-pack
118
119
  ```
119
120
 
120
- _See code: [src/commands/create.ts](https://github.com/TheMrZZ/sandstone-cli/blob/v0.5.4/src/commands/create.ts)_
121
+ _See code: [src/commands/create.ts](https://github.com/TheMrZZ/sandstone-cli/blob/v0.6.1/src/commands/create.ts)_
121
122
 
122
123
  ## `sand help [COMMAND]`
123
124
 
@@ -159,11 +160,11 @@ EXAMPLES
159
160
  $ sand update --cli --sandstone --skip
160
161
  ```
161
162
 
162
- _See code: [src/commands/update.ts](https://github.com/TheMrZZ/sandstone-cli/blob/v0.5.4/src/commands/update.ts)_
163
+ _See code: [src/commands/update.ts](https://github.com/TheMrZZ/sandstone-cli/blob/v0.6.1/src/commands/update.ts)_
163
164
 
164
165
  ## `sand watch PATH CONFIG-PATH`
165
166
 
166
- Build the datapack, and rebuild it on file change. ⛏
167
+ Build the packs, and rebuild them on file change. ⛏
167
168
 
168
169
  ```
169
170
  USAGE
@@ -174,7 +175,7 @@ ARGUMENTS
174
175
  CONFIG-PATH [default: .] Path of the sandstone.config.ts folder.
175
176
 
176
177
  OPTIONS
177
- -d, --dry Do not save the datapack. Mostly useful with `verbose`.
178
+ -d, --dry Do not save the pack. Mostly useful with `verbose`.
178
179
  -h, --help show CLI help
179
180
  -p, --production Runs Sandstone in production mode. This sets process.env.SANDSTONE_ENV to "production".
180
181
  -v, --verbose Log all resulting resources: functions, advancements...
@@ -182,23 +183,23 @@ OPTIONS
182
183
  --autoReload=port Automatically reload your data pack in-game. Requires to open the world to LAN with
183
184
  cheats enabled, and to specify the port.
184
185
 
186
+ --clientPath=clientPath Path of the client folder. Override the value specified in the configuration file.
187
+
185
188
  --description=description Description of the data pack. Override the value specified in the configuration file.
186
189
 
187
190
  --formatVersion=formatVersion Pack format version. Override the value specified in the configuration file.
188
191
 
189
192
  --fullTrace Show the full stack trace on errors.
190
193
 
191
- --minecraftPath=minecraftPath Path of the .minecraft folder. Override the value specified in the configuration file.
192
-
193
194
  --name=name Name of the data pack. Override the value specified in the configuration file.
194
195
 
195
196
  --namespace=namespace The default namespace. Override the value specified in the configuration file.
196
197
 
197
- --path=path The path to save the data pack at. Override the value specified in the configuration
198
+ --root Save the data pack & resource pack in the .minecraft/datapacks &
199
+ .minecraft/resource_packs folders. Override the value specified in the configuration
198
200
  file.
199
201
 
200
- --root Save the data pack in the `.minecraft/datapacks` folder. Override the value specified
201
- in the configuration file.
202
+ --serverPath=serverPath Path of the server folder. Override the value specified in the configuration file.
202
203
 
203
204
  --strictErrors Stop data pack compilation on type errors.
204
205
 
@@ -211,5 +212,5 @@ EXAMPLES
211
212
  $ sand watch --verbose --dry
212
213
  ```
213
214
 
214
- _See code: [src/commands/watch.ts](https://github.com/TheMrZZ/sandstone-cli/blob/v0.5.4/src/commands/watch.ts)_
215
+ _See code: [src/commands/watch.ts](https://github.com/TheMrZZ/sandstone-cli/blob/v0.6.1/src/commands/watch.ts)_
215
216
  <!-- commandsstop -->
package/bin/run CHANGED
File without changes
@@ -1,9 +1,10 @@
1
- import { ProjectFolders } from './utils';
1
+ import { ProjectFolders } from '../utils';
2
2
  export declare type BuildOptions = {
3
3
  world?: string;
4
4
  root?: boolean;
5
- path?: string;
6
- minecraftPath?: string;
5
+ clientPath?: string;
6
+ serverPath?: string;
7
+ ssh?: string;
7
8
  namespace?: string;
8
9
  name?: string;
9
10
  description?: string;
@@ -19,5 +20,7 @@ export declare type BuildOptions = {
19
20
  * @param options The options to build the project with.
20
21
  *
21
22
  * @param projectFolder The folder of the project. It needs a sandstone.config.ts, and it or one of its parent needs a package.json.
23
+ *
24
+ * @param changedFiles The files that changed since the last build.
22
25
  */
23
- export declare function buildProject(options: BuildOptions, folders: ProjectFolders): Promise<void>;
26
+ export declare function buildProject(options: BuildOptions, folders: ProjectFolders, resourceTypes: string[], changedFiles?: string[]): Promise<void>;
@@ -0,0 +1,461 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
5
+ }) : (function(o, m, k, k2) {
6
+ if (k2 === undefined) k2 = k;
7
+ o[k2] = m[k];
8
+ }));
9
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
10
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
11
+ }) : function(o, v) {
12
+ o["default"] = v;
13
+ });
14
+ var __importStar = (this && this.__importStar) || function (mod) {
15
+ if (mod && mod.__esModule) return mod;
16
+ var result = {};
17
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
18
+ __setModuleDefault(result, mod);
19
+ return result;
20
+ };
21
+ var __importDefault = (this && this.__importDefault) || function (mod) {
22
+ return (mod && mod.__esModule) ? mod : { "default": mod };
23
+ };
24
+ Object.defineProperty(exports, "__esModule", { value: true });
25
+ exports.buildProject = void 0;
26
+ const path_1 = __importDefault(require("path"));
27
+ const os = __importStar(require("os"));
28
+ const crypto_1 = __importDefault(require("crypto"));
29
+ const util_1 = require("util");
30
+ const fs_extra_1 = __importDefault(require("fs-extra"));
31
+ const pretty_error_1 = __importDefault(require("pretty-error"));
32
+ const klaw_1 = __importDefault(require("klaw"));
33
+ const madge_1 = __importDefault(require("madge"));
34
+ const graph_1 = require("./graph");
35
+ const chalk_1 = __importDefault(require("chalk"));
36
+ const adm_zip_1 = __importDefault(require("adm-zip"));
37
+ const pe = new pretty_error_1.default();
38
+ // Return the hash of a string
39
+ function hash(stringToHash) {
40
+ return crypto_1.default.createHash('md5').update(stringToHash).digest('hex');
41
+ }
42
+ // Recursively create a directory, without failing if it already exists
43
+ async function mkDir(dirPath) {
44
+ try {
45
+ await new Promise((resolve, reject) => {
46
+ fs_extra_1.default.mkdir(dirPath, { recursive: true }, (err) => {
47
+ if (err)
48
+ reject(err);
49
+ resolve();
50
+ });
51
+ });
52
+ }
53
+ catch (error) {
54
+ // Directory already exists
55
+ }
56
+ }
57
+ let cache = {};
58
+ const dependenciesCache = new graph_1.DependencyGraph({});
59
+ const fileResources = new Map();
60
+ function getNewModules(dependenciesGraph, rawFiles, projectFolder) {
61
+ const rawFilesPath = rawFiles.map(({ path }) => path);
62
+ // Get only the new modules
63
+ const newModules = [...dependenciesGraph.nodes.values()].filter((node) => rawFilesPath.includes(path_1.default.join(projectFolder, node.name)));
64
+ // Get their dependants, as a set to avoid duplicates
65
+ const newModulesDependencies = new Set(newModules.flatMap((node) => [...node.getDependsOn({ recursive: true, includeSelf: true })]));
66
+ // Sort them by number of dependencies, and return them
67
+ return [...newModulesDependencies].sort((a, b) => a.getDependencies({ recursive: true }).size - b.getDependencies({ recursive: true }).size);
68
+ }
69
+ /**
70
+ * Returns a set of all values present in set1 and not present in set2.
71
+ */
72
+ function diffSet(set1, set2) {
73
+ return [...set1].filter((element) => !set2.has(element));
74
+ }
75
+ /**
76
+ * Returns a map of all key/value present in map1 and not present in map2.
77
+ */
78
+ function diffMap(map1, map2) {
79
+ return new Map([...map1.entries()].filter(([key, value]) => !map2.has(key)));
80
+ }
81
+ function diffResources(tree1, tree2) {
82
+ return tree1.diff(tree2);
83
+ }
84
+ /**
85
+ *
86
+ * @param worldName The name of the world
87
+ * @param minecraftPath The optional location of the .minecraft folder.
88
+ * If left unspecified, the .minecraft will be found automatically.
89
+ */
90
+ async function getClientWorldPath(worldName, minecraftPath = undefined) {
91
+ let mcPath;
92
+ if (minecraftPath) {
93
+ mcPath = minecraftPath;
94
+ }
95
+ else {
96
+ mcPath = await getClientPath();
97
+ }
98
+ const savesPath = path_1.default.join(mcPath, 'saves');
99
+ const worldPath = path_1.default.join(savesPath, worldName);
100
+ if (!fs_extra_1.default.existsSync(worldPath)) {
101
+ const existingWorlds = (await fs_extra_1.default.readdir(savesPath, { withFileTypes: true })).filter((f) => f.isDirectory).map((f) => f.name);
102
+ throw new Error(`Unable to locate the "${worldPath}" folder. Word ${worldName} does not exists. List of existing worlds: ${JSON.stringify(existingWorlds, null, 2)}`);
103
+ }
104
+ return worldPath;
105
+ }
106
+ /**
107
+ * Get the .minecraft path
108
+ */
109
+ async function getClientPath() {
110
+ function getMCPath() {
111
+ switch (os.platform()) {
112
+ case 'win32':
113
+ return path_1.default.join(os.homedir(), 'AppData/Roaming/.minecraft');
114
+ case 'darwin':
115
+ return path_1.default.join(os.homedir(), 'Library/Application Support/minecraft');
116
+ case 'linux':
117
+ default:
118
+ return path_1.default.join(os.homedir(), '.minecraft');
119
+ }
120
+ }
121
+ const mcPath = getMCPath();
122
+ if (!await fs_extra_1.default.stat(mcPath)) {
123
+ throw new Error('Unable to locate the .minecraft folder. Please specify it manually.');
124
+ }
125
+ return mcPath;
126
+ }
127
+ /**
128
+ * Build the project, but might throw errors.
129
+ *
130
+ * @param cliOptions The options to build the project with.
131
+ *
132
+ * @param projectFolder The folder of the project. It needs a sandstone.config.ts, and it or one of its parent needs a package.json.
133
+ */
134
+ async function _buildProject(cliOptions, { absProjectFolder, rootFolder, sandstoneConfigFolder }, resourceTypes, changedFiles) {
135
+ var _a, _b, _c, _d, _e;
136
+ const sandstoneLocation = path_1.default.join(rootFolder, 'node_modules/sandstone/');
137
+ // First, read sandstone.config.ts to get all properties
138
+ const sandstoneConfig = require(path_1.default.join(sandstoneConfigFolder, 'sandstone.config.ts')).default;
139
+ const { saveOptions, scripts } = sandstoneConfig;
140
+ const outputFolder = path_1.default.join(rootFolder, '.sandstone', 'output');
141
+ /// OPTIONS ///
142
+ const clientPath = !cliOptions.production ? (cliOptions.clientPath || saveOptions.clientPath || await getClientPath()) : undefined;
143
+ const server = !cliOptions.production && (cliOptions.serverPath || saveOptions.serverPath || cliOptions.ssh || saveOptions.ssh) ? await (async () => {
144
+ if (cliOptions.ssh || saveOptions.ssh) {
145
+ const sshOptions = JSON.stringify(await fs_extra_1.default.readFile(cliOptions.ssh || saveOptions.ssh, 'utf8'));
146
+ // TODO: implement SFTP
147
+ return {
148
+ readFile: async (relativePath, encoding = 'utf8') => { },
149
+ writeFile: async (relativePath, contents) => { },
150
+ remove: async (relativePath) => { },
151
+ };
152
+ }
153
+ const serverPath = cliOptions.serverPath || saveOptions.serverPath;
154
+ return {
155
+ readFile: async (relativePath, encoding = 'utf8') => await fs_extra_1.default.readFile(path_1.default.join(serverPath, relativePath), encoding),
156
+ writeFile: async (relativePath, contents) => {
157
+ if (contents === undefined) {
158
+ await fs_extra_1.default.unlink(path_1.default.join(serverPath, relativePath));
159
+ }
160
+ else {
161
+ await fs_extra_1.default.writeFile(path_1.default.join(serverPath, relativePath), contents);
162
+ }
163
+ },
164
+ remove: async (relativePath) => await fs_extra_1.default.remove(path_1.default.join(serverPath, relativePath))
165
+ };
166
+ })() : undefined;
167
+ let worldName = cliOptions.world || saveOptions.world;
168
+ // Make sure the world exists
169
+ if (worldName && !cliOptions.production) {
170
+ await getClientWorldPath(worldName, clientPath);
171
+ }
172
+ const root = cliOptions.root !== undefined ? cliOptions.root : saveOptions.root;
173
+ const packName = (_a = cliOptions.name) !== null && _a !== void 0 ? _a : sandstoneConfig.name;
174
+ if (worldName && root) {
175
+ throw new Error(`Expected only 'world' or 'root'. Got both.`);
176
+ }
177
+ // Important /!\: The below if statements, which set environment variables, must run before importing any Sandstone file.
178
+ // Set the pack ID environment variable
179
+ // Set production/development mode
180
+ if (cliOptions.production) {
181
+ process.env.SANDSTONE_ENV = 'production';
182
+ }
183
+ else {
184
+ process.env.SANDSTONE_ENV = 'development';
185
+ }
186
+ if (sandstoneConfig.packUid) {
187
+ process.env.PACK_UID = sandstoneConfig.packUid;
188
+ }
189
+ // Set the namespace
190
+ const namespace = cliOptions.namespace || sandstoneConfig.namespace;
191
+ if (namespace) {
192
+ process.env.NAMESPACE = namespace;
193
+ }
194
+ const { onConflict } = sandstoneConfig;
195
+ if (onConflict) {
196
+ if (onConflict.default) {
197
+ process.env[`GENERAL_CONFLICT_STRATEGY`] = onConflict.default;
198
+ }
199
+ for (const resource of resourceTypes) {
200
+ if (onConflict[resource]) {
201
+ process.env[`${resource.toUpperCase()}_CONFLICT_STRATEGY`] = onConflict[resource];
202
+ }
203
+ }
204
+ }
205
+ // JSON indentation
206
+ process.env.INDENTATION = saveOptions.indentation;
207
+ // Pack mcmeta
208
+ process.env.PACK_OPTIONS = JSON.stringify(sandstoneConfig.packs);
209
+ // Configure error display
210
+ if (!cliOptions.fullTrace) {
211
+ pe.skipNodeFiles();
212
+ }
213
+ /// IMPORTING USER CODE ///
214
+ // The configuration is ready.
215
+ // Now, let's run the beforeAll script
216
+ await ((_b = scripts === null || scripts === void 0 ? void 0 : scripts.beforeAll) === null || _b === void 0 ? void 0 : _b.call(scripts));
217
+ // Finally, let's import all .ts & .js files under ./src.
218
+ let error = false;
219
+ // Get the list of all files
220
+ const rawFiles = [];
221
+ for await (const file of (0, klaw_1.default)(absProjectFolder)) {
222
+ rawFiles.push(file);
223
+ }
224
+ const changedFilesPaths = changedFiles === null || changedFiles === void 0 ? void 0 : changedFiles.map(file => ({ path: file }));
225
+ /**
226
+ * 1. Update dependency graphs
227
+ * 2. Delete all cache & resources for files dependent from the changed files
228
+ * 3. Import all changed files, & their dependents
229
+ * 4. Save only newly created resources
230
+ */
231
+ const graph = await (0, madge_1.default)(rawFiles.map(f => f.path).filter(f => !f.endsWith('.json')), {
232
+ fileExtensions: ['.ts', '.cts', '.mts', '.tsx', '.js', '.jsx', '.cjs', '.mjs', '.json'],
233
+ includeNpm: false,
234
+ baseDir: absProjectFolder,
235
+ detectiveOptions: {
236
+ es6: {
237
+ skipTypeImports: true,
238
+ },
239
+ ts: {
240
+ skipTypeImports: true,
241
+ },
242
+ },
243
+ });
244
+ // This dependencies graph is only partial.
245
+ const dependenciesGraph = new graph_1.DependencyGraph(graph.obj());
246
+ // Update the global dependency graph by merging it with the new one.
247
+ dependenciesCache.merge(dependenciesGraph);
248
+ // Transform resolved dependents into a flat list of files, and sort them by their number of dependencies
249
+ const newModules = getNewModules(dependenciesCache, changedFilesPaths !== null && changedFilesPaths !== void 0 ? changedFilesPaths : rawFiles, absProjectFolder);
250
+ const { sandstonePack } = require(sandstoneLocation);
251
+ // If files changed, we need to clean the cache & delete the related resources
252
+ if (changedFiles) {
253
+ for (const node of newModules) {
254
+ // For each changed file, we need to reset the require cache
255
+ delete require.cache[path_1.default.join(absProjectFolder, node.name)];
256
+ // Then we need to delete all resources the file created
257
+ const oldResources = fileResources.get(node.name);
258
+ if (oldResources) {
259
+ for (const resource of oldResources.resources) {
260
+ sandstonePack.core.deleteResource(resource.path, resource.resourceType);
261
+ }
262
+ }
263
+ }
264
+ }
265
+ // Now, let's build the file & its dependents. First files to be built are the ones with less dependencies.
266
+ for (const node of newModules) {
267
+ const modulePath = path_1.default.join(absProjectFolder, node.name);
268
+ const currentResources = {
269
+ resources: new Set([...sandstonePack.core.resourceNodes]),
270
+ objectives: new Set([...sandstonePack.objectives.entries()])
271
+ };
272
+ // We have a module, let's require it!
273
+ const filePath = path_1.default.resolve(modulePath);
274
+ try {
275
+ // Sometimes, a file might not exist because it has been deleted.
276
+ if (await fs_extra_1.default.pathExists(filePath)) {
277
+ require(filePath);
278
+ }
279
+ }
280
+ catch (e) {
281
+ logError(e, node.name);
282
+ error = true;
283
+ }
284
+ // Now, find the resources that were added by this file & store them.
285
+ // This will be used if those files are changed later.
286
+ const newResources = {
287
+ resources: diffResources(sandstonePack.core.resourceNodes, currentResources.resources),
288
+ objectives: diffSet(sandstonePack.objectives, currentResources.objectives),
289
+ };
290
+ fileResources.set(node.name, newResources);
291
+ }
292
+ if (error) {
293
+ return;
294
+ }
295
+ /// SAVING RESULTS ///
296
+ // Setup the cache if it doesn't exist.
297
+ // This cache is here to avoid writing files on disk when they did not change.
298
+ const newCache = {};
299
+ if (cache === undefined) {
300
+ cache = {};
301
+ }
302
+ // Save the pack
303
+ // Run the beforeSave script (TODO: This is where sandstone-server will remove restart env vars)
304
+ await ((_c = scripts === null || scripts === void 0 ? void 0 : scripts.beforeSave) === null || _c === void 0 ? void 0 : _c.call(scripts));
305
+ const packTypes = await sandstonePack.save({
306
+ // Additional parameters
307
+ dryRun: cliOptions.dry,
308
+ verbose: cliOptions.verbose,
309
+ fileHandler: (_d = saveOptions.customFileHandler) !== null && _d !== void 0 ? _d : (async ({ relativePath, content, contentSummary }) => {
310
+ // We hash the relative path alongside the content to ensure unique hash.
311
+ const hashValue = hash(contentSummary + relativePath);
312
+ // Add to new cache.
313
+ newCache[relativePath] = hashValue;
314
+ if (cache[relativePath] === hashValue) {
315
+ // Already in cache - skip
316
+ return;
317
+ }
318
+ // Not in cache: write to disk
319
+ const realPath = path_1.default.join(rootFolder, relativePath);
320
+ await mkDir(path_1.default.dirname(realPath));
321
+ return await fs_extra_1.default.writeFile(realPath, content);
322
+ })
323
+ });
324
+ async function archiveOutput(packType, outputPath) {
325
+ const archive = new adm_zip_1.default();
326
+ await archive.addLocalFolderPromise(outputPath, {});
327
+ await archive.writeZipPromise(`${outputPath}.zip`, { overwrite: true });
328
+ }
329
+ // TODO: implement linking to make the cache more useful when not archiving.
330
+ if (!cliOptions.production) {
331
+ for (const packType of packTypes) {
332
+ const outputPath = path_1.default.join(rootFolder, '.sandstone/output/archives', `${packName}_${packType.type}`);
333
+ if (packType.handleOutput) {
334
+ await packType.handleOutput('output', async (relativePath, encoding = 'utf8') => await fs_extra_1.default.readFile(path_1.default.join(outputPath, relativePath), encoding), async (relativePath, contents) => {
335
+ if (contents === undefined) {
336
+ await fs_extra_1.default.unlink(path_1.default.join(outputPath, relativePath));
337
+ }
338
+ else {
339
+ await fs_extra_1.default.writeFile(path_1.default.join(outputPath, relativePath), contents);
340
+ }
341
+ });
342
+ }
343
+ if (packType.archiveOutput) {
344
+ archiveOutput(packType, outputPath);
345
+ }
346
+ // Handle client
347
+ if (!(server && packType.networkSides === 'server')) {
348
+ let fullClientPath;
349
+ if (worldName) {
350
+ fullClientPath = path_1.default.join(clientPath, packType.clientPath);
351
+ try {
352
+ fullClientPath = fullClientPath.replace('$packName$', packName);
353
+ }
354
+ catch { }
355
+ try {
356
+ fullClientPath = fullClientPath.replace('$worldName$', worldName);
357
+ }
358
+ catch { }
359
+ }
360
+ else {
361
+ fullClientPath = path_1.default.join(clientPath, packType.rootPath);
362
+ try {
363
+ fullClientPath = fullClientPath.replace('$packName$', packName);
364
+ }
365
+ catch { }
366
+ }
367
+ if (packType.archiveOutput) {
368
+ await fs_extra_1.default.copyFile(`${outputPath}.zip`, `${fullClientPath}.zip`);
369
+ }
370
+ else {
371
+ await fs_extra_1.default.remove(fullClientPath);
372
+ await fs_extra_1.default.copy(outputPath, fullClientPath);
373
+ }
374
+ if (packType.handleOutput) {
375
+ await packType.handleOutput('client', async (relativePath, encoding = 'utf8') => await fs_extra_1.default.readFile(path_1.default.join(clientPath, relativePath), encoding), async (relativePath, contents) => {
376
+ if (contents === undefined) {
377
+ fs_extra_1.default.unlink(path_1.default.join(clientPath, relativePath));
378
+ }
379
+ else {
380
+ await fs_extra_1.default.writeFile(path_1.default.join(clientPath, relativePath), contents);
381
+ }
382
+ });
383
+ }
384
+ }
385
+ // Handle server
386
+ if (server && (packType.networkSides === 'server' || packType.networkSides === 'both')) {
387
+ let serverPath = packType.serverPath;
388
+ try {
389
+ serverPath = serverPath.replace('$packName$', packName);
390
+ }
391
+ catch { }
392
+ if (packType.archiveOutput) {
393
+ await server.writeFile(await fs_extra_1.default.readFile(`${outputPath}.zip`, 'utf8'), `${serverPath}.zip`);
394
+ }
395
+ else {
396
+ server.remove(serverPath);
397
+ for await (const file of (0, klaw_1.default)(outputPath)) {
398
+ await server.writeFile(path_1.default.join(serverPath, file.path.split(outputPath)[1]), await fs_extra_1.default.readFile(file.path));
399
+ }
400
+ }
401
+ if (packType.handleOutput) {
402
+ await packType.handleOutput('server', server.readFile, server.writeFile);
403
+ }
404
+ }
405
+ }
406
+ }
407
+ else {
408
+ for (const packType of packTypes) {
409
+ const outputPath = path_1.default.join(rootFolder, '.sandstone/output/archives', `${packName}_${packType.type}`);
410
+ if (packType.handleOutput) {
411
+ await packType.handleOutput('output', async (relativePath, encoding = 'utf8') => await fs_extra_1.default.readFile(path_1.default.join(outputPath, relativePath), encoding), async (relativePath, contents) => {
412
+ if (contents === undefined) {
413
+ await fs_extra_1.default.unlink(path_1.default.join(outputPath, relativePath));
414
+ }
415
+ else {
416
+ await fs_extra_1.default.writeFile(path_1.default.join(outputPath, relativePath), contents);
417
+ }
418
+ });
419
+ }
420
+ if (packType.archiveOutput) {
421
+ archiveOutput(packType, outputPath);
422
+ }
423
+ }
424
+ }
425
+ // Delete old files that aren't cached anymore
426
+ const oldFilesNames = new Set(Object.keys(cache));
427
+ Object.keys(newCache).forEach(name => oldFilesNames.delete(name));
428
+ await Promise.allSettled([...oldFilesNames.values()].map(name => (0, util_1.promisify)(fs_extra_1.default.rm)(path_1.default.join(outputFolder, name))));
429
+ // Override old cache
430
+ cache = newCache;
431
+ // Run the afterAll script
432
+ await ((_e = scripts === null || scripts === void 0 ? void 0 : scripts.afterAll) === null || _e === void 0 ? void 0 : _e.call(scripts));
433
+ }
434
+ /**
435
+ * Build the project. Will log errors and never throw any.
436
+ *
437
+ * @param options The options to build the project with.
438
+ *
439
+ * @param projectFolder The folder of the project. It needs a sandstone.config.ts, and it or one of its parent needs a package.json.
440
+ *
441
+ * @param changedFiles The files that changed since the last build.
442
+ */
443
+ async function buildProject(options, folders, resourceTypes, changedFiles) {
444
+ try {
445
+ await _buildProject(options, folders, resourceTypes, changedFiles);
446
+ }
447
+ catch (err) {
448
+ logError(err);
449
+ }
450
+ }
451
+ exports.buildProject = buildProject;
452
+ function logError(err, file) {
453
+ if (err) {
454
+ if (file) {
455
+ console.error(' ' + chalk_1.default.bgRed.white('BuildError') + chalk_1.default.gray(':'), `While loading "${file}", the following error happened:\n`);
456
+ }
457
+ console.error(pe.render(err));
458
+ }
459
+ }
460
+ process.on('unhandledRejection', logError);
461
+ process.on('uncaughtException', logError);