@w5s/configurator-core 1.0.0-alpha.8 → 1.0.0-alpha.9

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@w5s/configurator-core",
3
- "version": "1.0.0-alpha.8",
3
+ "version": "1.0.0-alpha.9",
4
4
  "description": "Core library for @w5s/configurator-core",
5
5
  "keywords": [
6
6
  "config",
@@ -36,7 +36,9 @@
36
36
  "scripts": {
37
37
  "postpack": "clean-package restore"
38
38
  },
39
- "dependencies": {},
39
+ "dependencies": {
40
+ "yaml": "^2.9.0"
41
+ },
40
42
  "engines": {
41
43
  "node": ">=22.0.0"
42
44
  },
@@ -44,5 +46,5 @@
44
46
  "access": "public"
45
47
  },
46
48
  "sideEffect": false,
47
- "gitHead": "fcde4af12d29afdb46ed396ef832c36a399ed057"
49
+ "gitHead": "e6c63e0164ce2199e59bdca360f4766b6a78584d"
48
50
  }
package/src/FileMode.ts CHANGED
@@ -1,33 +1,33 @@
1
- export interface FilePermissionSet {
1
+ export interface FileMode {
2
2
  /**
3
- * Read permission
3
+ * Group permissions
4
4
  */
5
- readonly read?: boolean;
5
+ readonly group?: FilePermissionSet;
6
6
 
7
7
  /**
8
- * Write permission
8
+ * Other permissions
9
9
  */
10
- readonly write?: boolean;
10
+ readonly other?: FilePermissionSet;
11
11
 
12
12
  /**
13
- * Execute permission
13
+ * Owner permissions
14
14
  */
15
- readonly execute?: boolean;
15
+ readonly owner?: FilePermissionSet;
16
16
  }
17
17
 
18
- export interface FileMode {
18
+ export interface FilePermissionSet {
19
19
  /**
20
- * Owner permissions
20
+ * Execute permission
21
21
  */
22
- readonly owner?: FilePermissionSet;
22
+ readonly execute?: boolean;
23
23
 
24
24
  /**
25
- * Group permissions
25
+ * Read permission
26
26
  */
27
- readonly group?: FilePermissionSet;
27
+ readonly read?: boolean;
28
28
 
29
29
  /**
30
- * Other permissions
30
+ * Write permission
31
31
  */
32
- readonly other?: FilePermissionSet;
32
+ readonly write?: boolean;
33
33
  }
package/src/__exists.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { access } from 'node:fs/promises';
2
1
  import { constants } from 'node:fs';
2
+ import { access } from 'node:fs/promises';
3
3
 
4
4
  export async function __exists(path: string) {
5
5
  try {
package/src/__toMode.ts CHANGED
@@ -1,13 +1,5 @@
1
1
  import type { FileMode, FilePermissionSet } from './FileMode.js';
2
2
 
3
- function toModeFlag(permissionSet: FilePermissionSet | undefined, read: number, write: number, execute: number): number {
4
- return (
5
- (permissionSet?.read === true ? read : 0)
6
- | (permissionSet?.write === true ? write : 0)
7
- | (permissionSet?.execute === true ? execute : 0)
8
- );
9
- }
10
-
11
3
  export function __toMode(mode: FileMode | undefined): number | undefined {
12
4
  return mode == null
13
5
  ? mode
@@ -17,3 +9,11 @@ export function __toMode(mode: FileMode | undefined): number | undefined {
17
9
  | toModeFlag(mode.other, 0o004, 0o002, 0o001)
18
10
  );
19
11
  }
12
+
13
+ function toModeFlag(permissionSet: FilePermissionSet | undefined, read: number, write: number, execute: number): number {
14
+ return (
15
+ (permissionSet?.read === true ? read : 0)
16
+ | (permissionSet?.write === true ? write : 0)
17
+ | (permissionSet?.execute === true ? execute : 0)
18
+ );
19
+ }
package/src/block.ts CHANGED
@@ -1,32 +1,32 @@
1
- import { type FileOptions, file, fileSync } from './file.js';
1
+ import { file, type FileOptions, fileSync } from './file.js';
2
2
 
3
3
  export interface BlockOptions {
4
4
  /**
5
- * The marker builder function that will take either `markerBegin` or `markerEnd`
6
- *
7
- * @default '# ${mark} MANAGED BLOCK'
5
+ * Block content to insert
8
6
  */
9
- marker?: (mark: 'Begin' | 'End') => string;
7
+ block: string;
10
8
 
11
9
  /**
12
- * File path
10
+ * Insert position
13
11
  */
14
- path: string;
12
+ insertPosition?: ['after', 'EndOfFile' | RegExp] | ['before', 'BeginningOfFile' | RegExp];
15
13
 
16
14
  /**
17
- * Block content to insert
15
+ * The marker builder function that will take either `markerBegin` or `markerEnd`
16
+ *
17
+ * @default '# ${mark} MANAGED BLOCK'
18
18
  */
19
- block: string;
19
+ marker?: (mark: 'Begin' | 'End') => string;
20
20
 
21
21
  /**
22
- * Insert position
22
+ * File path
23
23
  */
24
- insertPosition?: ['before', 'BeginningOfFile' | RegExp] | ['after', 'EndOfFile' | RegExp];
24
+ path: string;
25
25
 
26
26
  /**
27
27
  * Block target state
28
28
  */
29
- state?: 'present' | 'absent';
29
+ state?: 'absent' | 'present';
30
30
  }
31
31
 
32
32
  const EOF = 'EndOfFile';
@@ -49,12 +49,38 @@ const matchLast = (string: string, regexp: RegExp) => {
49
49
  return { firstIndex, lastIndex };
50
50
  };
51
51
 
52
+ /**
53
+ * Replace asynchronously a block in file that follows pattern :
54
+ *
55
+ * marker(markerBegin)
56
+ * ...
57
+ * marker(markerEnd)
58
+ *
59
+ * @param options
60
+ */
61
+ export function block(options: BlockOptions) {
62
+ return file(toFileOptions(options));
63
+ }
64
+
65
+ /**
66
+ * Replace synchronously a block in file that follows pattern :
67
+ *
68
+ * marker(markerBegin)
69
+ * ...
70
+ * marker(markerEnd)
71
+ *
72
+ * @param options
73
+ */
74
+ export function blockSync(options: BlockOptions) {
75
+ return fileSync(toFileOptions(options));
76
+ }
77
+
52
78
  function toFileOptions(options: BlockOptions): FileOptions {
53
79
  const {
54
- marker = (mark) => `# ${mark.toUpperCase()} MANAGED BLOCK`,
55
- path,
56
80
  block: blockName,
57
81
  insertPosition = ['after', EOF],
82
+ marker = (mark) => `# ${mark.toUpperCase()} MANAGED BLOCK`,
83
+ path,
58
84
  state = 'present',
59
85
  } = options;
60
86
 
@@ -89,17 +115,6 @@ function toFileOptions(options: BlockOptions): FileOptions {
89
115
  return fullContent;
90
116
  }
91
117
  switch (positionDirection) {
92
- case 'before': {
93
- if (positionAnchor !== BOF) {
94
- const { firstIndex } = matchLast(fullContent, positionAnchor);
95
- if (firstIndex >= 0) {
96
- return insertAt(fullContent, firstIndex, replaceBlock + EOL);
97
- }
98
- }
99
-
100
- // Beginning of file
101
- return replaceBlock + EOL + fullContent;
102
- }
103
118
  case 'after': {
104
119
  // insert
105
120
  if (positionAnchor !== EOF) {
@@ -112,6 +127,17 @@ function toFileOptions(options: BlockOptions): FileOptions {
112
127
  // end of file
113
128
  return fullContent + EOL + replaceBlock;
114
129
  }
130
+ case 'before': {
131
+ if (positionAnchor !== BOF) {
132
+ const { firstIndex } = matchLast(fullContent, positionAnchor);
133
+ if (firstIndex >= 0) {
134
+ return insertAt(fullContent, firstIndex, replaceBlock + EOL);
135
+ }
136
+ }
137
+
138
+ // Beginning of file
139
+ return replaceBlock + EOL + fullContent;
140
+ }
115
141
 
116
142
  default: {
117
143
  throw new Error(`Unsupported position ${String(positionDirection)}`);
@@ -125,29 +151,3 @@ function toFileOptions(options: BlockOptions): FileOptions {
125
151
  update: (sourceContent) => apply(sourceContent, blockName),
126
152
  };
127
153
  }
128
-
129
- /**
130
- * Replace asynchronously a block in file that follows pattern :
131
- *
132
- * marker(markerBegin)
133
- * ...
134
- * marker(markerEnd)
135
- *
136
- * @param options
137
- */
138
- export function block(options: BlockOptions) {
139
- return file(toFileOptions(options));
140
- }
141
-
142
- /**
143
- * Replace synchronously a block in file that follows pattern :
144
- *
145
- * marker(markerBegin)
146
- * ...
147
- * marker(markerEnd)
148
- *
149
- * @param options
150
- */
151
- export function blockSync(options: BlockOptions) {
152
- return fileSync(toFileOptions(options));
153
- }
package/src/directory.ts CHANGED
@@ -1,25 +1,27 @@
1
1
  import { chmodSync, mkdirSync, rmSync } from 'node:fs';
2
2
  import { chmod, mkdir, rm } from 'node:fs/promises';
3
- import { __exists } from './__exists.js';
3
+
4
4
  import type { FileMode } from './FileMode.js';
5
- import { __toMode } from './__toMode.js';
5
+
6
+ import { __exists } from './__exists.js';
6
7
  import { __existsSync } from './__existsSync.js';
8
+ import { __toMode } from './__toMode.js';
7
9
 
8
10
  export interface DirectoryOptions {
9
11
  /**
10
- * Directory path
12
+ * File permissions
11
13
  */
12
- readonly path: string;
14
+ readonly mode?: FileMode;
13
15
 
14
16
  /**
15
- * Directory target state
17
+ * Directory path
16
18
  */
17
- readonly state: 'present' | 'absent';
19
+ readonly path: string;
18
20
 
19
21
  /**
20
- * File permissions
22
+ * Directory target state
21
23
  */
22
- readonly mode?: FileMode;
24
+ readonly state: 'absent' | 'present';
23
25
  }
24
26
 
25
27
  /**
@@ -41,12 +43,12 @@ export interface DirectoryOptions {
41
43
  * @param options
42
44
  */
43
45
  export async function directory(options: DirectoryOptions): Promise<void> {
44
- const { path, state, mode } = options;
46
+ const { mode, path, state } = options;
45
47
  const isPresent = await __exists(path);
46
48
  if (state === 'present') {
47
49
  const newMode = __toMode(mode);
48
50
  if (!isPresent) {
49
- await mkdir(path, { recursive: true, mode: newMode });
51
+ await mkdir(path, { mode: newMode, recursive: true });
50
52
  }
51
53
  if (newMode != null && isPresent) {
52
54
  await chmod(path, newMode);
@@ -75,12 +77,12 @@ export async function directory(options: DirectoryOptions): Promise<void> {
75
77
  * @param options
76
78
  */
77
79
  export function directorySync(options: DirectoryOptions): void {
78
- const { path, state, mode } = options;
80
+ const { mode, path, state } = options;
79
81
  const isPresent = __existsSync(path);
80
82
  if (state === 'present') {
81
83
  const newMode = __toMode(mode);
82
84
  if (!isPresent) {
83
- mkdirSync(path, { recursive: true, mode: newMode });
85
+ mkdirSync(path, { mode: newMode, recursive: true });
84
86
  }
85
87
  if (newMode != null && isPresent) {
86
88
  chmodSync(path, newMode);
package/src/exec.ts CHANGED
@@ -9,27 +9,7 @@ export interface ExecOptions {
9
9
  /**
10
10
  * Stdio options
11
11
  */
12
- stdio?: 'inherit' | 'pipe' | 'ignore';
13
- }
14
-
15
- /**
16
- * Runs a command in a shell and returns a promise that resolves with an object
17
- * containing the stdout and stderr strings.
18
- *
19
- * @param command The command to run
20
- * @param args The arguments to pass to the command
21
- * @param options
22
- * @returns A promise that resolves with an object like `{ stdout: string, stderr: string }`
23
- */
24
- export function execSync(
25
- command: string,
26
- args: ReadonlyArray<string>,
27
- options?: ExecOptions,
28
- ): { stdout: string; stderr: string } {
29
- const result = spawnSync(command, args, { ...options });
30
- const encoding = 'utf8';
31
-
32
- return { stdout: result.stdout.toString(encoding), stderr: result.stderr.toString(encoding) };
12
+ stdio?: 'ignore' | 'inherit' | 'pipe';
33
13
  }
34
14
 
35
15
  /**
@@ -44,7 +24,7 @@ export async function exec(
44
24
  command: string,
45
25
  args: ReadonlyArray<string>,
46
26
  options?: ExecOptions,
47
- ): Promise<{ stdout: string; stderr: string }> {
27
+ ): Promise<{ stderr: string; stdout: string }> {
48
28
  return new Promise((resolve, reject) => {
49
29
  const encoding = 'utf8';
50
30
  const child = spawn(command, args, { ...options });
@@ -64,10 +44,30 @@ export async function exec(
64
44
  }
65
45
  // Handle process exit
66
46
  child.on('close', (_code) => {
67
- resolve({ stdout, stderr });
47
+ resolve({ stderr, stdout });
68
48
  });
69
49
 
70
50
  // Handle errors
71
51
  child.on('error', reject);
72
52
  });
73
53
  }
54
+
55
+ /**
56
+ * Runs a command in a shell and returns a promise that resolves with an object
57
+ * containing the stdout and stderr strings.
58
+ *
59
+ * @param command The command to run
60
+ * @param args The arguments to pass to the command
61
+ * @param options
62
+ * @returns A promise that resolves with an object like `{ stdout: string, stderr: string }`
63
+ */
64
+ export function execSync(
65
+ command: string,
66
+ args: ReadonlyArray<string>,
67
+ options?: ExecOptions,
68
+ ): { stderr: string; stdout: string } {
69
+ const result = spawnSync(command, args, { ...options });
70
+ const encoding = 'utf8';
71
+
72
+ return { stderr: result.stderr.toString(encoding), stdout: result.stdout.toString(encoding) };
73
+ }
package/src/file.ts CHANGED
@@ -1,36 +1,38 @@
1
- import { chmod, readFile, rm, writeFile } from 'node:fs/promises';
2
1
  import { chmodSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
2
+ import { chmod, readFile, rm, writeFile } from 'node:fs/promises';
3
+
4
+ import type { FileMode } from './FileMode.js';
5
+
3
6
  import { __exists } from './__exists.js';
4
7
  import { __existsSync } from './__existsSync.js';
5
- import type { FileMode } from './FileMode.js';
6
8
  import { __toMode } from './__toMode.js';
7
9
 
8
10
  export interface FileOptions {
9
11
  /**
10
- * File path
12
+ * File encoding
11
13
  */
12
- readonly path: string;
14
+ readonly encoding?: BufferEncoding;
13
15
 
14
16
  /**
15
- * File target state
17
+ * File permissions
16
18
  */
17
- readonly state: 'present' | 'absent';
19
+ readonly mode?: FileMode;
18
20
 
19
21
  /**
20
- * File content mapping function
21
- *
22
+ * File path
22
23
  */
23
- readonly update?: ((content: string) => string | undefined) | undefined;
24
+ readonly path: string;
24
25
 
25
26
  /**
26
- * File encoding
27
+ * File target state
27
28
  */
28
- readonly encoding?: BufferEncoding;
29
+ readonly state: 'absent' | 'present';
29
30
 
30
31
  /**
31
- * File permissions
32
+ * File content mapping function
33
+ *
32
34
  */
33
- readonly mode?: FileMode;
35
+ readonly update?: ((content: string) => string | undefined) | undefined;
34
36
  }
35
37
 
36
38
  /**
@@ -53,7 +55,7 @@ export interface FileOptions {
53
55
  * @param options
54
56
  */
55
57
  export async function file(options: FileOptions): Promise<void> {
56
- const { path, state, update, encoding = 'utf8', mode } = options;
58
+ const { encoding = 'utf8', mode, path, state, update } = options;
57
59
  if (state === 'present') {
58
60
  const isPresent = await __exists(path);
59
61
  const previousContent = isPresent ? await readFile(path, encoding) : '';
@@ -90,7 +92,7 @@ export async function file(options: FileOptions): Promise<void> {
90
92
  * @param options
91
93
  */
92
94
  export function fileSync(options: FileOptions): void {
93
- const { path, state, update, encoding = 'utf8', mode } = options;
95
+ const { encoding = 'utf8', mode, path, state, update } = options;
94
96
  if (state === 'present') {
95
97
  const isPresent = __existsSync(path);
96
98
  const previousContent = isPresent ? readFileSync(path, encoding) : '';
package/src/ignoreFile.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { type FileOptions, file, fileSync } from './file.js';
1
+ import { file, type FileOptions, fileSync } from './file.js';
2
2
 
3
3
  export interface IgnoreFileOptions extends Omit<FileOptions, 'update'> {
4
4
  /**
@@ -8,23 +8,6 @@ export interface IgnoreFileOptions extends Omit<FileOptions, 'update'> {
8
8
  readonly update?: ((content: string[] | undefined) => string[] | undefined) | undefined;
9
9
  }
10
10
 
11
- function toFileOption({ update, ...otherOptions }: IgnoreFileOptions): FileOptions {
12
- return {
13
- ...otherOptions,
14
-
15
- update:
16
- update == null
17
- ? update
18
- : (content) => {
19
- const eol = content.includes('\r\n') ? '\r\n' : '\n';
20
- const lines = content === '' ? undefined : content.split(eol);
21
- const updatedLines = update(lines);
22
-
23
- return updatedLines == null ? undefined : updatedLines.join(eol);
24
- },
25
- };
26
- }
27
-
28
11
  /**
29
12
  * Ensure file is present/absent asynchronously with content initialized or modified by line.
30
13
  *
@@ -56,3 +39,20 @@ export async function ignoreFile(options: IgnoreFileOptions): Promise<void> {
56
39
  export function ignoreFileSync(options: IgnoreFileOptions): void {
57
40
  return fileSync(toFileOption(options));
58
41
  }
42
+
43
+ function toFileOption({ update, ...otherOptions }: IgnoreFileOptions): FileOptions {
44
+ return {
45
+ ...otherOptions,
46
+
47
+ update:
48
+ update == null
49
+ ? update
50
+ : (content) => {
51
+ const eol = content.includes('\r\n') ? '\r\n' : '\n';
52
+ const lines = content === '' ? undefined : content.split(eol);
53
+ const updatedLines = update(lines);
54
+
55
+ return updatedLines == null ? undefined : updatedLines.join(eol);
56
+ },
57
+ };
58
+ }
package/src/index.ts CHANGED
@@ -1,8 +1,9 @@
1
- export * from './directory.js';
2
1
  export * from './block.js';
2
+ export * from './directory.js';
3
3
  export * from './file.js';
4
4
  export * from './ignoreFile.js';
5
5
  export * from './json.js';
6
6
  export * from './meta.js';
7
+ export * from './yaml.js';
7
8
  export * from './yarnConfig.js';
8
9
  export * from './yarnVersion.js';
package/src/json.ts CHANGED
@@ -1,28 +1,13 @@
1
- import { type FileOptions, file, fileSync } from './file.js';
2
-
3
- export type JSONValue = null | number | string | boolean | JSONValue[] | { [key: string]: JSONValue };
1
+ import { file, type FileOptions, fileSync } from './file.js';
4
2
 
5
3
  export interface JSONOption<V = JSONValue> extends Omit<FileOptions, 'update'> {
6
4
  /**
7
5
  * File content mapping function
8
6
  */
9
- readonly update?: ((content: V | undefined) => V | undefined) | undefined;
7
+ readonly update?: ((content: undefined | V) => undefined | V) | undefined;
10
8
  }
11
9
 
12
- function toFileOption<Value>({ update, ...otherOptions }: JSONOption<Value>): FileOptions {
13
- return {
14
- ...otherOptions,
15
-
16
- update:
17
- update == null
18
- ? update
19
- : (content) => {
20
- const jsonValue = content === '' ? undefined : (JSON.parse(content) as Value);
21
-
22
- return JSON.stringify(update(jsonValue));
23
- },
24
- };
25
- }
10
+ export type JSONValue = boolean | JSONValue[] | null | number | string | { [key: string]: JSONValue };
26
11
 
27
12
  /**
28
13
  * Ensure file is present/absent asynchronously with content value initialized or modified with `update`
@@ -41,3 +26,18 @@ export async function json<Value>(options: JSONOption<Value>): Promise<void> {
41
26
  export function jsonSync<Value>(options: JSONOption<Value>): void {
42
27
  return fileSync(toFileOption(options));
43
28
  }
29
+
30
+ function toFileOption<Value>({ update, ...otherOptions }: JSONOption<Value>): FileOptions {
31
+ return {
32
+ ...otherOptions,
33
+
34
+ update:
35
+ update == null
36
+ ? update
37
+ : (content) => {
38
+ const jsonValue = content === '' ? undefined : (JSON.parse(content) as Value);
39
+
40
+ return JSON.stringify(update(jsonValue));
41
+ },
42
+ };
43
+ }
package/src/meta.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  export const meta = Object.freeze({
2
+ // @ts-ignore - these variables are injected at build time
3
+ buildNumber: 1 as number, // (typeof __PACKAGE_BUILD_NUMBER__ === 'undefined' ? 0 : __PACKAGE_BUILD_NUMBER__) as number,
2
4
  // @ts-ignore - these variables are injected at build time
3
5
  name: (typeof __PACKAGE_NAME__ === 'undefined' ? '' : __PACKAGE_NAME__) as string,
4
6
  // @ts-ignore - these variables are injected at build time
5
7
  version: (typeof __PACKAGE_VERSION__ === 'undefined' ? '' : __PACKAGE_VERSION__) as string,
6
- // @ts-ignore - these variables are injected at build time
7
- buildNumber: 1 as number, // (typeof __PACKAGE_BUILD_NUMBER__ === 'undefined' ? 0 : __PACKAGE_BUILD_NUMBER__) as number,
8
8
  });
@@ -1,4 +1,5 @@
1
1
  import path from 'node:path';
2
+
2
3
  import { getRootPath } from './getRootPath.js';
3
4
 
4
5
  export function getTempPath() {
@@ -1,6 +1,7 @@
1
+ import { mkdir, rm } from 'node:fs/promises';
1
2
  import path from 'node:path';
2
3
  import { afterAll, beforeAll } from 'vitest';
3
- import { mkdir, rm } from 'node:fs/promises';
4
+
4
5
  import { getTempPath } from './getTempPath.js';
5
6
 
6
7
  export function getTestPath(prefix: string, remove = true) {
package/src/yaml.ts ADDED
@@ -0,0 +1,45 @@
1
+ import YAML from 'yaml';
2
+
3
+ import { file, type FileOptions, fileSync } from './file.js';
4
+
5
+ export interface YAMLOption<V = YAMLValue> extends Omit<FileOptions, 'update'> {
6
+ /**
7
+ * File content mapping function
8
+ */
9
+ readonly update?: ((content: undefined | V) => undefined | V) | undefined;
10
+ }
11
+
12
+ export type YAMLValue = boolean | null | number | string | YAMLValue[] | { [key: string]: YAMLValue };
13
+
14
+ /**
15
+ * Ensure file is present/absent asynchronously with content value initialized or modified with `update`
16
+ *
17
+ * @param options
18
+ */
19
+ export async function yaml<Value>(options: YAMLOption<Value>): Promise<void> {
20
+ return file(toFileOption(options));
21
+ }
22
+
23
+ /**
24
+ * Ensure file is present/absent synchronously with content value initialized or modified with `update`
25
+ *
26
+ * @param options
27
+ */
28
+ export function yamlSync<Value>(options: YAMLOption<Value>): void {
29
+ return fileSync(toFileOption(options));
30
+ }
31
+
32
+ function toFileOption<Value>({ update, ...otherOptions }: YAMLOption<Value>): FileOptions {
33
+ return {
34
+ ...otherOptions,
35
+
36
+ update:
37
+ update == null
38
+ ? update
39
+ : (content) => {
40
+ const yamlValue = content === '' ? undefined : (YAML.parse(content) as Value);
41
+
42
+ return YAML.stringify(update(yamlValue));
43
+ },
44
+ };
45
+ }