@w5s/dev 3.2.2 → 3.3.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.
package/src/block.ts DELETED
@@ -1,153 +0,0 @@
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
- /**
12
- * File path
13
- */
14
- path: string;
15
-
16
- /**
17
- * Block content to insert
18
- */
19
- block: string;
20
-
21
- /**
22
- * Insert position
23
- */
24
- insertPosition?: ['before', 'BeginningOfFile' | RegExp] | ['after', 'EndOfFile' | RegExp];
25
-
26
- /**
27
- * Block target state
28
- */
29
- state?: 'present' | 'absent';
30
- }
31
-
32
- const EOF = 'EndOfFile';
33
- const BOF = 'BeginningOfFile';
34
- const insertAt = (str: string, index: number, toInsert: string) => str.slice(0, index) + toInsert + str.slice(index);
35
- const matchLast = (string: string, regexp: RegExp) => {
36
- const matcher = new RegExp(regexp.source, `${regexp.flags}g`);
37
- let firstIndex = -1;
38
- let lastIndex = -1;
39
- let matches;
40
-
41
- while (true) {
42
- matches = matcher.exec(string);
43
- if (matches == null) {
44
- break;
45
- }
46
- firstIndex = matches.index;
47
- lastIndex = matcher.lastIndex;
48
- }
49
- return { firstIndex, lastIndex };
50
- };
51
-
52
- function toFileOptions(options: BlockOptions): FileOptions {
53
- const {
54
- marker = (mark) => `# ${mark.toUpperCase()} MANAGED BLOCK`,
55
- path,
56
- block: blockName,
57
- insertPosition = ['after', EOF],
58
- state = 'present',
59
- } = options;
60
-
61
- const EOL = '\n';
62
- const beginBlock = marker('Begin');
63
- const endBlock = marker('End');
64
-
65
- /**
66
- * @param content
67
- */
68
- function findBlock(content: string) {
69
- const startIndex = content.indexOf(beginBlock);
70
- const endIndex = content.indexOf(endBlock) + endBlock.length;
71
-
72
- return {
73
- endIndex,
74
- exists: startIndex !== -1 && endIndex >= 0,
75
- startIndex,
76
- };
77
- }
78
-
79
- function apply(fullContent: string, blockContent: string) {
80
- const found = findBlock(fullContent);
81
- const remove = state === 'absent';
82
- const replaceBlock = remove ? '' : beginBlock + EOL + blockContent + EOL + endBlock;
83
- const [positionDirection, positionAnchor] = insertPosition;
84
-
85
- if (found.exists) {
86
- return fullContent.slice(0, found.startIndex) + replaceBlock + fullContent.slice(found.endIndex);
87
- }
88
- if (remove) {
89
- return fullContent;
90
- }
91
- 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
- case 'after': {
104
- // insert
105
- if (positionAnchor !== EOF) {
106
- const { lastIndex } = matchLast(fullContent, positionAnchor);
107
- if (lastIndex >= 0) {
108
- return insertAt(fullContent, lastIndex, EOL + replaceBlock);
109
- }
110
- }
111
-
112
- // end of file
113
- return fullContent + EOL + replaceBlock;
114
- }
115
-
116
- default: {
117
- throw new Error(`Unsupported position ${String(positionDirection)}`);
118
- }
119
- }
120
- }
121
-
122
- return {
123
- path,
124
- state: 'present',
125
- update: (sourceContent) => apply(sourceContent, blockName),
126
- };
127
- }
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 DELETED
@@ -1,73 +0,0 @@
1
- import { existsSync, mkdirSync, rmSync } from 'node:fs';
2
- import { access, constants, mkdir, rm } from 'node:fs/promises';
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
- export interface DirectoryOptions {
14
- /**
15
- * Directory path
16
- */
17
- readonly path: string;
18
-
19
- /**
20
- * Directory target state
21
- */
22
- readonly state: 'present' | 'absent';
23
- }
24
-
25
- /**
26
- * Ensure directory is present/absent
27
- *
28
- * @example
29
- * ```ts
30
- * await directory({
31
- * path: 'foo/bar',
32
- * state: 'present',
33
- * })
34
- * ```
35
- *
36
- * @param options
37
- */
38
- export async function directory(options: DirectoryOptions): Promise<void> {
39
- const { path, state } = options;
40
- const isPresent = await exists(path);
41
- if (state === 'present') {
42
- if (!isPresent) {
43
- await mkdir(path, { recursive: true });
44
- }
45
- } else if (isPresent) {
46
- await rm(path, { recursive: true });
47
- }
48
- }
49
-
50
- /**
51
- * Ensure directory is present/absent
52
- *
53
- * @example
54
- * ```ts
55
- * await directorySync({
56
- * path: 'foo/bar',
57
- * state: 'present',
58
- * })
59
- * ```
60
- *
61
- * @param options
62
- */
63
- export function directorySync(options: DirectoryOptions): void {
64
- const { path, state } = options;
65
- const isPresent = existsSync(path);
66
- if (state === 'present') {
67
- if (!isPresent) {
68
- mkdirSync(path, { recursive: true });
69
- }
70
- } else if (isPresent) {
71
- rmSync(path, { recursive: true });
72
- }
73
- }
package/src/exec.ts DELETED
@@ -1,73 +0,0 @@
1
- import { spawn, spawnSync } from 'node:child_process';
2
-
3
- export interface ExecOptions {
4
- /**
5
- * Current working directory
6
- */
7
- cwd?: string;
8
-
9
- /**
10
- * Stdio options
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) };
33
- }
34
-
35
- /**
36
- * Runs a command in a shell and returns a promise that resolves with an object
37
- * containing the stdout and stderr strings.
38
- *
39
- * @param command The command to run
40
- * @param args The arguments to pass to the command
41
- * @param options
42
- */
43
- export async function exec(
44
- command: string,
45
- args: ReadonlyArray<string>,
46
- options?: ExecOptions,
47
- ): Promise<{ stdout: string; stderr: string }> {
48
- return new Promise((resolve, reject) => {
49
- const encoding = 'utf8';
50
- const child = spawn(command, args, { ...options });
51
- let stdout = '';
52
- let stderr = '';
53
-
54
- // Capture the stdout and stderr streams
55
- if (child.stdout != null) {
56
- child.stdout.on('data', (data) => {
57
- stdout += data.toString(encoding);
58
- });
59
- }
60
- if (child.stderr != null) {
61
- child.stderr.on('data', (data) => {
62
- stderr += data.toString(encoding);
63
- });
64
- }
65
- // Handle process exit
66
- child.on('close', (_code) => {
67
- resolve({ stdout, stderr });
68
- });
69
-
70
- // Handle errors
71
- child.on('error', reject);
72
- });
73
- }
package/src/file.ts DELETED
@@ -1,99 +0,0 @@
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
- /**
29
- * File target state
30
- */
31
- readonly state: 'present' | 'absent';
32
-
33
- /**
34
- * File content mapping function
35
- *
36
- */
37
- readonly update?: ((content: string) => string | undefined) | undefined;
38
-
39
- /**
40
- * File encoding
41
- */
42
- readonly encoding?: BufferEncoding;
43
- }
44
-
45
- /**
46
- * Ensure file is present/absent with content initialized or modified with `update
47
- *
48
- * @example
49
- * ```ts
50
- * await file({
51
- * path: 'foo/bar',
52
- * state: 'present',
53
- * update: (content) => content + '_test', // This will append '_test' after current content
54
- * })
55
- * ```
56
- *
57
- * @param options
58
- */
59
- export async function file(options: FileOptions): Promise<void> {
60
- const { path, state, update, encoding = 'utf8' } = options;
61
- if (state === 'present') {
62
- const isPresent = await exists(path);
63
- const previousContent = isPresent ? await readFile(path, encoding) : '';
64
- const newContent = update == null ? '' : update(previousContent);
65
- if (newContent != null) {
66
- await writeFile(path, newContent, encoding);
67
- }
68
- } else {
69
- await rm(path, { force: true });
70
- }
71
- }
72
-
73
- /**
74
- * Ensure file is present/absent with content initialized or modified with `update
75
- *
76
- * @example
77
- * ```ts
78
- * fileSync({
79
- * path: 'foo/bar',
80
- * state: 'present',
81
- * update: (content) => content + '_test', // This will append '_test' after current content
82
- * })
83
- * ```
84
- *
85
- * @param options
86
- */
87
- export function fileSync(options: FileOptions): void {
88
- const { path, state, update, encoding = 'utf8' } = options;
89
- if (state === 'present') {
90
- const isPresent = existsSync(path);
91
- const previousContent = isPresent ? readFileSync(path, encoding) : '';
92
- const newContent = update == null ? '' : update(previousContent);
93
- if (newContent != null) {
94
- writeFileSync(path, newContent, encoding);
95
- }
96
- } else {
97
- rmSync(path, { force: true });
98
- }
99
- }
package/src/json.ts DELETED
@@ -1,58 +0,0 @@
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
- /**
12
- * File target state
13
- */
14
- readonly state: 'present' | 'absent';
15
-
16
- /**
17
- * File content mapping function
18
- */
19
- readonly update?: ((content: V | undefined) => V | undefined) | undefined;
20
-
21
- /**
22
- * File encoding
23
- */
24
- readonly encoding?: BufferEncoding;
25
- }
26
-
27
- function toFileOption<Value>({ update, ...otherOptions }: JSONOption<Value>): FileOptions {
28
- return {
29
- ...otherOptions,
30
-
31
- update:
32
- update == null
33
- ? update
34
- : (content) => {
35
- const jsonValue = content === '' ? undefined : (JSON.parse(content) as Value);
36
-
37
- return JSON.stringify(update(jsonValue));
38
- },
39
- };
40
- }
41
-
42
- /**
43
- * Ensure file is present/absent asynchronously with content value initialized or modified with `update`
44
- *
45
- * @param options
46
- */
47
- export async function json<Value>(options: JSONOption<Value>): Promise<void> {
48
- return file(toFileOption(options));
49
- }
50
-
51
- /**
52
- * Ensure file is present/absent synchronously with content value initialized or modified with `update`
53
- *
54
- * @param options
55
- */
56
- export function jsonSync<Value>(options: JSONOption<Value>): void {
57
- return fileSync(toFileOption(options));
58
- }
package/src/yarnConfig.ts DELETED
@@ -1,61 +0,0 @@
1
- import { exec, execSync } from './exec.js';
2
-
3
- export interface YarnConfigOptions {
4
- /**
5
- * Configuration key
6
- */
7
- readonly key: string;
8
-
9
- /**
10
- * Option target state
11
- */
12
- readonly state: 'present' | 'absent';
13
-
14
- /**
15
- * File content mapping function
16
- *
17
- */
18
- readonly update?: ((content: string) => string | undefined) | undefined;
19
- }
20
-
21
- /**
22
- * Synchronous version of {@link yarnConfig}
23
- *
24
- * @param options
25
- * @example
26
- * yarnConfigSync({
27
- * key: 'nodeLinker',
28
- * state: 'present',
29
- * update: (content) => content.replace('node-modules', 'hoisted'),
30
- * })
31
- */
32
- export function yarnConfigSync(options: YarnConfigOptions) {
33
- const { key, state, update } = options;
34
- if (state === 'present') {
35
- const { stdout } = execSync('yarn', ['config', 'get', String(key)]);
36
- execSync('yarn', ['config', 'set', String(key), `${update == null ? '' : update(stdout)}`]);
37
- } else {
38
- execSync('yarn', ['config', 'unset']);
39
- }
40
- }
41
-
42
- /**
43
- * Set/Unset yarn configuration value
44
- *
45
- * @param options
46
- * @example
47
- * await yarnConfig({
48
- * key: 'nodeLinker',
49
- * state: 'present',
50
- * update: (content) => content.replace('node-modules', 'hoisted'),
51
- * })
52
- */
53
- export async function yarnConfig(options: YarnConfigOptions): Promise<void> {
54
- const { key, state, update } = options;
55
- if (state === 'present') {
56
- const { stdout } = await exec('yarn', ['config', 'get', String(key)]);
57
- await exec('yarn', ['config', 'set', String(key), `${update == null ? '' : update(stdout)}`]);
58
- } else {
59
- await exec('yarn', ['config', 'unset']);
60
- }
61
- }
@@ -1,56 +0,0 @@
1
- import { exec, execSync } from './exec.js';
2
-
3
- export type YarnVersionKind = 'berry' | 'classic';
4
-
5
- export interface YarnVersionOptions {
6
- /**
7
- * Option target state
8
- */
9
- readonly state: 'present' | 'absent';
10
-
11
- /**
12
- * Version mapping function
13
- *
14
- */
15
- readonly update?: (() => YarnVersionKind | undefined) | undefined;
16
- }
17
-
18
- /**
19
- * Synchronous version of {@link yarnVersion}
20
- *
21
- * @param options
22
- * @example
23
- * yarnVersionSync({
24
- * state: 'present',
25
- * update: () => 'berry', // or 'classic'
26
- * })
27
- */
28
- export function yarnVersionSync(options: YarnVersionOptions) {
29
- const { state, update } = options;
30
- if (state === 'present') {
31
- execSync('yarn', ['set', 'version', `${update == null ? 'berry' : update()}`]);
32
- } else {
33
- // TODO: remove yarn.lock
34
- throw new Error('Not implemented');
35
- }
36
- }
37
-
38
- /**
39
- * Set/Unset yarn configuration value
40
- *
41
- * @param options
42
- * @example
43
- * await yarnVersion({
44
- * state: 'present',
45
- * update: () => 'berry', // or 'classic'
46
- * })
47
- */
48
- export async function yarnVersion(options: YarnVersionOptions): Promise<void> {
49
- const { state, update } = options;
50
- if (state === 'present') {
51
- await exec('yarn', ['set', 'version', `${update == null ? 'berry' : update()}`]);
52
- } else {
53
- // TODO: remove yarn.lock
54
- throw new Error('Not implemented');
55
- }
56
- }