whet 0.0.31 → 0.0.33

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/bin/Reflect.d.ts CHANGED
@@ -7,6 +7,19 @@ abstract interface in an untyped manner. Use with care.
7
7
  */
8
8
  export declare class Reflect {
9
9
 
10
+ /**
11
+ Returns the value of the field named `field` on object `o`.
12
+
13
+ If `o` is not an object or has no field named `field`, the result is
14
+ null.
15
+
16
+ If the field is defined as a property, its accessors are ignored. Refer
17
+ to `Reflect.getProperty` for a function supporting property accessors.
18
+
19
+ If `field` is null, the result is unspecified.
20
+ */
21
+ static field(o: any, field: string): any
22
+
10
23
  /**
11
24
  Returns the fields of structure `o`.
12
25
 
package/bin/Reflect.js CHANGED
@@ -11,6 +11,25 @@ abstract interface in an untyped manner. Use with care.
11
11
  export const Reflect = Register.global("$hxClasses")["Reflect"] =
12
12
  class Reflect {
13
13
 
14
+ /**
15
+ Returns the value of the field named `field` on object `o`.
16
+
17
+ If `o` is not an object or has no field named `field`, the result is
18
+ null.
19
+
20
+ If the field is defined as a property, its accessors are ignored. Refer
21
+ to `Reflect.getProperty` for a function supporting property accessors.
22
+
23
+ If `field` is null, the result is unspecified.
24
+ */
25
+ static field(o, field) {
26
+ try {
27
+ return o[field];
28
+ }catch (_g) {
29
+ return null;
30
+ };
31
+ }
32
+
14
33
  /**
15
34
  Returns the fields of structure `o`.
16
35
 
@@ -0,0 +1,33 @@
1
+ import {PosInfos} from "./PosInfos"
2
+
3
+ /**
4
+ Log primarily provides the `trace()` method, which is invoked upon a call to
5
+ `trace()` in Haxe code.
6
+ */
7
+ export declare class Log {
8
+
9
+ /**
10
+ Format the output of `trace` before printing it.
11
+ */
12
+ static formatOutput(v: any, infos: PosInfos): string
13
+
14
+ /**
15
+ Outputs `v` in a platform-dependent way.
16
+
17
+ The second parameter `infos` is injected by the compiler and contains
18
+ information about the position where the `trace()` call was made.
19
+
20
+ This method can be rebound to a custom function:
21
+
22
+ var oldTrace = haxe.Log.trace; // store old function
23
+ haxe.Log.trace = function(v, ?infos) {
24
+ // handle trace
25
+ }
26
+ ...
27
+ haxe.Log.trace = oldTrace;
28
+
29
+ If it is bound to null, subsequent calls to `trace()` will cause an
30
+ exception.
31
+ */
32
+ static trace(v: any, infos?: null | PosInfos): void
33
+ }
@@ -0,0 +1,61 @@
1
+ import {Register} from "../genes/Register.js"
2
+ import {Std} from "../Std.js"
3
+
4
+ const $global = Register.$global
5
+
6
+ /**
7
+ Log primarily provides the `trace()` method, which is invoked upon a call to
8
+ `trace()` in Haxe code.
9
+ */
10
+ export const Log = Register.global("$hxClasses")["haxe.Log"] =
11
+ class Log {
12
+
13
+ /**
14
+ Format the output of `trace` before printing it.
15
+ */
16
+ static formatOutput(v, infos) {
17
+ let str = Std.string(v);
18
+ if (infos == null) {
19
+ return str;
20
+ };
21
+ let pstr = infos.fileName + ":" + infos.lineNumber;
22
+ if (infos.customParams != null) {
23
+ let _g = 0;
24
+ let _g1 = infos.customParams;
25
+ while (_g < _g1.length) str += ", " + Std.string(_g1[_g++]);
26
+ };
27
+ return pstr + ": " + str;
28
+ }
29
+
30
+ /**
31
+ Outputs `v` in a platform-dependent way.
32
+
33
+ The second parameter `infos` is injected by the compiler and contains
34
+ information about the position where the `trace()` call was made.
35
+
36
+ This method can be rebound to a custom function:
37
+
38
+ var oldTrace = haxe.Log.trace; // store old function
39
+ haxe.Log.trace = function(v, ?infos) {
40
+ // handle trace
41
+ }
42
+ ...
43
+ haxe.Log.trace = oldTrace;
44
+
45
+ If it is bound to null, subsequent calls to `trace()` will cause an
46
+ exception.
47
+ */
48
+ static trace(v, infos) {
49
+ let str = Log.formatOutput(v, infos);
50
+ if (typeof(console) != "undefined" && console.log != null) {
51
+ console.log(str);
52
+ };
53
+ }
54
+ static get __name__() {
55
+ return "haxe.Log"
56
+ }
57
+ get __class__() {
58
+ return Log
59
+ }
60
+ }
61
+
@@ -25,7 +25,7 @@ export declare class Source {
25
25
  }
26
26
 
27
27
  export declare class SourceData {
28
- protected constructor(id: string, data: Buffer)
28
+ protected constructor(id: string, data: Buffer, knownHash?: null | SourceHash)
29
29
  data: Buffer
30
30
 
31
31
  /**
@@ -56,6 +56,15 @@ export declare class SourceData {
56
56
  * @return Promise<SourceData>
57
57
  */
58
58
  static fromFile(id: string, path: string, pathId: string): Promise<SourceData>
59
+
60
+ /**
61
+ * Read file without recomputing hash. Used when mtime validation passed.
62
+ * @param id Path id relative to stone that generates it.
63
+ * @param path Actual path relative to CWD.
64
+ * @param pathId Path Id relative to project.
65
+ * @param knownHash The hash from cache (already validated via mtime).
66
+ */
67
+ static fromFileSkipHash(id: string, path: string, pathId: string, knownHash: SourceHash): Promise<SourceData>
59
68
  static fromString(id: string, s: string): SourceData
60
69
  static fromBytes(id: string, data: Buffer): SourceData
61
70
  }
@@ -61,12 +61,12 @@ class Source extends Register.inherits() {
61
61
 
62
62
  export const SourceData = Register.global("$hxClasses")["whet.SourceData"] =
63
63
  class SourceData extends Register.inherits() {
64
- new(id, data) {
64
+ new(id, data, knownHash) {
65
65
  this.filePath = null;
66
66
  this.filePathId = null;
67
67
  this.data = data;
68
68
  this.id = id;
69
- this.hash = SourceHash.fromBytes(data);
69
+ this.hash = (knownHash != null) ? knownHash : SourceHash.fromBytes(data);
70
70
  }
71
71
 
72
72
  /**
@@ -130,6 +130,29 @@ class SourceData extends Register.inherits() {
130
130
  });
131
131
  });
132
132
  }
133
+
134
+ /**
135
+ * Read file without recomputing hash. Used when mtime validation passed.
136
+ * @param id Path id relative to stone that generates it.
137
+ * @param path Actual path relative to CWD.
138
+ * @param pathId Path Id relative to project.
139
+ * @param knownHash The hash from cache (already validated via mtime).
140
+ */
141
+ static fromFileSkipHash(id, path, pathId, knownHash) {
142
+ return new Promise(function (res, rej) {
143
+ Fs.readFile(path, function (err, buffer) {
144
+ if (err != null) {
145
+ Log.log(50, ...["File does not exist.", {"id": id, "path": path, "error": err}]);
146
+ rej(err);
147
+ } else {
148
+ let source = new SourceData(id, buffer, knownHash);
149
+ source.filePath = path;
150
+ source.filePathId = pathId;
151
+ res(source);
152
+ };
153
+ });
154
+ });
155
+ }
133
156
  static fromString(id, s) {
134
157
  return SourceData.fromBytes(id, Buffer.from(s, "utf-8"));
135
158
  }
@@ -1,5 +1,6 @@
1
1
  import {Router} from "./route/Router.js"
2
2
  import {MaybeArray_Fields_} from "./magic/MaybeArray.js"
3
+ import {HashCache} from "./cache/HashCache.js"
3
4
  import {Utils} from "./Utils.js"
4
5
  import {Stone} from "./Stone.js"
5
6
  import {Register} from "../genes/Register.js"
@@ -25,15 +26,7 @@ class SourceHash extends Register.inherits() {
25
26
  return this.bytes.toString("hex");
26
27
  }
27
28
  static fromFile(path) {
28
- return new Promise(function (res, rej) {
29
- Fs.readFile(path, function (err, bytes) {
30
- if (err != null) {
31
- rej(err);
32
- } else {
33
- res(SourceHash.fromBytes(bytes));
34
- };
35
- });
36
- });
29
+ return HashCache.get().getFileHash(path);
37
30
  }
38
31
 
39
32
  /**
@@ -104,6 +104,17 @@ export declare class Stone<T extends StoneConfig> {
104
104
  */
105
105
  list(): Promise<string[]>
106
106
 
107
+ /**
108
+ * Optional filter describing what this Stone can produce.
109
+ * Used by Router to skip Stones that can't match a query.
110
+ * If null, Stone is assumed to potentially produce anything.
111
+ *
112
+ * This is a function (not a static value) because Stone outputs
113
+ * may depend on runtime config which can change externally.
114
+ * Override in subclasses to provide filtering optimization.
115
+ */
116
+ getOutputFilter(): null | OutputFilter
117
+
107
118
  /**
108
119
  * Caches this resource under supplied `path` as a single copy.
109
120
  * @param path
@@ -159,3 +170,19 @@ export type StoneConfig = {
159
170
  }
160
171
 
161
172
  export type AnyStone = Stone<any>
173
+
174
+ /**
175
+ * Describes what file types/patterns a Stone can produce.
176
+ * Used by Router to skip Stones that can't match a query.
177
+ */
178
+ export type OutputFilter = {
179
+ /**
180
+ * File extensions this Stone can produce (without dot). Null = any.
181
+ * Supports compound extensions like "png.meta.json" for metadata files.
182
+ */
183
+ extensions?: null | string[],
184
+ /**
185
+ Glob patterns for output files. Null = matches any file with valid extension.
186
+ */
187
+ patterns?: null | string[]
188
+ }
package/bin/whet/Stone.js CHANGED
@@ -241,6 +241,19 @@ class Stone extends Register.inherits() {
241
241
  });
242
242
  }
243
243
 
244
+ /**
245
+ * Optional filter describing what this Stone can produce.
246
+ * Used by Router to skip Stones that can't match a query.
247
+ * If null, Stone is assumed to potentially produce anything.
248
+ *
249
+ * This is a function (not a static value) because Stone outputs
250
+ * may depend on runtime config which can change externally.
251
+ * Override in subclasses to provide filtering optimization.
252
+ */
253
+ getOutputFilter() {
254
+ return null;
255
+ }
256
+
244
257
  /**
245
258
  * Caches this resource under supplied `path` as a single copy.
246
259
  * @param path
package/bin/whet/Whet.js CHANGED
@@ -12,7 +12,7 @@ const $global = Register.$global
12
12
  export const Whet_Fields_ = Register.global("$hxClasses")["whet._Whet.Whet_Fields_"] =
13
13
  class Whet_Fields_ {
14
14
  static main() {
15
- Whet_Fields_.program.enablePositionalOptions().passThroughOptions().description("Project tooling.").usage("[options] [command] [+ [command]...]").version("0.0.31", "-v, --version").allowUnknownOption(true).showSuggestionAfterError(true).option("-p, --project <file>", "project to run", "Project.mjs").option("-l, --log-level <level>", "log level, a string/number", "info").option("--no-pretty", "disable pretty logging").exitOverride();
15
+ Whet_Fields_.program.enablePositionalOptions().passThroughOptions().description("Project tooling.").usage("[options] [command] [+ [command]...]").version("0.0.33", "-v, --version").allowUnknownOption(true).showSuggestionAfterError(true).option("-p, --project <file>", "project to run", "Project.mjs").option("-l, --log-level <level>", "log level, a string/number", "info").option("--no-pretty", "disable pretty logging").exitOverride();
16
16
  try {
17
17
  Whet_Fields_.program.parse();
18
18
  }catch (_g) {
@@ -25,7 +25,9 @@ export type FileCacheValue<H, S> = {
25
25
  files: {
26
26
  fileHash: H,
27
27
  filePath: S,
28
- id: S
28
+ id: S,
29
+ mtime?: null | number,
30
+ size?: null | number
29
31
  }[],
30
32
  hash: H
31
33
  }
@@ -1,3 +1,4 @@
1
+ import {HashCache} from "./HashCache.js"
1
2
  import {BaseCache} from "./BaseCache.js"
2
3
  import {Utils} from "../Utils.js"
3
4
  import {SourceHash} from "../SourceHash.js"
@@ -41,7 +42,7 @@ class FileCache extends Register.inherits(() => BaseCache, true) {
41
42
  while (_g3 < _g4.length) {
42
43
  let file = _g4[_g3];
43
44
  ++_g3;
44
- _g2.push({"fileHash": SourceHash.fromHex(file.fileHash), "filePath": file.filePath, "id": file.id});
45
+ _g2.push({"fileHash": SourceHash.fromHex(file.fileHash), "filePath": file.filePath, "id": file.id, "mtime": file.mtime, "size": file.size});
45
46
  };
46
47
  _g.push({"hash": tmp, "ctime": val1, "baseDir": val2, "files": _g2});
47
48
  };
@@ -62,6 +63,7 @@ class FileCache extends Register.inherits(() => BaseCache, true) {
62
63
  } else {
63
64
  idOverride = null;
64
65
  };
66
+ let _gthis = this;
65
67
  let _g1 = [];
66
68
  let _g2 = 0;
67
69
  let _g3 = source.data;
@@ -69,7 +71,10 @@ class FileCache extends Register.inherits(() => BaseCache, true) {
69
71
  let data = _g3[_g2];
70
72
  ++_g2;
71
73
  _g1.push(data.getFilePathId(idOverride).then(function (filePath) {
72
- return {"fileHash": SourceHash.fromBytes(data.data), "filePath": filePath, "id": data.id};
74
+ let cwdPath = Path.posix.join(".", _gthis.rootDir, ".", filePath);
75
+ return HashCache.getStats(cwdPath).then(function (stats) {
76
+ return {"fileHash": SourceHash.fromBytes(data.data), "filePath": filePath, "id": data.id, "mtime": stats.mtime, "size": stats.size};
77
+ });
73
78
  }));
74
79
  };
75
80
  return Promise.all(_g1).then(function (files) {
@@ -100,17 +105,32 @@ class FileCache extends Register.inherits(() => BaseCache, true) {
100
105
  ++_g2;
101
106
  _g1.push(new Promise(function (res, rej) {
102
107
  let path = Path.posix.join(".", _gthis.rootDir, ".", file.filePath);
103
- SourceData.fromFile(file.id, path, file.filePath).then(function (sourceData) {
104
- if (sourceData == null || !stone.ignoreFileHash && !SourceHash.equals(sourceData.hash, file.fileHash)) {
105
- rej("Invalid.");
106
- } else {
107
- res(sourceData);
108
+ Fs.stat(path, function (statErr, stats) {
109
+ if (statErr != null) {
110
+ if (((statErr) instanceof Error) && statErr.code == "ENOENT") {
111
+ rej("Invalid.");
112
+ } else {
113
+ rej(statErr);
114
+ };
115
+ return;
108
116
  };
109
- }, function (err) {
110
- if (((err) instanceof Error) && err.code == "ENOENT") {
111
- rej("Invalid.");
117
+ let mtimeMatch = file.mtime != null && stats.mtimeMs == file.mtime && (stats.size | 0) == file.size;
118
+ if (mtimeMatch && !stone.ignoreFileHash) {
119
+ SourceData.fromFileSkipHash(file.id, path, file.filePath, file.fileHash).then(res, rej);
112
120
  } else {
113
- rej(err);
121
+ SourceData.fromFile(file.id, path, file.filePath).then(function (sourceData) {
122
+ if (sourceData == null || !stone.ignoreFileHash && !SourceHash.equals(sourceData.hash, file.fileHash)) {
123
+ rej("Invalid.");
124
+ } else {
125
+ res(sourceData);
126
+ };
127
+ }, function (err) {
128
+ if (((err) instanceof Error) && err.code == "ENOENT") {
129
+ rej("Invalid.");
130
+ } else {
131
+ rej(err);
132
+ };
133
+ });
114
134
  };
115
135
  });
116
136
  }));
@@ -236,7 +256,7 @@ class FileCache extends Register.inherits(() => BaseCache, true) {
236
256
  while (_g3 < _g4.length) {
237
257
  let file = _g4[_g3];
238
258
  ++_g3;
239
- _g2.push({"fileHash": SourceHash.toHex(file.fileHash), "filePath": Path.posix.join(".", "./", ".", file.filePath), "id": Path.posix.join(".", "./", ".", file.id)});
259
+ _g2.push({"fileHash": SourceHash.toHex(file.fileHash), "filePath": Path.posix.join(".", "./", ".", file.filePath), "id": Path.posix.join(".", "./", ".", file.id), "mtime": file.mtime, "size": file.size});
240
260
  };
241
261
  _g.push({"hash": tmp, "ctime": val1, "ctimePretty": tmp1, "baseDir": tmp2, "files": _g2});
242
262
  };
@@ -0,0 +1,36 @@
1
+ import {SourceHash} from "../SourceHash"
2
+ import {Map as Map__1} from "../../Map"
3
+
4
+ /**
5
+ * In-memory cache for file hashes based on mtime+size.
6
+ * Avoids re-reading and re-hashing file contents when files haven't changed.
7
+ */
8
+ export declare class HashCache {
9
+ protected constructor()
10
+ protected cache: Map__1<string, CachedHash>
11
+
12
+ /**
13
+ * Get hash for file, using cache if mtime/size match.
14
+ * Returns cached hash or computes new one.
15
+ */
16
+ getFileHash(path: string): Promise<SourceHash>
17
+ protected static instance: HashCache
18
+ static get(): HashCache
19
+
20
+ /**
21
+ * Get file stats (mtime and size) for a path.
22
+ * Useful for storing alongside cache entries.
23
+ */
24
+ static getStats(path: string): Promise<FileStats>
25
+ }
26
+
27
+ export type CachedHash = {
28
+ hash: string,
29
+ mtime: number,
30
+ size: number
31
+ }
32
+
33
+ export type FileStats = {
34
+ mtime: number,
35
+ size: number
36
+ }
@@ -0,0 +1,79 @@
1
+ import {SourceHash} from "../SourceHash.js"
2
+ import {StringMap} from "../../haxe/ds/StringMap.js"
3
+ import {Register} from "../../genes/Register.js"
4
+ import * as Fs from "fs"
5
+
6
+ const $global = Register.$global
7
+
8
+ /**
9
+ * In-memory cache for file hashes based on mtime+size.
10
+ * Avoids re-reading and re-hashing file contents when files haven't changed.
11
+ */
12
+ export const HashCache = Register.global("$hxClasses")["whet.cache.HashCache"] =
13
+ class HashCache extends Register.inherits() {
14
+ new() {
15
+ this.cache = new StringMap();
16
+ }
17
+
18
+ /**
19
+ * Get hash for file, using cache if mtime/size match.
20
+ * Returns cached hash or computes new one.
21
+ */
22
+ getFileHash(path) {
23
+ let _gthis = this;
24
+ return new Promise(function (res, rej) {
25
+ Fs.stat(path, function (err, stats) {
26
+ if (err != null) {
27
+ rej(err);
28
+ return;
29
+ };
30
+ let cached = _gthis.cache.inst.get(path);
31
+ let mtimeMs = stats.mtimeMs;
32
+ if (cached != null && cached.mtime == mtimeMs && cached.size == (stats.size | 0)) {
33
+ res(SourceHash.fromHex(cached.hash));
34
+ return;
35
+ };
36
+ Fs.readFile(path, function (err, data) {
37
+ if (err != null) {
38
+ rej(err);
39
+ return;
40
+ };
41
+ let hash = SourceHash.fromBytes(data);
42
+ let this1 = _gthis.cache;
43
+ let value = {"mtime": mtimeMs, "size": stats.size | 0, "hash": SourceHash.toHex(hash)};
44
+ this1.inst.set(path, value);
45
+ res(hash);
46
+ });
47
+ });
48
+ });
49
+ }
50
+ static get() {
51
+ if (HashCache.instance == null) {
52
+ HashCache.instance = new HashCache();
53
+ };
54
+ return HashCache.instance;
55
+ }
56
+
57
+ /**
58
+ * Get file stats (mtime and size) for a path.
59
+ * Useful for storing alongside cache entries.
60
+ */
61
+ static getStats(path) {
62
+ return new Promise(function (res, rej) {
63
+ Fs.stat(path, function (err, stats) {
64
+ if (err != null) {
65
+ rej(err);
66
+ } else {
67
+ res({"mtime": stats.mtimeMs, "size": stats.size | 0});
68
+ };
69
+ });
70
+ });
71
+ }
72
+ static get __name__() {
73
+ return "whet.cache.HashCache"
74
+ }
75
+ get __class__() {
76
+ return HashCache
77
+ }
78
+ }
79
+
@@ -0,0 +1,32 @@
1
+ import {OutputFilter} from "../Stone"
2
+
3
+ /**
4
+ * Utility for matching queries against Stone output filters.
5
+ * Enables skipping entire Stone subtrees when their outputs can't match a query.
6
+ */
7
+ export declare class OutputFilterMatcher {
8
+
9
+ /**
10
+ * Check if a query could possibly match a Stone's output filter.
11
+ * Returns true if the Stone should be enumerated, false if it can be skipped.
12
+ *
13
+ * @param query The search pattern (often a specific file path)
14
+ * @param filter The Stone's output filter (null = matches anything)
15
+ * @param queryIsPattern True if query contains wildcards
16
+ */
17
+ static couldMatch(query: string, filter: null | OutputFilter, queryIsPattern: boolean): boolean
18
+
19
+ /**
20
+ * Extract extension from path, supporting compound extensions.
21
+ * "title.png" → "png"
22
+ * "title.png.meta.json" → "png.meta.json"
23
+ * Returns null if no extension or contains wildcard.
24
+ */
25
+ protected static getExtension(path: string): null | string
26
+
27
+ /**
28
+ * Check if query extension matches any filter extension.
29
+ * Handles compound extensions: query "png.meta.json" matches filter "json" or "meta.json" or "png.meta.json"
30
+ */
31
+ protected static extensionMatches(queryExt: string, filterExts: string[]): boolean
32
+ }
@@ -0,0 +1,83 @@
1
+ import Minimatch from "minimatch"
2
+ import {Register} from "../../genes/Register.js"
3
+
4
+ const $global = Register.$global
5
+
6
+ /**
7
+ * Utility for matching queries against Stone output filters.
8
+ * Enables skipping entire Stone subtrees when their outputs can't match a query.
9
+ */
10
+ export const OutputFilterMatcher = Register.global("$hxClasses")["whet.route.OutputFilterMatcher"] =
11
+ class OutputFilterMatcher {
12
+
13
+ /**
14
+ * Check if a query could possibly match a Stone's output filter.
15
+ * Returns true if the Stone should be enumerated, false if it can be skipped.
16
+ *
17
+ * @param query The search pattern (often a specific file path)
18
+ * @param filter The Stone's output filter (null = matches anything)
19
+ * @param queryIsPattern True if query contains wildcards
20
+ */
21
+ static couldMatch(query, filter, queryIsPattern) {
22
+ if (filter == null) {
23
+ return true;
24
+ };
25
+ if (filter.extensions != null) {
26
+ let queryExt = OutputFilterMatcher.getExtension(query);
27
+ if (queryExt != null && !OutputFilterMatcher.extensionMatches(queryExt, filter.extensions)) {
28
+ return false;
29
+ };
30
+ };
31
+ if (!queryIsPattern && filter.patterns != null) {
32
+ let _g = 0;
33
+ let _g1 = filter.patterns;
34
+ while (_g < _g1.length) if (new Minimatch.Minimatch("**/" + _g1[_g++], null).match(query)) {
35
+ return true;
36
+ };
37
+ return false;
38
+ };
39
+ return true;
40
+ }
41
+
42
+ /**
43
+ * Extract extension from path, supporting compound extensions.
44
+ * "title.png" → "png"
45
+ * "title.png.meta.json" → "png.meta.json"
46
+ * Returns null if no extension or contains wildcard.
47
+ */
48
+ static getExtension(path) {
49
+ let name = path.substring(path.lastIndexOf("/") + 1);
50
+ let firstDot = name.indexOf(".");
51
+ if (firstDot == -1 || firstDot == name.length - 1) {
52
+ return null;
53
+ };
54
+ let ext = name.substring(firstDot + 1).toLowerCase();
55
+ if (ext.indexOf("*") != -1) {
56
+ return null;
57
+ };
58
+ return ext;
59
+ }
60
+
61
+ /**
62
+ * Check if query extension matches any filter extension.
63
+ * Handles compound extensions: query "png.meta.json" matches filter "json" or "meta.json" or "png.meta.json"
64
+ */
65
+ static extensionMatches(queryExt, filterExts) {
66
+ let _g = 0;
67
+ while (_g < filterExts.length) {
68
+ let filterExt = filterExts[_g];
69
+ ++_g;
70
+ if (queryExt == filterExt || queryExt.endsWith("." + filterExt)) {
71
+ return true;
72
+ };
73
+ };
74
+ return false;
75
+ }
76
+ static get __name__() {
77
+ return "whet.route.OutputFilterMatcher"
78
+ }
79
+ get __class__() {
80
+ return OutputFilterMatcher
81
+ }
82
+ }
83
+
@@ -1,7 +1,7 @@
1
1
  import {RouteResult} from "./RouteResult"
2
2
  import {RoutePathType} from "../magic/RoutePathType"
3
3
  import {MinimatchType} from "../magic/MinimatchType"
4
- import {AnyStone} from "../Stone"
4
+ import {OutputFilter, AnyStone} from "../Stone"
5
5
  import {SourceHash} from "../SourceHash"
6
6
  import Minimatch from "minimatch"
7
7
 
@@ -19,6 +19,7 @@ export declare class Router {
19
19
 
20
20
  /**
21
21
  * Get combined hash of all sources that fit the `pattern`.
22
+ * Includes matched serveIds in hash to capture filter effects.
22
23
  */
23
24
  getHash(pattern?: null | MinimatchType): Promise<SourceHash>
24
25
 
@@ -27,6 +28,12 @@ export declare class Router {
27
28
  */
28
29
  saveInto(pattern: MinimatchType, saveInto: string, clearFirst?: boolean): Promise<any>
29
30
  listContents(pattern?: null | MinimatchType): Promise<string>
31
+
32
+ /**
33
+ * Compute combined output filter from all routes.
34
+ * Must be dynamic (not cached) because Stone configs can change externally.
35
+ */
36
+ getOutputFilter(): null | OutputFilter
30
37
  }
31
38
 
32
39
  export type Filter = {
@@ -1,4 +1,5 @@
1
1
  import {RouteResult} from "./RouteResult.js"
2
+ import {OutputFilterMatcher} from "./OutputFilterMatcher.js"
2
3
  import {RoutePathType_Fields_} from "../magic/RoutePathType.js"
3
4
  import {MinimatchType_Fields_} from "../magic/MinimatchType.js"
4
5
  import {Utils} from "../Utils.js"
@@ -31,6 +32,8 @@ class Router extends Register.inherits() {
31
32
  let _gthis = this;
32
33
  return new Promise(function (res, rej) {
33
34
  let allRouteProms = [];
35
+ let pattern = (mainFilters.length > 0 && mainFilters[0].filter != null) ? mainFilters[0].filter.pattern : null;
36
+ let queryIsPattern = pattern != null && (pattern.indexOf("*") != -1 || pattern.indexOf("?") != -1);
34
37
  let _g = 0;
35
38
  let _g1 = _gthis.routes;
36
39
  while (_g < _g1.length) {
@@ -127,6 +130,18 @@ class Router extends Register.inherits() {
127
130
  };
128
131
  routeFilters.push({"pathSoFar": [], "filter": f, "inProgress": true, "remDirs": []});
129
132
  };
133
+ let outputFilter = null;
134
+ if (((route.source) instanceof Stone)) {
135
+ outputFilter = route.source.getOutputFilter();
136
+ } else if (((route.source) instanceof Router)) {
137
+ outputFilter = route.source.getOutputFilter();
138
+ };
139
+ if (outputFilter != null) {
140
+ let queryPattern = (routeFilters.length > 0 && routeFilters[0].filter != null) ? routeFilters[0].filter.pattern : null;
141
+ if (queryPattern != null && !OutputFilterMatcher.couldMatch(queryPattern, outputFilter, queryIsPattern)) {
142
+ continue;
143
+ };
144
+ };
130
145
  let prom;
131
146
  if (((route.source) instanceof Stone)) {
132
147
  let stone = route.source;
@@ -190,10 +205,12 @@ class Router extends Register.inherits() {
190
205
 
191
206
  /**
192
207
  * Get combined hash of all sources that fit the `pattern`.
208
+ * Includes matched serveIds in hash to capture filter effects.
193
209
  */
194
210
  getHash(pattern) {
195
211
  return this.get(pattern).then(function (items) {
196
212
  let uniqueStones = [];
213
+ let serveIds = [];
197
214
  let _g = 0;
198
215
  while (_g < items.length) {
199
216
  let item = items[_g];
@@ -201,7 +218,17 @@ class Router extends Register.inherits() {
201
218
  if (uniqueStones.indexOf(item.source) == -1) {
202
219
  uniqueStones.push(item.source);
203
220
  };
221
+ serveIds.push(item.serveId);
204
222
  };
223
+ serveIds.sort(function (a, b) {
224
+ if (a < b) {
225
+ return -1;
226
+ } else if (a > b) {
227
+ return 1;
228
+ } else {
229
+ return 0;
230
+ };
231
+ });
205
232
  let result = new Array(uniqueStones.length);
206
233
  let _g1 = 0;
207
234
  let _g2 = uniqueStones.length;
@@ -210,7 +237,16 @@ class Router extends Register.inherits() {
210
237
  result[i] = uniqueStones[i].getHash();
211
238
  };
212
239
  return Promise.all(result).then(function (hashes) {
213
- return SourceHash.merge(...hashes);
240
+ hashes.sort(function (a, b) {
241
+ if (a.toString() < b.toString()) {
242
+ return -1;
243
+ } else if (a.toString() > b.toString()) {
244
+ return 1;
245
+ } else {
246
+ return 0;
247
+ };
248
+ });
249
+ return SourceHash.merge(...hashes).add(SourceHash.fromString(serveIds.join("\n")));
214
250
  });
215
251
  });
216
252
  }
@@ -260,6 +296,55 @@ class Router extends Register.inherits() {
260
296
  return result.join("\n");
261
297
  });
262
298
  }
299
+
300
+ /**
301
+ * Compute combined output filter from all routes.
302
+ * Must be dynamic (not cached) because Stone configs can change externally.
303
+ */
304
+ getOutputFilter() {
305
+ let allExtensions = new Array();
306
+ let allPatterns = new Array();
307
+ let hasUnfiltered = false;
308
+ let _g = 0;
309
+ let _g1 = this.routes;
310
+ while (_g < _g1.length) {
311
+ let route = _g1[_g];
312
+ ++_g;
313
+ let childFilter = null;
314
+ if (((route.source) instanceof Stone)) {
315
+ childFilter = route.source.getOutputFilter();
316
+ } else if (((route.source) instanceof Router)) {
317
+ childFilter = route.source.getOutputFilter();
318
+ };
319
+ if (childFilter == null) {
320
+ hasUnfiltered = true;
321
+ break;
322
+ };
323
+ if (childFilter.extensions != null) {
324
+ let _g = 0;
325
+ let _g1 = childFilter.extensions;
326
+ while (_g < _g1.length) {
327
+ let ext = _g1[_g];
328
+ ++_g;
329
+ if (allExtensions.indexOf(ext) == -1) {
330
+ allExtensions.push(ext);
331
+ };
332
+ };
333
+ };
334
+ if (childFilter.patterns != null) {
335
+ let _g = 0;
336
+ let _g1 = childFilter.patterns;
337
+ while (_g < _g1.length) {
338
+ let prefixed = Path.posix.join(route.routeUnder, _g1[_g++]);
339
+ allPatterns.push(prefixed);
340
+ };
341
+ };
342
+ };
343
+ if (hasUnfiltered) {
344
+ return null;
345
+ };
346
+ return {"extensions": (allExtensions.length > 0) ? allExtensions : null, "patterns": (allPatterns.length > 0) ? allPatterns : null};
347
+ }
263
348
  static get __name__() {
264
349
  return "whet.route.Router"
265
350
  }
@@ -9,6 +9,7 @@ import {Project} from "../Project"
9
9
  export declare class Files extends Stone<FilesConfig> {
10
10
  constructor(config: FilesConfig)
11
11
  protected initConfig(): void
12
+ protected generateHash(): Promise<SourceHash>
12
13
  list(): Promise<string[]>
13
14
  protected generate(hash: SourceHash): Promise<SourceData[]>
14
15
  protected walk<T>(onFile: ((arg0: string) => T), onDirFile: ((arg0: string, arg1: string) => T)): Promise<T[]>
@@ -1,7 +1,9 @@
1
1
  import {MaybeArray_Fields_} from "../magic/MaybeArray.js"
2
+ import {HashCache} from "../cache/HashCache.js"
2
3
  import {Utils} from "../Utils.js"
3
4
  import {Stone} from "../Stone.js"
4
5
  import {IdUtils, RootDir} from "../SourceId.js"
6
+ import {SourceHash} from "../SourceHash.js"
5
7
  import {SourceData} from "../Source.js"
6
8
  import {Register} from "../../genes/Register.js"
7
9
 
@@ -15,6 +17,35 @@ class Files extends Register.inherits(() => Stone, true) {
15
17
  initConfig() {
16
18
  this.config.recursive = (this.config.recursive != null) ? this.config.recursive : true;
17
19
  }
20
+ generateHash() {
21
+ let hashCache = HashCache.get();
22
+ let onDirFile = function (dir, dirFile) {
23
+ return hashCache.getFileHash(dirFile);
24
+ };
25
+ let files = [];
26
+ let dirs = [];
27
+ let _g = 0;
28
+ let _g1 = MaybeArray_Fields_.makeArray(this.config.paths);
29
+ while (_g < _g1.length) {
30
+ let path = _g1[_g];
31
+ ++_g;
32
+ if (path.length == 0 || path.charCodeAt(path.length - 1) == 47) {
33
+ dirs.push(Utils.listDirectoryFiles(this.cwdPath(path), this.config.recursive).then(function (arr) {
34
+ let _g = 0;
35
+ while (_g < arr.length) files.push(onDirFile(path, arr[_g++]));
36
+ }));
37
+ } else {
38
+ files.push(hashCache.getFileHash(this.cwdPath(path)));
39
+ };
40
+ };
41
+ return Promise.all(dirs).then(function (_) {
42
+ return files;
43
+ }).then(function (hashProms) {
44
+ return Promise.all(hashProms);
45
+ }).then(function (hashes) {
46
+ return SourceHash.merge(...hashes);
47
+ });
48
+ }
18
49
  list() {
19
50
  let _gthis = this;
20
51
  let onDirFile = function (dir, dirFile) {
@@ -7,6 +7,7 @@ import * as Http from "http"
7
7
  import {Register} from "../../genes/Register.js"
8
8
  import {Std} from "../../Std.js"
9
9
  import {Reflect as Reflect__1} from "../../Reflect.js"
10
+ import {Lambda} from "../../Lambda.js"
10
11
 
11
12
  const $global = Register.$global
12
13
 
@@ -71,34 +72,63 @@ class Server extends Register.inherits(Stone) {
71
72
  let _gthis = this;
72
73
  switch (req.method) {
73
74
  case "GET":
74
- if (id.length == 0 || id.charCodeAt(id.length - 1) == 47) {
75
- let id1 = id;
76
- let dir = id1.substring(0, id1.lastIndexOf("/") + 1);
77
- id = Path.posix.join((dir.length == 0) ? "./" : dir, "index.html");
78
- } else if (Path.posix.extname(id) == "") {
79
- id = "" + id + "/index.html";
75
+ let isDirOrNoExt = id.length == 0 || id.charCodeAt(id.length - 1) == 47 || Path.posix.extname(id) == "";
76
+ let queryPromise;
77
+ if (isDirOrNoExt) {
78
+ let searchPattern = id.length == 0 || id.charCodeAt(id.length - 1) == 47;
79
+ queryPromise = this.config.router.get((searchPattern) ? id + "**" : id + "/**");
80
+ } else {
81
+ queryPromise = Promise.resolve([]);
80
82
  };
81
- this.config.router.get(id).then(function (routeResult) {
82
- let sourcePromise = (routeResult.length > 0) ? routeResult[0].get() : (_gthis.routeDynamic != null) ? _gthis.routeDynamic(id) : Promise.resolve(null);
83
- return sourcePromise.then(function (source) {
84
- if (source == null) {
85
- res.writeHead(404, "File not found.");
83
+ queryPromise.then(function (dirResults) {
84
+ if (isDirOrNoExt && dirResults.length == 1) {
85
+ id = dirResults[0].serveId;
86
+ } else if (isDirOrNoExt) {
87
+ if (!req.url.substring(0, (searchIndex > 0) ? searchIndex : req.url.length).endsWith("/")) {
88
+ let redirectUrl = (searchIndex > 0) ? req.url.substring(0, searchIndex) + "/" + req.url.substring(searchIndex) : req.url + "/";
89
+ res.writeHead(301, "Moved Permanently", {"Location": redirectUrl});
86
90
  res.end();
87
91
  return;
88
92
  };
89
- let headers = {"Content-Type": Mime.getType(Path.posix.extname(id).toLowerCase()), "Last-Modified": new Date(source.source.ctime * 1000).toUTCString(), "Content-Length": Std.string(source.data.length), "Cache-Control": "no-store, no-cache"};
90
- if (_gthis.config.headers != null) {
91
- let access = _gthis.config.headers;
92
- let _g_keys = Reflect__1.fields(access);
93
- let _g_index = 0;
94
- while (_g_index < _g_keys.length) {
95
- let key = _g_keys[_g_index++];
96
- headers[key] = access[key];
97
- };
93
+ if (id.length == 0 || id.charCodeAt(id.length - 1) == 47) {
94
+ let id1 = id;
95
+ let dir = id1.substring(0, id1.lastIndexOf("/") + 1);
96
+ id = Path.posix.join((dir.length == 0) ? "./" : dir, "index.html");
97
+ } else if (Path.posix.extname(id) == "") {
98
+ id = "" + id + "/index.html";
98
99
  };
99
- res.writeHead(200, headers);
100
- res.write(source.data, "binary");
101
- res.end();
100
+ };
101
+ _gthis.config.router.get(id).then(function (routeResult) {
102
+ let sourcePromise = (routeResult.length > 0) ? routeResult[0].get() : (_gthis.routeDynamic != null) ? _gthis.routeDynamic(id) : Promise.resolve(null);
103
+ return sourcePromise.then(function (source) {
104
+ if (source == null) {
105
+ res.writeHead(404, "File not found.");
106
+ res.end();
107
+ return;
108
+ };
109
+ let headers = {"Content-Type": Mime.getType(Path.posix.extname(id).toLowerCase()), "Last-Modified": new Date(source.source.ctime * 1000).toUTCString(), "Content-Length": Std.string(source.data.length), "Cache-Control": "no-store, no-cache"};
110
+ if (_gthis.config.headers != null) {
111
+ let access = _gthis.config.headers;
112
+ let _g_keys = Reflect__1.fields(access);
113
+ let _g_index = 0;
114
+ while (_g_index < _g_keys.length) {
115
+ let key = _g_keys[_g_index++];
116
+ headers[key] = access[key];
117
+ };
118
+ };
119
+ res.writeHead(200, headers);
120
+ res.write(source.data, "binary");
121
+ res.end();
122
+ })["catch"](function (e) {
123
+ Log.log(40, ...["Server error.", {"error": e}]);
124
+ res.writeHead(500, "Error happened.", _gthis.config.headers);
125
+ if (((e) instanceof Error)) {
126
+ res.write(e.stack, "utf-8");
127
+ } else {
128
+ res.write(Std.string(e), "utf-8");
129
+ };
130
+ res.end();
131
+ });
102
132
  })["catch"](function (e) {
103
133
  Log.log(40, ...["Server error.", {"error": e}]);
104
134
  res.writeHead(500, "Error happened.", _gthis.config.headers);
@@ -124,6 +154,68 @@ class Server extends Register.inherits(Stone) {
124
154
  res.writeHead(200, this.config.headers);
125
155
  res.end();
126
156
  break
157
+ case "POST":
158
+ let stoneId = Path.posix.join(".", "./", ".", id);
159
+ let stone = Lambda.find(this.project.stones, function (s) {
160
+ return s.id == stoneId;
161
+ });
162
+ if (stone == null) {
163
+ let e = "Could not find stone with such id.";
164
+ Log.log(40, ...["Server error.", {"error": e}]);
165
+ res.writeHead(500, "Error happened.", _gthis.config.headers);
166
+ if (((e) instanceof Error)) {
167
+ res.write(e.stack, "utf-8");
168
+ } else {
169
+ res.write(Std.string(e), "utf-8");
170
+ };
171
+ res.end();
172
+ } else {
173
+ let body = "";
174
+ req.on("data", function (chunk) {
175
+ body += chunk;
176
+ return body;
177
+ });
178
+ req.on("end", function () {
179
+ let request = JSON.parse(body);
180
+ if (request.config != null) {
181
+ let _g = 0;
182
+ let _g1 = Reflect__1.fields(stone.config);
183
+ while (_g < _g1.length) {
184
+ let field = _g1[_g];
185
+ ++_g;
186
+ stone.config[field] = Reflect__1.field(request.config, field);
187
+ };
188
+ };
189
+ if (request.getSource) {
190
+ stone.getSource().then(function (src) {
191
+ let resJson = {};
192
+ let _g = 0;
193
+ let _g1 = src.data;
194
+ while (_g < _g1.length) {
195
+ let data = _g1[_g];
196
+ ++_g;
197
+ resJson[data.id] = data.data.toString("base64");
198
+ };
199
+ res.writeHead(200, _gthis.config.headers);
200
+ res.write(JSON.stringify(resJson), "utf-8");
201
+ res.end();
202
+ })["catch"](function (e) {
203
+ Log.log(40, ...["Server error.", {"error": e}]);
204
+ res.writeHead(500, "Error happened.", _gthis.config.headers);
205
+ if (((e) instanceof Error)) {
206
+ res.write(e.stack, "utf-8");
207
+ } else {
208
+ res.write(Std.string(e), "utf-8");
209
+ };
210
+ res.end();
211
+ });
212
+ } else {
213
+ res.writeHead(200, _gthis.config.headers);
214
+ res.end();
215
+ };
216
+ });
217
+ };
218
+ break
127
219
  case "PUT":
128
220
  let cmd = [Path.posix.join(".", "./", ".", id)];
129
221
  let body = "";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "whet",
3
- "version": "0.0.31",
3
+ "version": "0.0.33",
4
4
  "description": "NodeJS based assets management and project tooling library.",
5
5
  "scripts": {
6
6
  "devinit": "npx dts2hx commander pino-pretty --modular --noLibWrap",
@@ -13,7 +13,9 @@
13
13
  "files": [
14
14
  "bin"
15
15
  ],
16
- "bin": "bin/whet.js",
16
+ "bin": {
17
+ "whet": "bin/whet.js"
18
+ },
17
19
  "main": "bin/whet.js",
18
20
  "type": "module",
19
21
  "author": "Peter Achberger",