@markuplint/create-rule 5.0.0-rc.2 → 5.0.0-rc.5

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/CHANGELOG.md CHANGED
@@ -3,6 +3,24 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ # [5.0.0-rc.5](https://github.com/markuplint/markuplint/compare/v5.0.0-rc.4...v5.0.0-rc.5) (2026-08-28)
7
+
8
+ ### Code Refactoring
9
+
10
+ - **rules:** redesign v5 rule system — naming, splits, specConformance ([#3989](https://github.com/markuplint/markuplint/issues/3989)) ([e925565](https://github.com/markuplint/markuplint/commit/e925565ce537848d7d1573369723cbce724a841b)), closes [#4](https://github.com/markuplint/markuplint/issues/4) [#aside-conditional-role-mapping-aria-13](https://github.com/markuplint/markuplint/issues/aside-conditional-role-mapping-aria-13)
11
+
12
+ ### BREAKING CHANGES
13
+
14
+ - **rules:** with no alias coverage.
15
+
16
+ # [5.0.0-rc.4](https://github.com/markuplint/markuplint/compare/v5.0.0-rc.3...v5.0.0-rc.4) (2026-04-19)
17
+
18
+ **Note:** Version bump only for package @markuplint/create-rule
19
+
20
+ # [5.0.0-rc.3](https://github.com/markuplint/markuplint/compare/v5.0.0-rc.2...v5.0.0-rc.3) (2026-04-19)
21
+
22
+ **Note:** Version bump only for package @markuplint/create-rule
23
+
6
24
  # [5.0.0-rc.2](https://github.com/markuplint/markuplint/compare/v5.0.0-rc.1...v5.0.0-rc.2) (2026-04-15)
7
25
 
8
26
  **Note:** Version bump only for package @markuplint/create-rule
package/README.md CHANGED
@@ -50,7 +50,7 @@ $ npx @markuplint/create-rule -p project -n my-plugin -r no-empty-alt --json
50
50
  | `--json` | | Output result as JSON | `false` |
51
51
  | `--help` | `-h` | Show help message | |
52
52
 
53
- Available categories: `validation`, `a11y`, `naming-convention`, `maintainability`, `style`
53
+ Available categories: `syntax`, `structure`, `attributes`, `references`, `forms`, `a11y`, `style`, `maintainability`, `compat`
54
54
 
55
55
  > When contributing to core (`--purpose core`), `--lang` is always TypeScript and `--test` is always enabled regardless of the options provided.
56
56
 
@@ -73,7 +73,7 @@ The CLI asks questions in the following order:
73
73
  3. **Rule name** — The kebab-case name of the rule (e.g., `no-empty-alt`)
74
74
  4. **Core-only questions** (contribute to core only):
75
75
  - Description
76
- - Category (`validation`, `a11y`, `naming-convention`, `maintainability`, `style`)
76
+ - Category (`syntax`, `structure`, `attributes`, `references`, `forms`, `a11y`, `style`, `maintainability`, `compat`)
77
77
  - Severity (`error` or `warning`)
78
78
  5. **Language** — TypeScript or JavaScript (core mode always uses TypeScript)
79
79
  6. **Tests** — Whether to generate test files (core mode always includes tests)
package/lib/cli.js CHANGED
@@ -4,16 +4,23 @@ import { input, installModule, select, confirm, font, header, xterm } from '@mar
4
4
  import { createRuleHelper } from './create-rule-helper.js';
5
5
  import { isMarkuplintRepo } from './is-markuplint-repo.js';
6
6
  const KEBAB_CASE = /^[a-z][\da-z]*(?:-[a-z][\da-z]*)*$/i;
7
- const CATEGORIES = ['validation', 'a11y', 'naming-convention', 'maintainability', 'style'];
7
+ const CATEGORIES = [
8
+ 'syntax',
9
+ 'structure',
10
+ 'attributes',
11
+ 'references',
12
+ 'forms',
13
+ 'a11y',
14
+ 'style',
15
+ 'maintainability',
16
+ 'compat',
17
+ ];
8
18
  const SEVERITIES = ['error', 'warning'];
9
19
  const PURPOSE_MAP = {
10
20
  project: 'ADD_TO_PROJECT',
11
21
  package: 'PUBLISH_AS_PACKAGE',
12
22
  core: 'CONTRIBUTE_TO_CORE',
13
23
  };
14
- /**
15
- * Icon mapping for scaffold output display, keyed by base file name.
16
- */
17
24
  const icons = {
18
25
  README: '📝',
19
26
  index: '📜',
@@ -21,9 +28,6 @@ const icons = {
21
28
  package: '🎁',
22
29
  tsconfig: '💎',
23
30
  };
24
- /**
25
- * Prints the CLI usage information, options, and examples to stdout.
26
- */
27
31
  function printHelp() {
28
32
  process.stdout.write(`
29
33
  Usage: create-rule [options]
@@ -40,8 +44,8 @@ Options:
40
44
  --no-test Skip test file generation
41
45
  -d, --description <text> Rule description (required for core)
42
46
  -c, --category <cat> Category (required for core):
43
- validation, a11y, naming-convention,
44
- maintainability, style
47
+ syntax, structure, attributes, references,
48
+ forms, a11y, style, maintainability, compat
45
49
  -s, --severity <level> Severity: error or warning (required for core)
46
50
  --json Output result as JSON
47
51
  -h, --help Show this help message
@@ -58,16 +62,11 @@ Examples:
58
62
  `);
59
63
  }
60
64
  /**
61
- * Sentinel class thrown when `--help` is requested to signal a
62
- * successful early exit without using `process.exit`.
65
+ * Sentinel for a successful early exit on `--help`, avoiding `process.exit`.
63
66
  */
64
67
  class HelpRequested {
65
68
  code = 0;
66
69
  }
67
- /**
68
- * Error thrown when CLI arguments are invalid or missing.
69
- * Includes a usage hint directing users to `--help`.
70
- */
71
70
  class UsageHintError extends Error {
72
71
  constructor(message) {
73
72
  super(`${message}\nRun 'create-rule --help' for usage.`);
@@ -75,10 +74,8 @@ class UsageHintError extends Error {
75
74
  }
76
75
  }
77
76
  /**
78
- * Parses `process.argv` into validated {@link CreateRuleHelperParams}.
79
- *
80
- * @returns The parsed parameters and output format flag, or `null` when
81
- * no arguments are provided (indicating interactive mode).
77
+ * @returns The parsed parameters, or `null` when no arguments are provided
78
+ * (indicating interactive mode).
82
79
  * @throws {HelpRequested} When `--help` is passed.
83
80
  * @throws {UsageHintError} When required options are missing or values are invalid.
84
81
  */
@@ -212,14 +209,6 @@ export async function createRule() {
212
209
  await createRuleInteractive();
213
210
  }
214
211
  }
215
- /**
216
- * Creates a rule non-interactively from pre-validated CLI options.
217
- * Runs the scaffold, prints results (or JSON), and installs dependencies.
218
- *
219
- * @param params - The validated rule creation parameters.
220
- * @param json - When `true`, outputs the result as JSON instead of
221
- * the human-readable file list.
222
- */
223
212
  async function createRuleNonInteractive(params, json) {
224
213
  const result = await createRuleHelper(params);
225
214
  if (!json) {
@@ -250,13 +239,6 @@ async function createRuleNonInteractive(params, json) {
250
239
  await installModule(result.devDependencies, true);
251
240
  }
252
241
  }
253
- /**
254
- * Interactive CLI wizard for creating a new markuplint rule.
255
- *
256
- * Guides the user through selecting a purpose, naming the plugin and rule,
257
- * choosing a language, and optionally generating tests. After scaffolding,
258
- * it prints the generated files and installs any required dependencies.
259
- */
260
242
  async function createRuleInteractive() {
261
243
  process.stdout.write(header('Create a rule'));
262
244
  process.stdout.write('\n');
@@ -281,11 +263,15 @@ async function createRuleInteractive() {
281
263
  category: await select({
282
264
  message: 'Category:',
283
265
  choices: [
284
- { name: 'Conformance checking', value: 'validation' },
266
+ { name: 'Syntax', value: 'syntax' },
267
+ { name: 'Structure', value: 'structure' },
268
+ { name: 'Attributes', value: 'attributes' },
269
+ { name: 'References', value: 'references' },
270
+ { name: 'Forms', value: 'forms' },
285
271
  { name: 'Accessibility', value: 'a11y' },
286
- { name: 'Naming Convention', value: 'naming-convention' },
287
- { name: 'Maintainability', value: 'maintainability' },
288
272
  { name: 'Style', value: 'style' },
273
+ { name: 'Maintainability', value: 'maintainability' },
274
+ { name: 'Browser Compatibility', value: 'compat' },
289
275
  ],
290
276
  }),
291
277
  severity: await select({
@@ -318,14 +304,6 @@ async function createRuleInteractive() {
318
304
  await installModule(result.devDependencies, true);
319
305
  }
320
306
  }
321
- /**
322
- * Prints a single scaffolded file entry to stdout with a check mark, icon, and file path.
323
- *
324
- * @param name - The plugin or module name used as a prefix.
325
- * @param icon - The icon character to display next to the file name.
326
- * @param title - The display title (typically the file name).
327
- * @param filePath - The absolute path to the generated file.
328
- */
329
307
  function printFile(name, icon, title, filePath) {
330
308
  const _marker = xterm(39)('✔') + ' ';
331
309
  const _title = (icon, title) => `${icon} ` + font.bold(`${name}/${title}`);
@@ -1,8 +1,3 @@
1
- /**
2
- * Custom error class for failures that occur during rule scaffolding.
3
- * Thrown when preconditions are not met (e.g., directory already exists,
4
- * core options missing, or repository not found).
5
- */
6
1
  export declare class CreateRuleHelperError extends Error {
7
2
  name: string;
8
3
  }
@@ -1,8 +1,3 @@
1
- /**
2
- * Custom error class for failures that occur during rule scaffolding.
3
- * Thrown when preconditions are not met (e.g., directory already exists,
4
- * core options missing, or repository not found).
5
- */
6
1
  export class CreateRuleHelperError extends Error {
7
2
  name = 'CreateRuleHelperError';
8
3
  }
@@ -1,9 +1,2 @@
1
1
  import type { CreateRuleHelperParams, CreateRuleHelperResult } from './types.js';
2
- /**
3
- * Dispatches rule creation to the appropriate scaffolding function based on the
4
- * specified purpose. Acts as the central entry point for programmatic rule creation.
5
- *
6
- * @param params - The helper parameters including purpose, plugin name, rule name, language, and options.
7
- * @returns The scaffold result containing generated files and dependencies.
8
- */
9
2
  export declare function createRuleHelper(params: CreateRuleHelperParams): Promise<CreateRuleHelperResult>;
@@ -1,13 +1,6 @@
1
1
  import { createRulePackage } from './create-rule-package.js';
2
2
  import { createRuleToCore } from './create-rule-to-core.js';
3
3
  import { createRuleToProject } from './create-rule-to-project.js';
4
- /**
5
- * Dispatches rule creation to the appropriate scaffolding function based on the
6
- * specified purpose. Acts as the central entry point for programmatic rule creation.
7
- *
8
- * @param params - The helper parameters including purpose, plugin name, rule name, language, and options.
9
- * @returns The scaffold result containing generated files and dependencies.
10
- */
11
4
  export async function createRuleHelper(params) {
12
5
  switch (params.purpose) {
13
6
  case 'ADD_TO_PROJECT': {
@@ -1,13 +1,2 @@
1
1
  import type { CreateRuleCreatorParams, CreateRuleHelperResult } from './types.js';
2
- /**
3
- * Scaffolds a new markuplint rule as a standalone publishable npm package
4
- * in the current working directory.
5
- *
6
- * Validates that the current directory is empty before proceeding.
7
- * Generates a complete package structure including `package.json`.
8
- *
9
- * @param params - The rule creation parameters (plugin name, rule name, language, test preference).
10
- * @returns The scaffold result containing generated files and dependencies.
11
- * @throws {CreateRuleHelperError} If the current directory is not empty.
12
- */
13
2
  export declare function createRulePackage({ pluginName, ruleName, lang, needTest, }: CreateRuleCreatorParams): Promise<CreateRuleHelperResult>;
@@ -2,17 +2,6 @@ import path from 'node:path';
2
2
  import { CreateRuleHelperError } from './create-rule-helper-error.js';
3
3
  import { glob } from './glob.js';
4
4
  import { installScaffold } from './install-scaffold.js';
5
- /**
6
- * Scaffolds a new markuplint rule as a standalone publishable npm package
7
- * in the current working directory.
8
- *
9
- * Validates that the current directory is empty before proceeding.
10
- * Generates a complete package structure including `package.json`.
11
- *
12
- * @param params - The rule creation parameters (plugin name, rule name, language, test preference).
13
- * @returns The scaffold result containing generated files and dependencies.
14
- * @throws {CreateRuleHelperError} If the current directory is not empty.
15
- */
16
5
  export async function createRulePackage({ pluginName, ruleName, lang, needTest, }) {
17
6
  const newRuleDir = path.resolve(process.cwd(), '*');
18
7
  const files = await glob(newRuleDir);
@@ -1,22 +1,3 @@
1
1
  import type { CreateRuleCreatorParams, CreateRuleHelperResult } from './types.js';
2
- /**
3
- * Scaffolds a new rule within the markuplint core rules directory.
4
- *
5
- * Creates the rule in the `packages/@markuplint/rules/src/<ruleName>` directory
6
- * of the monorepo. Requires core-specific parameters (description, category, severity)
7
- * and always uses TypeScript with tests enabled.
8
- *
9
- * @param params - The rule creation parameters. The `core` property is required.
10
- * @returns The scaffold result containing generated files and dependencies.
11
- * @throws {CreateRuleHelperError} If core options are not defined or a rule with the same name already exists.
12
- */
13
2
  export declare function createRuleToCore({ ruleName, core }: CreateRuleCreatorParams): Promise<CreateRuleHelperResult>;
14
- /**
15
- * Resolves the absolute path to the core rules source directory within the
16
- * markuplint monorepo. Searches upward from the cwd for the repository root
17
- * and then constructs the path to `packages/@markuplint/rules/src`.
18
- *
19
- * @returns The absolute path to the core rules directory.
20
- * @throws {CreateRuleHelperError} If the monorepo root or the core rules directory is not found.
21
- */
22
3
  export declare function getRulesDir(): Promise<string>;
@@ -3,21 +3,7 @@ import { CreateRuleHelperError } from './create-rule-helper-error.js';
3
3
  import { fsExists } from './fs-exists.js';
4
4
  import { installScaffold } from './install-scaffold.js';
5
5
  import { searchCoreRepository } from './search-core-repository.js';
6
- /**
7
- * Relative path segments from the monorepo root to the core rules source directory.
8
- */
9
6
  const rulesRelDir = ['packages', '@markuplint', 'rules', 'src'];
10
- /**
11
- * Scaffolds a new rule within the markuplint core rules directory.
12
- *
13
- * Creates the rule in the `packages/@markuplint/rules/src/<ruleName>` directory
14
- * of the monorepo. Requires core-specific parameters (description, category, severity)
15
- * and always uses TypeScript with tests enabled.
16
- *
17
- * @param params - The rule creation parameters. The `core` property is required.
18
- * @returns The scaffold result containing generated files and dependencies.
19
- * @throws {CreateRuleHelperError} If core options are not defined or a rule with the same name already exists.
20
- */
21
7
  export async function createRuleToCore({ ruleName, core }) {
22
8
  if (!core) {
23
9
  throw new CreateRuleHelperError('Core options are not defined');
@@ -36,14 +22,6 @@ export async function createRuleToCore({ ruleName, core }) {
36
22
  core,
37
23
  });
38
24
  }
39
- /**
40
- * Resolves the absolute path to the core rules source directory within the
41
- * markuplint monorepo. Searches upward from the cwd for the repository root
42
- * and then constructs the path to `packages/@markuplint/rules/src`.
43
- *
44
- * @returns The absolute path to the core rules directory.
45
- * @throws {CreateRuleHelperError} If the monorepo root or the core rules directory is not found.
46
- */
47
25
  export async function getRulesDir() {
48
26
  const rootDir = await searchCoreRepository();
49
27
  if (!rootDir) {
@@ -1,10 +1,2 @@
1
1
  import type { CreateRuleCreatorParams, CreateRuleHelperResult } from './types.js';
2
- /**
3
- * Scaffolds a new markuplint rule as a local plugin directory within the
4
- * current project. Creates a new directory named after the plugin in the cwd.
5
- *
6
- * @param params - The rule creation parameters (plugin name, rule name, language, test preference).
7
- * @returns The scaffold result containing generated files and dependencies.
8
- * @throws {CreateRuleHelperError} If the target plugin directory already exists.
9
- */
10
2
  export declare function createRuleToProject({ pluginName, ruleName, lang, needTest, }: CreateRuleCreatorParams): Promise<CreateRuleHelperResult>;
@@ -2,14 +2,6 @@ import path from 'node:path';
2
2
  import { CreateRuleHelperError } from './create-rule-helper-error.js';
3
3
  import { fsExists } from './fs-exists.js';
4
4
  import { installScaffold } from './install-scaffold.js';
5
- /**
6
- * Scaffolds a new markuplint rule as a local plugin directory within the
7
- * current project. Creates a new directory named after the plugin in the cwd.
8
- *
9
- * @param params - The rule creation parameters (plugin name, rule name, language, test preference).
10
- * @returns The scaffold result containing generated files and dependencies.
11
- * @throws {CreateRuleHelperError} If the target plugin directory already exists.
12
- */
13
5
  export async function createRuleToProject({ pluginName, ruleName, lang, needTest, }) {
14
6
  const pluginDir = path.resolve(process.cwd(), pluginName);
15
7
  if (await fsExists(pluginDir)) {
@@ -1,10 +1 @@
1
- /**
2
- * Checks whether a file or directory exists at the given path.
3
- *
4
- * Uses `fs.stat` internally and treats `ENOENT` errors as a non-existent path.
5
- * Any other filesystem errors are re-thrown.
6
- *
7
- * @param path - The absolute or relative filesystem path to check.
8
- * @returns `true` if the path exists, `false` otherwise.
9
- */
10
1
  export declare function fsExists(path: string): Promise<boolean>;
package/lib/fs-exists.js CHANGED
@@ -1,13 +1,4 @@
1
1
  import { stat } from 'node:fs/promises';
2
- /**
3
- * Checks whether a file or directory exists at the given path.
4
- *
5
- * Uses `fs.stat` internally and treats `ENOENT` errors as a non-existent path.
6
- * Any other filesystem errors are re-thrown.
7
- *
8
- * @param path - The absolute or relative filesystem path to check.
9
- * @returns `true` if the path exists, `false` otherwise.
10
- */
11
2
  export async function fsExists(path) {
12
3
  const res = await stat(path).catch(error => {
13
4
  if (error?.code === 'ENOENT') {
package/lib/glob.d.ts CHANGED
@@ -1,10 +1,5 @@
1
1
  /**
2
- * Cross-platform glob wrapper that normalizes path separators before matching.
3
- *
4
2
  * Converts backslashes (Windows `path.sep`) to forward slashes so that
5
3
  * glob patterns work consistently across operating systems.
6
- *
7
- * @param pattern - The glob pattern to match against (may contain OS-specific separators).
8
- * @returns An array of file paths matching the pattern.
9
4
  */
10
5
  export declare const glob: (pattern: string) => Promise<string[]>;
package/lib/glob.js CHANGED
@@ -1,13 +1,8 @@
1
1
  import path from 'node:path';
2
2
  import { glob as origin } from 'glob';
3
3
  /**
4
- * Cross-platform glob wrapper that normalizes path separators before matching.
5
- *
6
4
  * Converts backslashes (Windows `path.sep`) to forward slashes so that
7
5
  * glob patterns work consistently across operating systems.
8
- *
9
- * @param pattern - The glob pattern to match against (may contain OS-specific separators).
10
- * @returns An array of file paths matching the pattern.
11
6
  */
12
7
  export const glob = async (pattern) => {
13
8
  const normalized = pattern.split(path.sep).join('/');
@@ -1,18 +1,4 @@
1
1
  import type { CreateRuleCreatorParams, CreateRuleHelperResult } from './types.js';
2
- /**
3
- * Installs scaffold template files to the destination directory and optionally
4
- * generates a `package.json` with appropriate scripts and dependency declarations.
5
- *
6
- * This is the low-level function used by all scaffold strategies (core, project, package).
7
- * It copies template files from the built-in `scaffold/<type>` directory, applies
8
- * placeholder replacements, and sets up the project structure.
9
- *
10
- * @param scaffoldType - The type of scaffold to install ("core", "project", or "package").
11
- * @param dest - The absolute path to the destination directory.
12
- * @param params - The creation parameters, extended with an optional `packageJson` flag
13
- * indicating whether to generate a `package.json` file.
14
- * @returns The scaffold result containing the list of generated files and dependency arrays.
15
- */
16
2
  export declare function installScaffold(scaffoldType: 'core' | 'project' | 'package', dest: string, params: CreateRuleCreatorParams & {
17
3
  readonly packageJson?: boolean;
18
4
  }): Promise<CreateRuleHelperResult>;
@@ -2,20 +2,6 @@ import fs from 'node:fs/promises';
2
2
  import path from 'node:path';
3
3
  import { fsExists } from './fs-exists.js';
4
4
  import { transfer } from './transfer.js';
5
- /**
6
- * Installs scaffold template files to the destination directory and optionally
7
- * generates a `package.json` with appropriate scripts and dependency declarations.
8
- *
9
- * This is the low-level function used by all scaffold strategies (core, project, package).
10
- * It copies template files from the built-in `scaffold/<type>` directory, applies
11
- * placeholder replacements, and sets up the project structure.
12
- *
13
- * @param scaffoldType - The type of scaffold to install ("core", "project", or "package").
14
- * @param dest - The absolute path to the destination directory.
15
- * @param params - The creation parameters, extended with an optional `packageJson` flag
16
- * indicating whether to generate a `package.json` file.
17
- * @returns The scaffold result containing the list of generated files and dependency arrays.
18
- */
19
5
  export async function installScaffold(scaffoldType, dest, params) {
20
6
  const exists = await fsExists(dest);
21
7
  if (!exists) {
@@ -1,8 +1,4 @@
1
1
  /**
2
- * Determines whether the current working directory is inside the markuplint
3
- * core monorepo. This is used to conditionally offer the "Contribute to core"
4
- * option in the CLI wizard.
5
- *
6
- * @returns `true` if the cwd is within the markuplint monorepo, `false` otherwise.
2
+ * Used to conditionally offer the "Contribute to core" option in the CLI wizard.
7
3
  */
8
4
  export declare function isMarkuplintRepo(): Promise<boolean>;
@@ -1,10 +1,6 @@
1
1
  import { searchCoreRepository } from './search-core-repository.js';
2
2
  /**
3
- * Determines whether the current working directory is inside the markuplint
4
- * core monorepo. This is used to conditionally offer the "Contribute to core"
5
- * option in the CLI wizard.
6
- *
7
- * @returns `true` if the cwd is within the markuplint monorepo, `false` otherwise.
3
+ * Used to conditionally offer the "Contribute to core" option in the CLI wizard.
8
4
  */
9
5
  export async function isMarkuplintRepo() {
10
6
  const rootDir = await searchCoreRepository();
@@ -1,8 +1 @@
1
- /**
2
- * Reads the `package.json` file in the given directory and extracts the package name.
3
- *
4
- * @param dir - The directory containing the `package.json` file.
5
- * @returns The `name` field from the package.json, or `null` if the file
6
- * does not exist, cannot be parsed, or has no `name` field.
7
- */
8
1
  export declare function readPackageJson(dir: string): Promise<string | null>;
@@ -1,12 +1,5 @@
1
1
  import fs from 'node:fs/promises';
2
2
  import path from 'node:path';
3
- /**
4
- * Reads the `package.json` file in the given directory and extracts the package name.
5
- *
6
- * @param dir - The directory containing the `package.json` file.
7
- * @returns The `name` field from the package.json, or `null` if the file
8
- * does not exist, cannot be parsed, or has no `name` field.
9
- */
10
3
  export async function readPackageJson(dir) {
11
4
  const filePath = path.resolve(dir, 'package.json');
12
5
  try {
@@ -1,9 +1 @@
1
- /**
2
- * Searches for the markuplint core monorepo root by traversing up from the
3
- * current working directory. Identifies the root by checking for a
4
- * `package.json` with the name `"markuplint-packages"`.
5
- *
6
- * @returns The absolute path to the monorepo root directory, or `null` if
7
- * the current working directory is not within the markuplint repository.
8
- */
9
1
  export declare function searchCoreRepository(): Promise<string | null>;
@@ -1,13 +1,5 @@
1
1
  import path from 'node:path';
2
2
  import { readPackageJson } from './read-package-json.js';
3
- /**
4
- * Searches for the markuplint core monorepo root by traversing up from the
5
- * current working directory. Identifies the root by checking for a
6
- * `package.json` with the name `"markuplint-packages"`.
7
- *
8
- * @returns The absolute path to the monorepo root directory, or `null` if
9
- * the current working directory is not within the markuplint repository.
10
- */
11
3
  export async function searchCoreRepository() {
12
4
  const paths = path.resolve(process.cwd()).split(path.sep);
13
5
  while (true) {
package/lib/transfer.d.ts CHANGED
@@ -1,27 +1,13 @@
1
1
  import type { File } from './types.js';
2
- /**
3
- * Options controlling how scaffold files are transferred to their destination.
4
- */
5
2
  type TransferOptions = {
6
- /** Whether to transpile TypeScript files to JavaScript. */
7
3
  readonly transpile?: boolean;
8
- /** Whether to include test files in the transfer. */
9
4
  readonly test?: boolean;
10
- /** A mapping of placeholder names to replacement values for template substitution. */
11
5
  readonly replacer?: Readonly<Record<string, string | void>>;
12
6
  };
13
7
  /**
14
- * Transfers scaffold template files from a source directory to a destination directory.
15
- *
16
- * Scans all files in `baseDir`, processes each file through template substitution,
17
- * optional TypeScript-to-JavaScript transpilation, and Prettier formatting, then
18
- * writes the results to `destDir`.
19
- *
20
- * @param scaffoldType - The type of scaffold being transferred ("core", "project", or "package").
21
- * @param baseDir - The source directory containing scaffold template files.
22
- * @param destDir - The destination directory where processed files will be written.
23
- * @param options - Optional settings for transpilation, test inclusion, and placeholder replacement.
24
- * @returns An array of file metadata for each successfully transferred file.
8
+ * Template authoring constraint: because users may choose JavaScript output, every
9
+ * `.ts` template under `scaffold/` must remain a correct example after `tsc.transpile()`
10
+ * strips its type syntax.
25
11
  */
26
12
  export declare function transfer(scaffoldType: 'core' | 'project' | 'package', baseDir: string, destDir: string, options?: TransferOptions): Promise<File[]>;
27
13
  export {};
package/lib/transfer.js CHANGED
@@ -7,17 +7,9 @@ import { fsExists } from './fs-exists.js';
7
7
  import { glob } from './glob.js';
8
8
  const { transpile, ScriptTarget } = tsc;
9
9
  /**
10
- * Transfers scaffold template files from a source directory to a destination directory.
11
- *
12
- * Scans all files in `baseDir`, processes each file through template substitution,
13
- * optional TypeScript-to-JavaScript transpilation, and Prettier formatting, then
14
- * writes the results to `destDir`.
15
- *
16
- * @param scaffoldType - The type of scaffold being transferred ("core", "project", or "package").
17
- * @param baseDir - The source directory containing scaffold template files.
18
- * @param destDir - The destination directory where processed files will be written.
19
- * @param options - Optional settings for transpilation, test inclusion, and placeholder replacement.
20
- * @returns An array of file metadata for each successfully transferred file.
10
+ * Template authoring constraint: because users may choose JavaScript output, every
11
+ * `.ts` template under `scaffold/` must remain a correct example after `tsc.transpile()`
12
+ * strips its type syntax.
21
13
  */
22
14
  export async function transfer(scaffoldType, baseDir, destDir, options) {
23
15
  const files = await scan(baseDir, destDir);
@@ -51,7 +43,10 @@ async function transferFile(scaffoldType, file, options) {
51
43
  contents = contents.replaceAll(new RegExp(`__${before}__`, 'g'), after);
52
44
  }
53
45
  }
54
- // Remove prettier ignore comment
46
+ // Remove prettier ignore comment.
47
+ // The markers exist only so that formatters do not rewrite placeholder expressions
48
+ // (e.g. `__ruleName__c`) inside the scaffold templates; the output is fully
49
+ // reformatted by Prettier below, and generated files must not inherit the markers.
55
50
  contents = contents.replace(/\n\s*\/\/ prettier-ignore/, '');
56
51
  contents = contents.replace(/\n\s*<!-- prettier-ignore(?:-(?:start|end))? -->/, '');
57
52
  const newFile = { ...file };
@@ -62,6 +57,7 @@ async function transferFile(scaffoldType, file, options) {
62
57
  target: ScriptTarget.ESNext,
63
58
  }, newFile.filePath);
64
59
  // Insert new line before comments and the export keyword
60
+ // to restore readability, since transpiling drops the blank lines.
65
61
  contents = contents.replaceAll(/(\n)(\s+\/\*\*|export)/g, '$1\n$2');
66
62
  }
67
63
  const candidateName = options?.replacer?.[newFile.name.replaceAll('_', '')];