@w5s/dev 1.3.2 → 1.4.0

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.
@@ -0,0 +1,45 @@
1
+ export interface BlockOptions {
2
+ /**
3
+ * The marker builder function that will take either `markerBegin` or `markerEnd`
4
+ *
5
+ * @default '# ${mark} MANAGED BLOCK'
6
+ */
7
+ marker?: (mark: 'Begin' | 'End') => string;
8
+ /**
9
+ * File path
10
+ */
11
+ path: string;
12
+ /**
13
+ * Block content to insert
14
+ */
15
+ block: string;
16
+ /**
17
+ * Insert position
18
+ */
19
+ insertPosition?: ['before', 'BeginningOfFile'] | ['after', 'EndOfFile'];
20
+ /**
21
+ * Block target state
22
+ */
23
+ state?: 'present' | 'absent';
24
+ }
25
+ /**
26
+ * Replace asynchronously a block in file that follows pattern :
27
+ *
28
+ * marker(markerBegin)
29
+ * ...
30
+ * marker(markerEnd)
31
+ *
32
+ * @param options
33
+ */
34
+ export declare function block(options: BlockOptions): Promise<void>;
35
+ /**
36
+ * Replace synchronously a block in file that follows pattern :
37
+ *
38
+ * marker(markerBegin)
39
+ * ...
40
+ * marker(markerEnd)
41
+ *
42
+ * @param options
43
+ */
44
+ export declare function blockSync(options: BlockOptions): void;
45
+ //# sourceMappingURL=block.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"block.d.ts","sourceRoot":"","sources":["../src/block.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,YAAY;IAC3B;;;;OAIG;IACH,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,GAAG,KAAK,KAAK,MAAM,CAAC;IAC3C;;OAEG;IACH,IAAI,EAAE,MAAM,CAAC;IACb;;OAEG;IACH,KAAK,EAAE,MAAM,CAAC;IACd;;OAEG;IACH,cAAc,CAAC,EAAE,CAAC,QAAQ,EAAE,iBAAiB,CAAC,GAAG,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC;IACxE;;OAEG;IACH,KAAK,CAAC,EAAE,SAAS,GAAG,QAAQ,CAAC;CAC9B;AA0ED;;;;;;;;GAQG;AACH,wBAAgB,KAAK,CAAC,OAAO,EAAE,YAAY,iBAE1C;AAED;;;;;;;;GAQG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,YAAY,QAE9C"}
package/dist/block.js ADDED
@@ -0,0 +1,88 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.blockSync = exports.block = void 0;
4
+ const file_js_1 = require("./file.js");
5
+ const EOF = 'EndOfFile';
6
+ const BOF = 'BeginningOfFile';
7
+ function toFileOptions(options) {
8
+ const { marker = (mark) => `# ${mark.toUpperCase()} MANAGED BLOCK`, path, block: blockName, insertPosition = ['after', EOF], state = 'present', } = options;
9
+ const EOL = '\n';
10
+ const beginBlock = marker('Begin');
11
+ const endBlock = marker('End');
12
+ /**
13
+ * @param content
14
+ */
15
+ function findBlock(content) {
16
+ const startIndex = content.indexOf(beginBlock);
17
+ const endIndex = content.indexOf(endBlock) + endBlock.length;
18
+ return {
19
+ endIndex,
20
+ exists: startIndex >= 0 && endIndex >= 0,
21
+ startIndex,
22
+ };
23
+ }
24
+ function apply(fullContent, blockContent) {
25
+ const found = findBlock(fullContent);
26
+ const remove = state === 'absent';
27
+ const replaceBlock = remove ? '' : beginBlock + EOL + blockContent + EOL + endBlock;
28
+ const [positionDirection, positionAnchor] = insertPosition;
29
+ if (found.exists) {
30
+ return fullContent.slice(0, found.startIndex) + replaceBlock + fullContent.slice(found.endIndex);
31
+ }
32
+ if (remove) {
33
+ return fullContent;
34
+ }
35
+ switch (positionDirection) {
36
+ case 'after': {
37
+ // insert
38
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
39
+ if (positionAnchor === EOF) {
40
+ return fullContent + EOL + replaceBlock;
41
+ }
42
+ return fullContent;
43
+ }
44
+ case 'before': {
45
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
46
+ if (positionAnchor === BOF) {
47
+ return replaceBlock + EOL + fullContent;
48
+ }
49
+ return fullContent;
50
+ }
51
+ default: {
52
+ throw new Error(`Unsupported position ${String(positionDirection)}`);
53
+ }
54
+ }
55
+ }
56
+ return {
57
+ path,
58
+ state: 'present',
59
+ update: (sourceContent) => apply(sourceContent, blockName),
60
+ };
61
+ }
62
+ /**
63
+ * Replace asynchronously a block in file that follows pattern :
64
+ *
65
+ * marker(markerBegin)
66
+ * ...
67
+ * marker(markerEnd)
68
+ *
69
+ * @param options
70
+ */
71
+ function block(options) {
72
+ return (0, file_js_1.file)(toFileOptions(options));
73
+ }
74
+ exports.block = block;
75
+ /**
76
+ * Replace synchronously a block in file that follows pattern :
77
+ *
78
+ * marker(markerBegin)
79
+ * ...
80
+ * marker(markerEnd)
81
+ *
82
+ * @param options
83
+ */
84
+ function blockSync(options) {
85
+ return (0, file_js_1.fileSync)(toFileOptions(options));
86
+ }
87
+ exports.blockSync = blockSync;
88
+ //# sourceMappingURL=block.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"block.js","sourceRoot":"","sources":["../src/block.ts"],"names":[],"mappings":"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"}
package/dist/file.d.ts ADDED
@@ -0,0 +1,52 @@
1
+ /// <reference types="node" />
2
+ export interface FileOptions {
3
+ /**
4
+ * File path
5
+ */
6
+ readonly path: string;
7
+ /**
8
+ * File target state
9
+ */
10
+ readonly state: 'present' | 'absent';
11
+ /**
12
+ * File content mapping function
13
+ *
14
+ * @param content
15
+ */
16
+ readonly update?: (content: string) => string | undefined;
17
+ /**
18
+ * File encoding
19
+ */
20
+ readonly encoding?: BufferEncoding;
21
+ }
22
+ /**
23
+ * Ensure file is present/absent with content initialized or modified with `update
24
+ *
25
+ * @example
26
+ * ```ts
27
+ * await file({
28
+ * path: 'foo/bar',
29
+ * state: 'present',
30
+ * update: (content) => content + '_test', // This will append '_test' after current content
31
+ * })
32
+ * ```
33
+ *
34
+ * @param options
35
+ */
36
+ export declare function file(options: FileOptions): Promise<void>;
37
+ /**
38
+ * Ensure file is present/absent with content initialized or modified with `update
39
+ *
40
+ * @example
41
+ * ```ts
42
+ * fileSync({
43
+ * path: 'foo/bar',
44
+ * state: 'present',
45
+ * update: (content) => content + '_test', // This will append '_test' after current content
46
+ * })
47
+ * ```
48
+ *
49
+ * @param options
50
+ */
51
+ export declare function fileSync(options: FileOptions): void;
52
+ //# sourceMappingURL=file.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file.d.ts","sourceRoot":"","sources":["../src/file.ts"],"names":[],"mappings":";AAqBA,MAAM,WAAW,WAAW;IAC1B;;OAEG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB;;OAEG;IACH,QAAQ,CAAC,KAAK,EAAE,SAAS,GAAG,QAAQ,CAAC;IACrC;;;;OAIG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAC;IAC1D;;OAEG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,cAAc,CAAC;CACpC;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,IAAI,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAY9D;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,QAAQ,CAAC,OAAO,EAAE,WAAW,GAAG,IAAI,CAYnD"}
package/dist/file.js ADDED
@@ -0,0 +1,82 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.fileSync = exports.file = void 0;
4
+ const promises_1 = require("node:fs/promises");
5
+ const node_fs_1 = require("node:fs");
6
+ async function exists(path) {
7
+ try {
8
+ await (0, promises_1.access)(path, node_fs_1.constants.F_OK);
9
+ return true;
10
+ }
11
+ catch {
12
+ return false;
13
+ }
14
+ }
15
+ function existsSync(path) {
16
+ try {
17
+ (0, node_fs_1.accessSync)(path, node_fs_1.constants.F_OK);
18
+ return true;
19
+ }
20
+ catch {
21
+ return false;
22
+ }
23
+ }
24
+ /**
25
+ * Ensure file is present/absent with content initialized or modified with `update
26
+ *
27
+ * @example
28
+ * ```ts
29
+ * await file({
30
+ * path: 'foo/bar',
31
+ * state: 'present',
32
+ * update: (content) => content + '_test', // This will append '_test' after current content
33
+ * })
34
+ * ```
35
+ *
36
+ * @param options
37
+ */
38
+ async function file(options) {
39
+ const { path, state, update, encoding = 'utf8' } = options;
40
+ if (state === 'present') {
41
+ const isPresent = await exists(path);
42
+ const previousContent = isPresent ? await (0, promises_1.readFile)(path, encoding) : '';
43
+ const newContent = update == null ? '' : update(previousContent);
44
+ if (newContent != null) {
45
+ await (0, promises_1.writeFile)(path, newContent, encoding);
46
+ }
47
+ }
48
+ else {
49
+ await (0, promises_1.rm)(path);
50
+ }
51
+ }
52
+ exports.file = file;
53
+ /**
54
+ * Ensure file is present/absent with content initialized or modified with `update
55
+ *
56
+ * @example
57
+ * ```ts
58
+ * fileSync({
59
+ * path: 'foo/bar',
60
+ * state: 'present',
61
+ * update: (content) => content + '_test', // This will append '_test' after current content
62
+ * })
63
+ * ```
64
+ *
65
+ * @param options
66
+ */
67
+ function fileSync(options) {
68
+ const { path, state, update, encoding = 'utf8' } = options;
69
+ if (state === 'present') {
70
+ const isPresent = existsSync(path);
71
+ const previousContent = isPresent ? (0, node_fs_1.readFileSync)(path, encoding) : '';
72
+ const newContent = update == null ? '' : update(previousContent);
73
+ if (newContent != null) {
74
+ (0, node_fs_1.writeFileSync)(path, newContent, encoding);
75
+ }
76
+ }
77
+ else {
78
+ (0, node_fs_1.rmSync)(path);
79
+ }
80
+ }
81
+ exports.fileSync = fileSync;
82
+ //# sourceMappingURL=file.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file.js","sourceRoot":"","sources":["../src/file.ts"],"names":[],"mappings":";;;AAAA,+CAAmE;AACnE,qCAAqF;AAErF,KAAK,UAAU,MAAM,CAAC,IAAY;IAChC,IAAI;QACF,MAAM,IAAA,iBAAM,EAAC,IAAI,EAAE,mBAAS,CAAC,IAAI,CAAC,CAAC;QACnC,OAAO,IAAI,CAAC;KACb;IAAC,MAAM;QACN,OAAO,KAAK,CAAC;KACd;AACH,CAAC;AAED,SAAS,UAAU,CAAC,IAAY;IAC9B,IAAI;QACF,IAAA,oBAAU,EAAC,IAAI,EAAE,mBAAS,CAAC,IAAI,CAAC,CAAC;QACjC,OAAO,IAAI,CAAC;KACb;IAAC,MAAM;QACN,OAAO,KAAK,CAAC;KACd;AACH,CAAC;AAuBD;;;;;;;;;;;;;GAaG;AACI,KAAK,UAAU,IAAI,CAAC,OAAoB;IAC7C,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,GAAG,MAAM,EAAE,GAAG,OAAO,CAAC;IAC3D,IAAI,KAAK,KAAK,SAAS,EAAE;QACvB,MAAM,SAAS,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,CAAC;QACrC,MAAM,eAAe,GAAG,SAAS,CAAC,CAAC,CAAC,MAAM,IAAA,mBAAQ,EAAC,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QACxE,MAAM,UAAU,GAAG,MAAM,IAAI,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,eAAe,CAAC,CAAC;QACjE,IAAI,UAAU,IAAI,IAAI,EAAE;YACtB,MAAM,IAAA,oBAAS,EAAC,IAAI,EAAE,UAAU,EAAE,QAAQ,CAAC,CAAC;SAC7C;KACF;SAAM;QACL,MAAM,IAAA,aAAE,EAAC,IAAI,CAAC,CAAC;KAChB;AACH,CAAC;AAZD,oBAYC;AAED;;;;;;;;;;;;;GAaG;AACH,SAAgB,QAAQ,CAAC,OAAoB;IAC3C,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,GAAG,MAAM,EAAE,GAAG,OAAO,CAAC;IAC3D,IAAI,KAAK,KAAK,SAAS,EAAE;QACvB,MAAM,SAAS,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC;QACnC,MAAM,eAAe,GAAG,SAAS,CAAC,CAAC,CAAC,IAAA,sBAAY,EAAC,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QACtE,MAAM,UAAU,GAAG,MAAM,IAAI,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,eAAe,CAAC,CAAC;QACjE,IAAI,UAAU,IAAI,IAAI,EAAE;YACtB,IAAA,uBAAa,EAAC,IAAI,EAAE,UAAU,EAAE,QAAQ,CAAC,CAAC;SAC3C;KACF;SAAM;QACL,IAAA,gBAAM,EAAC,IAAI,CAAC,CAAC;KACd;AACH,CAAC;AAZD,4BAYC"}
package/dist/index.d.ts CHANGED
@@ -1,3 +1,6 @@
1
1
  export * from './eslint.js';
2
+ export * from './block.js';
3
+ export * from './file.js';
4
+ export * from './json.js';
2
5
  export * from './project.js';
3
6
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC;AAC3B,cAAc,WAAW,CAAC;AAC1B,cAAc,WAAW,CAAC;AAC1B,cAAc,cAAc,CAAC"}
package/dist/index.js CHANGED
@@ -15,5 +15,8 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
17
  __exportStar(require("./eslint.js"), exports);
18
+ __exportStar(require("./block.js"), exports);
19
+ __exportStar(require("./file.js"), exports);
20
+ __exportStar(require("./json.js"), exports);
18
21
  __exportStar(require("./project.js"), exports);
19
22
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,8CAA4B;AAC5B,+CAA6B"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,8CAA4B;AAC5B,6CAA2B;AAC3B,4CAA0B;AAC1B,4CAA0B;AAC1B,+CAA6B"}
package/dist/json.d.ts ADDED
@@ -0,0 +1,37 @@
1
+ /// <reference types="node" />
2
+ export type JSONValue = null | number | string | boolean | JSONValue[] | {
3
+ [key: string]: JSONValue;
4
+ };
5
+ export interface JSONOption<V = JSONValue> {
6
+ /**
7
+ * File path
8
+ */
9
+ readonly path: string;
10
+ /**
11
+ * File target state
12
+ */
13
+ readonly state: 'present' | 'absent';
14
+ /**
15
+ * File content mapping function
16
+ *
17
+ * @param content
18
+ */
19
+ readonly update?: (content: V | undefined) => V | undefined;
20
+ /**
21
+ * File encoding
22
+ */
23
+ readonly encoding?: BufferEncoding;
24
+ }
25
+ /**
26
+ * Ensure file is present/absent asynchronously with content value initialized or modified with `update`
27
+ *
28
+ * @param options
29
+ */
30
+ export declare function json<Value>(options: JSONOption<Value>): Promise<void>;
31
+ /**
32
+ * Ensure file is present/absent synchronously with content value initialized or modified with `update`
33
+ *
34
+ * @param options
35
+ */
36
+ export declare function jsonSync<Value>(options: JSONOption<Value>): void;
37
+ //# sourceMappingURL=json.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"json.d.ts","sourceRoot":"","sources":["../src/json.ts"],"names":[],"mappings":";AAEA,MAAM,MAAM,SAAS,GAAG,IAAI,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,SAAS,EAAE,GAAG;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,CAAC;AAEtG,MAAM,WAAW,UAAU,CAAC,CAAC,GAAG,SAAS;IACvC;;OAEG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB;;OAEG;IACH,QAAQ,CAAC,KAAK,EAAE,SAAS,GAAG,QAAQ,CAAC;IACrC;;;;OAIG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,SAAS,KAAK,CAAC,GAAG,SAAS,CAAC;IAC5D;;OAEG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,cAAc,CAAC;CACpC;AAiBD;;;;GAIG;AACH,wBAAsB,IAAI,CAAC,KAAK,EAAE,OAAO,EAAE,UAAU,CAAC,KAAK,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAE3E;AAED;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,OAAO,EAAE,UAAU,CAAC,KAAK,CAAC,GAAG,IAAI,CAEhE"}
package/dist/json.js ADDED
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.jsonSync = exports.json = void 0;
4
+ const file_js_1 = require("./file.js");
5
+ function toFileOption({ update, ...otherOptions }) {
6
+ return {
7
+ ...otherOptions,
8
+ update: update == null
9
+ ? update
10
+ : (content) => {
11
+ const jsonValue = content === '' ? undefined : JSON.parse(content);
12
+ return JSON.stringify(update(jsonValue));
13
+ },
14
+ };
15
+ }
16
+ /**
17
+ * Ensure file is present/absent asynchronously with content value initialized or modified with `update`
18
+ *
19
+ * @param options
20
+ */
21
+ async function json(options) {
22
+ return (0, file_js_1.file)(toFileOption(options));
23
+ }
24
+ exports.json = json;
25
+ /**
26
+ * Ensure file is present/absent synchronously with content value initialized or modified with `update`
27
+ *
28
+ * @param options
29
+ */
30
+ function jsonSync(options) {
31
+ return (0, file_js_1.fileSync)(toFileOption(options));
32
+ }
33
+ exports.jsonSync = jsonSync;
34
+ //# sourceMappingURL=json.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"json.js","sourceRoot":"","sources":["../src/json.ts"],"names":[],"mappings":";;;AAAA,uCAA6D;AAyB7D,SAAS,YAAY,CAAQ,EAAE,MAAM,EAAE,GAAG,YAAY,EAAqB;IACzE,OAAO;QACL,GAAG,YAAY;QAEf,MAAM,EACJ,MAAM,IAAI,IAAI;YACZ,CAAC,CAAC,MAAM;YACR,CAAC,CAAC,CAAC,OAAO,EAAE,EAAE;gBACV,MAAM,SAAS,GAAG,OAAO,KAAK,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAE,IAAI,CAAC,KAAK,CAAC,OAAO,CAAW,CAAC;gBAE9E,OAAO,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC;YAC3C,CAAC;KACR,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACI,KAAK,UAAU,IAAI,CAAQ,OAA0B;IAC1D,OAAO,IAAA,cAAI,EAAC,YAAY,CAAC,OAAO,CAAC,CAAC,CAAC;AACrC,CAAC;AAFD,oBAEC;AAED;;;;GAIG;AACH,SAAgB,QAAQ,CAAQ,OAA0B;IACxD,OAAO,IAAA,kBAAQ,EAAC,YAAY,CAAC,OAAO,CAAC,CAAC,CAAC;AACzC,CAAC;AAFD,4BAEC"}
package/dist/project.d.ts CHANGED
@@ -1,5 +1,21 @@
1
1
  export declare namespace Project {
2
+ /**
3
+ * A type of a file extension
4
+ */
2
5
  type Extension = `.${string}`;
6
+ /**
7
+ * Object hash of all well-known file extension category to file extensions mapping
8
+ */
9
+ interface ExtensionRegistry {
10
+ javascript: readonly Extension[];
11
+ javascriptreact: readonly Extension[];
12
+ typescript: readonly Extension[];
13
+ typescriptreact: readonly Extension[];
14
+ }
15
+ /**
16
+ * A list of "vscode-like" language identifiers (i.e. "javascript", "javascriptreact")
17
+ */
18
+ type LanguageId = keyof ExtensionRegistry;
3
19
  /**
4
20
  * Supported ECMA version
5
21
  *
@@ -9,6 +25,18 @@ export declare namespace Project {
9
25
  * ```
10
26
  */
11
27
  function ecmaVersion(): 2022;
28
+ /**
29
+ * Return a list of extensions
30
+ *
31
+ * @example
32
+ * ```ts
33
+ * Project.queryExtensions(['javascript']); // ['.js', '.cjs', ...]
34
+ * Project.queryExtensions(['typescript', 'typescriptreact']); // ['.ts', '.mts', ..., '.tsx']
35
+ * ```
36
+ *
37
+ * @param languages
38
+ */
39
+ function queryExtensions(languages: LanguageId[]): readonly Extension[];
12
40
  /**
13
41
  * Supported file extensions
14
42
  *
@@ -1 +1 @@
1
- {"version":3,"file":"project.d.ts","sourceRoot":"","sources":["../src/project.ts"],"names":[],"mappings":"AAIA,yBAAiB,OAAO,CAAC;IACvB,KAAY,SAAS,GAAG,IAAI,MAAM,EAAE,CAAC;IAErC;;;;;;;OAOG;IACH,SAAgB,WAAW,SAE1B;IAaD;;;;;;;OAOG;IACH,SAAgB,gBAAgB,4BAE/B;IAkBD;;;;;;;OAOG;IACH,SAAgB,kBAAkB,4BAEjC;IAeD;;;;;;;OAOG;IACH,SAAgB,OAAO,sBAEtB;IAED;;;;;;;OAOG;IACH,SAAgB,mBAAmB,CAAC,UAAU,EAAE,SAAS,SAAS,EAAE,GAAG,MAAM,CAE5E;IAED;;;;;;;OAOG;IACH,SAAgB,gBAAgB,CAAC,UAAU,EAAE,SAAS,SAAS,EAAE,GAAG,MAAM,CAEzE;CACF"}
1
+ {"version":3,"file":"project.d.ts","sourceRoot":"","sources":["../src/project.ts"],"names":[],"mappings":"AAIA,yBAAiB,OAAO,CAAC;IACvB;;OAEG;IACH,KAAY,SAAS,GAAG,IAAI,MAAM,EAAE,CAAC;IAErC;;OAEG;IACH,UAAiB,iBAAiB;QAChC,UAAU,EAAE,SAAS,SAAS,EAAE,CAAC;QACjC,eAAe,EAAE,SAAS,SAAS,EAAE,CAAC;QACtC,UAAU,EAAE,SAAS,SAAS,EAAE,CAAC;QACjC,eAAe,EAAE,SAAS,SAAS,EAAE,CAAC;KACvC;IAED;;OAEG;IACH,KAAY,UAAU,GAAG,MAAM,iBAAiB,CAAC;IAEjD;;;;;;;OAOG;IACH,SAAgB,WAAW,SAE1B;IASD;;;;;;;;;;OAUG;IACH,SAAgB,eAAe,CAAC,SAAS,EAAE,UAAU,EAAE,GAAG,SAAS,SAAS,EAAE,CAO7E;IAED;;;;;;;OAOG;IACH,SAAgB,gBAAgB,4BAE/B;IAkBD;;;;;;;OAOG;IACH,SAAgB,kBAAkB,4BAEjC;IAeD;;;;;;;OAOG;IACH,SAAgB,OAAO,sBAEtB;IAED;;;;;;;OAOG;IACH,SAAgB,mBAAmB,CAAC,UAAU,EAAE,SAAS,SAAS,EAAE,GAAG,MAAM,CAE5E;IAED;;;;;;;OAOG;IACH,SAAgB,gBAAgB,CAAC,UAAU,EAAE,SAAS,SAAS,EAAE,GAAG,MAAM,CAEzE;CACF"}
package/dist/project.js CHANGED
@@ -18,16 +18,29 @@ var Project;
18
18
  return 2022;
19
19
  }
20
20
  Project.ecmaVersion = ecmaVersion;
21
- const SOURCE_EXTENSIONS = Object.freeze([
22
- '.ts',
23
- '.tsx',
24
- '.cts',
25
- '.mts',
26
- '.js',
27
- '.jsx',
28
- '.cjs',
29
- '.mjs',
30
- ]);
21
+ const registry = {
22
+ javascript: ['.js', '.cjs', '.mjs'],
23
+ javascriptreact: ['.jsx'],
24
+ typescript: ['.ts', '.cts', '.mts'],
25
+ typescriptreact: ['.tsx'],
26
+ };
27
+ /**
28
+ * Return a list of extensions
29
+ *
30
+ * @example
31
+ * ```ts
32
+ * Project.queryExtensions(['javascript']); // ['.js', '.cjs', ...]
33
+ * Project.queryExtensions(['typescript', 'typescriptreact']); // ['.ts', '.mts', ..., '.tsx']
34
+ * ```
35
+ *
36
+ * @param languages
37
+ */
38
+ function queryExtensions(languages) {
39
+ return languages
40
+ .reduce((previousValue, currentValue) => previousValue.concat(registry[currentValue] ?? []), [])
41
+ .sort();
42
+ }
43
+ Project.queryExtensions = queryExtensions;
31
44
  /**
32
45
  * Supported file extensions
33
46
  *
@@ -37,7 +50,7 @@ var Project;
37
50
  * ```
38
51
  */
39
52
  function sourceExtensions() {
40
- return SOURCE_EXTENSIONS;
53
+ return queryExtensions(['javascript', 'javascriptreact', 'typescript', 'typescriptreact']);
41
54
  }
42
55
  Project.sourceExtensions = sourceExtensions;
43
56
  const RESOURCE_EXTENSIONS = Object.freeze([
@@ -1 +1 @@
1
- {"version":3,"file":"project.js","sourceRoot":"","sources":["../src/project.ts"],"names":[],"mappings":";;;AAAA,SAAS,YAAY,CAAC,KAAa;IACjC,OAAO,KAAK,CAAC,UAAU,CAAC,qBAAqB,EAAE,MAAM,CAAC,CAAC,CAAC,oCAAoC;AAC9F,CAAC;AAED,IAAiB,OAAO,CAkHvB;AAlHD,WAAiB,OAAO;IAGtB;;;;;;;OAOG;IACH,SAAgB,WAAW;QACzB,OAAO,IAAa,CAAC;IACvB,CAAC;IAFe,mBAAW,cAE1B,CAAA;IAED,MAAM,iBAAiB,GAAyB,MAAM,CAAC,MAAM,CAAC;QAC5D,KAAK;QACL,MAAM;QACN,MAAM;QACN,MAAM;QACN,KAAK;QACL,MAAM;QACN,MAAM;QACN,MAAM;KACP,CAAC,CAAC;IAEH;;;;;;;OAOG;IACH,SAAgB,gBAAgB;QAC9B,OAAO,iBAAiB,CAAC;IAC3B,CAAC;IAFe,wBAAgB,mBAE/B,CAAA;IAED,MAAM,mBAAmB,GAAyB,MAAM,CAAC,MAAM,CAAC;QAC9D,MAAM;QACN,OAAO;QACP,OAAO;QACP,OAAO;QACP,MAAM;QACN,MAAM;QACN,MAAM;QACN,OAAO;QACP,MAAM;QACN,MAAM;QACN,UAAU;QACV,MAAM;QACN,OAAO;KACR,CAAC,CAAC;IAEH;;;;;;;OAOG;IACH,SAAgB,kBAAkB;QAChC,OAAO,mBAAmB,CAAC;IAC7B,CAAC;IAFe,0BAAkB,qBAEjC,CAAA;IAED,MAAM,OAAO,GAAG,MAAM,CAAC,MAAM,CAAC;QAC5B,eAAe;QACf,QAAQ;QACR,MAAM;QACN,WAAW;QACX,OAAO;QACP,MAAM;QACN,MAAM;QACN,MAAM;QACN,MAAM;QACN,MAAM;KACP,CAAC,CAAC;IAEH;;;;;;;OAOG;IACH,SAAgB,OAAO;QACrB,OAAO,OAAO,CAAC;IACjB,CAAC;IAFe,eAAO,UAEtB,CAAA;IAED;;;;;;;OAOG;IACH,SAAgB,mBAAmB,CAAC,UAAgC;QAClE,OAAO,IAAI,MAAM,CAAC,IAAI,UAAU,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IACpE,CAAC;IAFe,2BAAmB,sBAElC,CAAA;IAED;;;;;;;OAOG;IACH,SAAgB,gBAAgB,CAAC,UAAgC;QAC/D,OAAO,OAAO,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;IACzE,CAAC;IAFe,wBAAgB,mBAE/B,CAAA;AACH,CAAC,EAlHgB,OAAO,uBAAP,OAAO,QAkHvB"}
1
+ {"version":3,"file":"project.js","sourceRoot":"","sources":["../src/project.ts"],"names":[],"mappings":";;;AAAA,SAAS,YAAY,CAAC,KAAa;IACjC,OAAO,KAAK,CAAC,UAAU,CAAC,qBAAqB,EAAE,MAAM,CAAC,CAAC,CAAC,oCAAoC;AAC9F,CAAC;AAED,IAAiB,OAAO,CAoJvB;AApJD,WAAiB,OAAO;IAqBtB;;;;;;;OAOG;IACH,SAAgB,WAAW;QACzB,OAAO,IAAa,CAAC;IACvB,CAAC;IAFe,mBAAW,cAE1B,CAAA;IAED,MAAM,QAAQ,GAA4C;QACxD,UAAU,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC;QACnC,eAAe,EAAE,CAAC,MAAM,CAAC;QACzB,UAAU,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC;QACnC,eAAe,EAAE,CAAC,MAAM,CAAC;KAC1B,CAAC;IAEF;;;;;;;;;;OAUG;IACH,SAAgB,eAAe,CAAC,SAAuB;QACrD,OAAO,SAAS;aACb,MAAM,CACL,CAAC,aAAa,EAAE,YAAY,EAAE,EAAE,CAAC,aAAa,CAAC,MAAM,CAAC,QAAQ,CAAC,YAAY,CAAC,IAAK,EAAkB,CAAC,EACpG,EAAE,CACH;aACA,IAAI,EAAE,CAAC;IACZ,CAAC;IAPe,uBAAe,kBAO9B,CAAA;IAED;;;;;;;OAOG;IACH,SAAgB,gBAAgB;QAC9B,OAAO,eAAe,CAAC,CAAC,YAAY,EAAE,iBAAiB,EAAE,YAAY,EAAE,iBAAiB,CAAC,CAAC,CAAC;IAC7F,CAAC;IAFe,wBAAgB,mBAE/B,CAAA;IAED,MAAM,mBAAmB,GAAyB,MAAM,CAAC,MAAM,CAAC;QAC9D,MAAM;QACN,OAAO;QACP,OAAO;QACP,OAAO;QACP,MAAM;QACN,MAAM;QACN,MAAM;QACN,OAAO;QACP,MAAM;QACN,MAAM;QACN,UAAU;QACV,MAAM;QACN,OAAO;KACR,CAAC,CAAC;IAEH;;;;;;;OAOG;IACH,SAAgB,kBAAkB;QAChC,OAAO,mBAAmB,CAAC;IAC7B,CAAC;IAFe,0BAAkB,qBAEjC,CAAA;IAED,MAAM,OAAO,GAAG,MAAM,CAAC,MAAM,CAAC;QAC5B,eAAe;QACf,QAAQ;QACR,MAAM;QACN,WAAW;QACX,OAAO;QACP,MAAM;QACN,MAAM;QACN,MAAM;QACN,MAAM;QACN,MAAM;KACP,CAAC,CAAC;IAEH;;;;;;;OAOG;IACH,SAAgB,OAAO;QACrB,OAAO,OAAO,CAAC;IACjB,CAAC;IAFe,eAAO,UAEtB,CAAA;IAED;;;;;;;OAOG;IACH,SAAgB,mBAAmB,CAAC,UAAgC;QAClE,OAAO,IAAI,MAAM,CAAC,IAAI,UAAU,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IACpE,CAAC;IAFe,2BAAmB,sBAElC,CAAA;IAED;;;;;;;OAOG;IACH,SAAgB,gBAAgB,CAAC,UAAgC;QAC/D,OAAO,OAAO,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;IACzE,CAAC;IAFe,wBAAgB,mBAE/B,CAAA;AACH,CAAC,EApJgB,OAAO,uBAAP,OAAO,QAoJvB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@w5s/dev",
3
- "version": "1.3.2",
3
+ "version": "1.4.0",
4
4
  "description": "Shared development constants and functions",
5
5
  "keywords": [
6
6
  "config",
@@ -43,5 +43,5 @@
43
43
  "publishConfig": {
44
44
  "access": "public"
45
45
  },
46
- "gitHead": "7c7006522408a5ab144f8def71edab7fface1e18"
46
+ "gitHead": "40aa9e728c83ee50a5a381817c59df91458cf6a0"
47
47
  }
package/src/block.ts ADDED
@@ -0,0 +1,124 @@
1
+ import { type FileOptions, file, fileSync } from './file.js';
2
+
3
+ export interface BlockOptions {
4
+ /**
5
+ * The marker builder function that will take either `markerBegin` or `markerEnd`
6
+ *
7
+ * @default '# ${mark} MANAGED BLOCK'
8
+ */
9
+ marker?: (mark: 'Begin' | 'End') => string;
10
+ /**
11
+ * File path
12
+ */
13
+ path: string;
14
+ /**
15
+ * Block content to insert
16
+ */
17
+ block: string;
18
+ /**
19
+ * Insert position
20
+ */
21
+ insertPosition?: ['before', 'BeginningOfFile'] | ['after', 'EndOfFile'];
22
+ /**
23
+ * Block target state
24
+ */
25
+ state?: 'present' | 'absent';
26
+ }
27
+
28
+ const EOF = 'EndOfFile';
29
+ const BOF = 'BeginningOfFile';
30
+
31
+ function toFileOptions(options: BlockOptions): FileOptions {
32
+ const {
33
+ marker = (mark) => `# ${mark.toUpperCase()} MANAGED BLOCK`,
34
+ path,
35
+ block: blockName,
36
+ insertPosition = ['after', EOF],
37
+ state = 'present',
38
+ } = options;
39
+
40
+ const EOL = '\n';
41
+ const beginBlock = marker('Begin');
42
+ const endBlock = marker('End');
43
+
44
+ /**
45
+ * @param content
46
+ */
47
+ function findBlock(content: string) {
48
+ const startIndex = content.indexOf(beginBlock);
49
+ const endIndex = content.indexOf(endBlock) + endBlock.length;
50
+
51
+ return {
52
+ endIndex,
53
+ exists: startIndex >= 0 && endIndex >= 0,
54
+ startIndex,
55
+ };
56
+ }
57
+
58
+ function apply(fullContent: string, blockContent: string) {
59
+ const found = findBlock(fullContent);
60
+ const remove = state === 'absent';
61
+ const replaceBlock = remove ? '' : beginBlock + EOL + blockContent + EOL + endBlock;
62
+ const [positionDirection, positionAnchor] = insertPosition;
63
+
64
+ if (found.exists) {
65
+ return fullContent.slice(0, found.startIndex) + replaceBlock + fullContent.slice(found.endIndex);
66
+ }
67
+ if (remove) {
68
+ return fullContent;
69
+ }
70
+ switch (positionDirection) {
71
+ case 'after': {
72
+ // insert
73
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
74
+ if (positionAnchor === EOF) {
75
+ return fullContent + EOL + replaceBlock;
76
+ }
77
+
78
+ return fullContent;
79
+ }
80
+ case 'before': {
81
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
82
+ if (positionAnchor === BOF) {
83
+ return replaceBlock + EOL + fullContent;
84
+ }
85
+ return fullContent;
86
+ }
87
+ default: {
88
+ throw new Error(`Unsupported position ${String(positionDirection)}`);
89
+ }
90
+ }
91
+ }
92
+
93
+ return {
94
+ path,
95
+ state: 'present',
96
+ update: (sourceContent) => apply(sourceContent, blockName),
97
+ };
98
+ }
99
+
100
+ /**
101
+ * Replace asynchronously a block in file that follows pattern :
102
+ *
103
+ * marker(markerBegin)
104
+ * ...
105
+ * marker(markerEnd)
106
+ *
107
+ * @param options
108
+ */
109
+ export function block(options: BlockOptions) {
110
+ return file(toFileOptions(options));
111
+ }
112
+
113
+ /**
114
+ * Replace synchronously a block in file that follows pattern :
115
+ *
116
+ * marker(markerBegin)
117
+ * ...
118
+ * marker(markerEnd)
119
+ *
120
+ * @param options
121
+ */
122
+ export function blockSync(options: BlockOptions) {
123
+ return fileSync(toFileOptions(options));
124
+ }
package/src/file.ts ADDED
@@ -0,0 +1,97 @@
1
+ import { readFile, rm, writeFile, access } from 'node:fs/promises';
2
+ import { accessSync, constants, readFileSync, rmSync, writeFileSync } from 'node:fs';
3
+
4
+ async function exists(path: string) {
5
+ try {
6
+ await access(path, constants.F_OK);
7
+ return true;
8
+ } catch {
9
+ return false;
10
+ }
11
+ }
12
+
13
+ function existsSync(path: string) {
14
+ try {
15
+ accessSync(path, constants.F_OK);
16
+ return true;
17
+ } catch {
18
+ return false;
19
+ }
20
+ }
21
+
22
+ export interface FileOptions {
23
+ /**
24
+ * File path
25
+ */
26
+ readonly path: string;
27
+ /**
28
+ * File target state
29
+ */
30
+ readonly state: 'present' | 'absent';
31
+ /**
32
+ * File content mapping function
33
+ *
34
+ * @param content
35
+ */
36
+ readonly update?: (content: string) => string | undefined;
37
+ /**
38
+ * File encoding
39
+ */
40
+ readonly encoding?: BufferEncoding;
41
+ }
42
+
43
+ /**
44
+ * Ensure file is present/absent with content initialized or modified with `update
45
+ *
46
+ * @example
47
+ * ```ts
48
+ * await file({
49
+ * path: 'foo/bar',
50
+ * state: 'present',
51
+ * update: (content) => content + '_test', // This will append '_test' after current content
52
+ * })
53
+ * ```
54
+ *
55
+ * @param options
56
+ */
57
+ export async function file(options: FileOptions): Promise<void> {
58
+ const { path, state, update, encoding = 'utf8' } = options;
59
+ if (state === 'present') {
60
+ const isPresent = await exists(path);
61
+ const previousContent = isPresent ? await readFile(path, encoding) : '';
62
+ const newContent = update == null ? '' : update(previousContent);
63
+ if (newContent != null) {
64
+ await writeFile(path, newContent, encoding);
65
+ }
66
+ } else {
67
+ await rm(path);
68
+ }
69
+ }
70
+
71
+ /**
72
+ * Ensure file is present/absent with content initialized or modified with `update
73
+ *
74
+ * @example
75
+ * ```ts
76
+ * fileSync({
77
+ * path: 'foo/bar',
78
+ * state: 'present',
79
+ * update: (content) => content + '_test', // This will append '_test' after current content
80
+ * })
81
+ * ```
82
+ *
83
+ * @param options
84
+ */
85
+ export function fileSync(options: FileOptions): void {
86
+ const { path, state, update, encoding = 'utf8' } = options;
87
+ if (state === 'present') {
88
+ const isPresent = existsSync(path);
89
+ const previousContent = isPresent ? readFileSync(path, encoding) : '';
90
+ const newContent = update == null ? '' : update(previousContent);
91
+ if (newContent != null) {
92
+ writeFileSync(path, newContent, encoding);
93
+ }
94
+ } else {
95
+ rmSync(path);
96
+ }
97
+ }
package/src/index.ts CHANGED
@@ -1,2 +1,5 @@
1
1
  export * from './eslint.js';
2
+ export * from './block.js';
3
+ export * from './file.js';
4
+ export * from './json.js';
2
5
  export * from './project.js';
package/src/json.ts ADDED
@@ -0,0 +1,57 @@
1
+ import { type FileOptions, file, fileSync } from './file.js';
2
+
3
+ export type JSONValue = null | number | string | boolean | JSONValue[] | { [key: string]: JSONValue };
4
+
5
+ export interface JSONOption<V = JSONValue> {
6
+ /**
7
+ * File path
8
+ */
9
+ readonly path: string;
10
+ /**
11
+ * File target state
12
+ */
13
+ readonly state: 'present' | 'absent';
14
+ /**
15
+ * File content mapping function
16
+ *
17
+ * @param content
18
+ */
19
+ readonly update?: (content: V | undefined) => V | undefined;
20
+ /**
21
+ * File encoding
22
+ */
23
+ readonly encoding?: BufferEncoding;
24
+ }
25
+
26
+ function toFileOption<Value>({ update, ...otherOptions }: JSONOption<Value>): FileOptions {
27
+ return {
28
+ ...otherOptions,
29
+
30
+ update:
31
+ update == null
32
+ ? update
33
+ : (content) => {
34
+ const jsonValue = content === '' ? undefined : (JSON.parse(content) as Value);
35
+
36
+ return JSON.stringify(update(jsonValue));
37
+ },
38
+ };
39
+ }
40
+
41
+ /**
42
+ * Ensure file is present/absent asynchronously with content value initialized or modified with `update`
43
+ *
44
+ * @param options
45
+ */
46
+ export async function json<Value>(options: JSONOption<Value>): Promise<void> {
47
+ return file(toFileOption(options));
48
+ }
49
+
50
+ /**
51
+ * Ensure file is present/absent synchronously with content value initialized or modified with `update`
52
+ *
53
+ * @param options
54
+ */
55
+ export function jsonSync<Value>(options: JSONOption<Value>): void {
56
+ return fileSync(toFileOption(options));
57
+ }
package/src/project.ts CHANGED
@@ -3,8 +3,26 @@ function escapeRegExp(value: string) {
3
3
  }
4
4
 
5
5
  export namespace Project {
6
+ /**
7
+ * A type of a file extension
8
+ */
6
9
  export type Extension = `.${string}`;
7
10
 
11
+ /**
12
+ * Object hash of all well-known file extension category to file extensions mapping
13
+ */
14
+ export interface ExtensionRegistry {
15
+ javascript: readonly Extension[];
16
+ javascriptreact: readonly Extension[];
17
+ typescript: readonly Extension[];
18
+ typescriptreact: readonly Extension[];
19
+ }
20
+
21
+ /**
22
+ * A list of "vscode-like" language identifiers (i.e. "javascript", "javascriptreact")
23
+ */
24
+ export type LanguageId = keyof ExtensionRegistry;
25
+
8
26
  /**
9
27
  * Supported ECMA version
10
28
  *
@@ -17,16 +35,32 @@ export namespace Project {
17
35
  return 2022 as const;
18
36
  }
19
37
 
20
- const SOURCE_EXTENSIONS: readonly Extension[] = Object.freeze([
21
- '.ts',
22
- '.tsx',
23
- '.cts',
24
- '.mts',
25
- '.js',
26
- '.jsx',
27
- '.cjs',
28
- '.mjs',
29
- ]);
38
+ const registry: { [key: string]: readonly Extension[] } = {
39
+ javascript: ['.js', '.cjs', '.mjs'],
40
+ javascriptreact: ['.jsx'],
41
+ typescript: ['.ts', '.cts', '.mts'],
42
+ typescriptreact: ['.tsx'],
43
+ };
44
+
45
+ /**
46
+ * Return a list of extensions
47
+ *
48
+ * @example
49
+ * ```ts
50
+ * Project.queryExtensions(['javascript']); // ['.js', '.cjs', ...]
51
+ * Project.queryExtensions(['typescript', 'typescriptreact']); // ['.ts', '.mts', ..., '.tsx']
52
+ * ```
53
+ *
54
+ * @param languages
55
+ */
56
+ export function queryExtensions(languages: LanguageId[]): readonly Extension[] {
57
+ return languages
58
+ .reduce<Extension[]>(
59
+ (previousValue, currentValue) => previousValue.concat(registry[currentValue] ?? ([] as Extension[])),
60
+ []
61
+ )
62
+ .sort();
63
+ }
30
64
 
31
65
  /**
32
66
  * Supported file extensions
@@ -37,7 +71,7 @@ export namespace Project {
37
71
  * ```
38
72
  */
39
73
  export function sourceExtensions() {
40
- return SOURCE_EXTENSIONS;
74
+ return queryExtensions(['javascript', 'javascriptreact', 'typescript', 'typescriptreact']);
41
75
  }
42
76
 
43
77
  const RESOURCE_EXTENSIONS: readonly Extension[] = Object.freeze([