Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions configurator.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { defineConfig } from '@w5s/configurator/config';

export default defineConfig({
preset: './configurator.preset.ts',
});
6 changes: 6 additions & 0 deletions configurator.preset.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { definePreset, useRuntimeContext } from '@w5s/configurator-core';

export default definePreset((config) => {
console.log(config);
console.log(useRuntimeContext());
});
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@
"@lerna-lite/publish": "5.0.0",
"@types/node": "20.19.39",
"@w5s/commitlint-config": "workspace:*",
"@w5s/configurator": "workspace:*",
"@w5s/configurator-core": "workspace:*",
"@w5s/conventional-changelog": "workspace:*",
"@w5s/cspell-config": "workspace:*",
"@w5s/eslint-config": "workspace:*",
Expand Down
4 changes: 3 additions & 1 deletion packages/configurator-core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,9 @@
"test": "pnpm run \"/^test:.*/\"",
"test:src": "vitest run"
},
"dependencies": {},
"dependencies": {
"@w5s/configurator": "workspace:*"
},
"devDependencies": {
"@w5s/tsdown-config": "workspace:*",
"tsdown": "0.21.7",
Expand Down
4 changes: 4 additions & 0 deletions packages/configurator-core/src/context.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export {
definePreset,
useRuntimeContext,
} from '@w5s/configurator/runtime';
45 changes: 45 additions & 0 deletions packages/configurator-core/src/directory.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { describe, expect, it } from 'vitest';
import nodePath from 'node:path';
import { existsSync } from 'node:fs';
import { RuntimeContext } from '@w5s/configurator/runtime';
import { directory, directorySync } from './directory.js';
import { getTestPath } from './testing/index.js';

Expand Down Expand Up @@ -28,4 +29,48 @@ describe.each([
});
}).not.toThrow();
});

it('should skip create in dryRun mode', async () => {
const path = nodePath.join(testPath, `${String(_)}-dry-run-create`);

await RuntimeContext.run(
{
...RuntimeContext.default,
isDebug: false,
isDryRun: true,
},
async () => {
await subject({
path,
state: 'present',
});
},
);

expect(existsSync(path)).toEqual(false);
});

it('should skip delete in dryRun mode', async () => {
const path = nodePath.join(testPath, `${String(_)}-dry-run-delete`);
directorySync({
path,
state: 'present',
});

await RuntimeContext.run(
{
...RuntimeContext.default,
isDebug: false,
isDryRun: true,
},
async () => {
await subject({
path,
state: 'absent',
});
},
);

expect(existsSync(path)).toEqual(true);
});
});
15 changes: 9 additions & 6 deletions packages/configurator-core/src/directory.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { chmodSync, mkdirSync, rmSync } from 'node:fs';
import { chmod, mkdir, rm } from 'node:fs/promises';
import { useRuntimeContext } from './context.js';
import { __exists } from './__exists.js';
import type { FileMode } from './FileMode.js';
import { __toMode } from './__toMode.js';
Expand Down Expand Up @@ -43,15 +44,16 @@ export interface DirectoryOptions {
export async function directory(options: DirectoryOptions): Promise<void> {
const { path, state, mode } = options;
const isPresent = await __exists(path);
const { isDryRun } = useRuntimeContext();
if (state === 'present') {
const newMode = __toMode(mode);
if (!isPresent) {
if (!isDryRun && !isPresent) {
await mkdir(path, { recursive: true, mode: newMode });
}
if (newMode != null && isPresent) {
if (!isDryRun && newMode != null && isPresent) {
await chmod(path, newMode);
}
} else if (isPresent) {
} else if (!isDryRun && isPresent) {
await rm(path, { recursive: true });
}
}
Expand All @@ -77,15 +79,16 @@ export async function directory(options: DirectoryOptions): Promise<void> {
export function directorySync(options: DirectoryOptions): void {
const { path, state, mode } = options;
const isPresent = __existsSync(path);
const { isDryRun } = useRuntimeContext();
if (state === 'present') {
const newMode = __toMode(mode);
if (!isPresent) {
if (!isDryRun && !isPresent) {
mkdirSync(path, { recursive: true, mode: newMode });
}
if (newMode != null && isPresent) {
if (!isDryRun && newMode != null && isPresent) {
chmodSync(path, newMode);
}
} else if (isPresent) {
} else if (!isDryRun && isPresent) {
rmSync(path, { recursive: true });
}
}
64 changes: 64 additions & 0 deletions packages/configurator-core/src/file.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { describe, expect, it } from 'vitest';
import { chmod, readFile, stat, writeFile } from 'node:fs/promises';
import nodePath from 'node:path';
import { RuntimeContext } from '@w5s/configurator/runtime';
import { file, fileSync } from './file.js';
import { getTestPath } from './testing/index.js';

Expand Down Expand Up @@ -133,4 +134,67 @@ describe(file, () => {
await expect(readFile(path, 'utf8')).resolves.toEqual('foo');
await expect(readMode(path)).resolves.toEqual(0o640);
});

it('should skip async mutations in dryRun mode', async () => {
const path = nodePath.join(testPath, 'dry-run-async');

await RuntimeContext.run(
{
...RuntimeContext.default,
isDebug: false,
isDryRun: true,
},
async () => {
await file({
path,
state: 'present',
update: () => 'foo',
});
},
);

await expect(stat(path)).rejects.toThrow();
});

it('should skip async deletes in dryRun mode', async () => {
const path = nodePath.join(testPath, 'dry-run-delete');
await writeFile(path, 'foo');

await RuntimeContext.run(
{
...RuntimeContext.default,
isDebug: false,
isDryRun: true,
},
async () => {
await file({
path,
state: 'absent',
});
},
);

await expect(readFile(path, 'utf8')).resolves.toEqual('foo');
});

it('should skip sync mutations in dryRun mode', async () => {
const path = nodePath.join(testPath, 'dry-run-sync');

await RuntimeContext.run(
{
...RuntimeContext.default,
isDebug: false,
isDryRun: true,
},
async () => {
fileSync({
path,
state: 'present',
update: () => 'foo',
});
},
);

await expect(stat(path)).rejects.toThrow();
});
});
19 changes: 13 additions & 6 deletions packages/configurator-core/src/file.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { chmod, readFile, rm, writeFile } from 'node:fs/promises';
import { chmodSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
import { useRuntimeContext } from './context.js';
import { __exists } from './__exists.js';
import { __existsSync } from './__existsSync.js';
import type { FileMode } from './FileMode.js';
Expand Down Expand Up @@ -54,19 +55,22 @@ export interface FileOptions {
*/
export async function file(options: FileOptions): Promise<void> {
const { path, state, update, encoding = 'utf8', mode } = options;
const { isDryRun } = useRuntimeContext();
if (state === 'present') {
const isPresent = await __exists(path);
const previousContent = isPresent ? await readFile(path, encoding) : '';
const newContent = update == null ? (isPresent ? undefined : '') : update(previousContent);
const newMode = __toMode(mode);
if (newContent != null) {
if (!isDryRun && newContent != null) {
await writeFile(path, newContent, { encoding, mode: newMode });
}
if (newMode != null && (isPresent || newContent != null)) {
if (!isDryRun && newMode != null && (isPresent || newContent != null)) {
await chmod(path, newMode);
}
} else {
await rm(path, { force: true });
if (!isDryRun) {
await rm(path, { force: true });
}
}
}

Expand All @@ -91,18 +95,21 @@ export async function file(options: FileOptions): Promise<void> {
*/
export function fileSync(options: FileOptions): void {
const { path, state, update, encoding = 'utf8', mode } = options;
const { isDryRun } = useRuntimeContext();
if (state === 'present') {
const isPresent = __existsSync(path);
const previousContent = isPresent ? readFileSync(path, encoding) : '';
const newContent = update == null ? (isPresent ? undefined : '') : update(previousContent);
const newMode = __toMode(mode);
if (newContent != null) {
if (!isDryRun && newContent != null) {
writeFileSync(path, newContent, { encoding, mode: newMode });
}
if (newMode != null && (isPresent || newContent != null)) {
if (!isDryRun && newMode != null && (isPresent || newContent != null)) {
chmodSync(path, newMode);
}
} else {
rmSync(path, { force: true });
if (!isDryRun) {
rmSync(path, { force: true });
}
}
}
2 changes: 2 additions & 0 deletions packages/configurator-core/src/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ describe('index', () => {
expect(Object.keys(Module).sort()).toEqual(
[
// Public API
'definePreset',
'directory',
'directorySync',
'block',
Expand All @@ -16,6 +17,7 @@ describe('index', () => {
'json',
'jsonSync',
'meta',
'useRuntimeContext',
'yarnConfig',
'yarnConfigSync',
'yarnVersion',
Expand Down
1 change: 1 addition & 0 deletions packages/configurator-core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ export * from './json.js';
export * from './meta.js';
export * from './yarnConfig.js';
export * from './yarnVersion.js';
export * from './context.js';
25 changes: 24 additions & 1 deletion packages/configurator-core/src/json.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { describe, it, expect } from 'vitest';
import { readFile } from 'node:fs/promises';
import { readFile, writeFile } from 'node:fs/promises';
import nodePath from 'node:path';
import { RuntimeContext } from '@w5s/configurator/runtime';
import { json } from './json.js';
import { getTestPath } from './testing/index.js';

Expand Down Expand Up @@ -30,4 +31,26 @@ describe(json, () => {
});
await expect(readFile(path, 'utf8')).resolves.toEqual('["foo","bar"]');
});

it('should skip mutations in dryRun mode', async () => {
const path = nodePath.join(testPath, 'dry-run');
await writeFile(path, '["foo"]');

await RuntimeContext.run(
{
...RuntimeContext.default,
isDebug: false,
isDryRun: true,
},
async () => {
await json<string[]>({
path,
state: 'present',
update: (content = []) => [...content, 'bar'],
});
},
);

await expect(readFile(path, 'utf8')).resolves.toEqual('["foo"]');
});
});
10 changes: 10 additions & 0 deletions packages/configurator/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Change Log

All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.

## 1.0.0-alpha.0 (2026-03-26)

### ✨ Features

- Initialize @w5s/configurator package
44 changes: 44 additions & 0 deletions packages/configurator/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<!-- AUTO-GENERATED-CONTENT:START (PKG_JSON:template=# W5S Configurator _(${name})_) -->
# W5S Configurator _(@w5s/configurator)_
<!-- AUTO-GENERATED-CONTENT:END -->

[![NPM Version][package-version-svg]][package-url]
[![License][license-image]][license-url]

<!-- AUTO-GENERATED-CONTENT:START (PKG_JSON:template=> ${description}&unknownTxt= ) -->
> CLI tool to run configurator presets
<!-- AUTO-GENERATED-CONTENT:END -->

## Installation

<!-- AUTO-GENERATED-CONTENT:START (PKG_JSON:template=```console\nnpm install --save-dev ${name}\n```) -->
```console
npm install --save-dev @w5s/configurator
```
<!-- AUTO-GENERATED-CONTENT:END -->

## Usage

<!-- AUTO-GENERATED-CONTENT:START (PKG_JSON:template=```json\n"${name}"\n```) -->
```json
"@w5s/configurator"
```
<!-- AUTO-GENERATED-CONTENT:END -->

## License
<!-- AUTO-GENERATED-CONTENT:START (PKG_JSON:template=[${license}][license-url] © ${author}) -->
[MIT][license-url] © Julien Polo <julien.polo@gmail.com>
<!-- AUTO-GENERATED-CONTENT:END -->

<!-- VARIABLES -->

<!-- AUTO-GENERATED-CONTENT:START (PKG_JSON:template=[package-version-svg]: https://img.shields.io/npm/v/${name}.svg?style=flat-square) -->
[package-version-svg]: https://img.shields.io/npm/v/@w5s/configurator.svg?style=flat-square
<!-- AUTO-GENERATED-CONTENT:END -->
<!-- AUTO-GENERATED-CONTENT:START (PKG_JSON:template=[package-url]: https://www.npmjs.com/package/${name}) -->
[package-url]: https://www.npmjs.com/package/@w5s/configurator
<!-- AUTO-GENERATED-CONTENT:END -->
<!-- AUTO-GENERATED-CONTENT:START (PKG_JSON:template=[license-image]: https://img.shields.io/badge/license-${license}-green.svg?style=flat-square) -->
[license-image]: https://img.shields.io/badge/license-MIT-green.svg?style=flat-square
<!-- AUTO-GENERATED-CONTENT:END -->
[license-url]: ../../LICENSE
Loading
Loading