@metreeca/rack 0.1.0 → 0.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/README.md CHANGED
@@ -2,14 +2,19 @@
2
2
 
3
3
  [![npm](https://img.shields.io/npm/v/@metreeca/rack)](https://www.npmjs.com/package/@metreeca/rack)
4
4
 
5
- Shared development tooling for @metreeca packages.
5
+ Everyday tooling for npm package development.
6
6
 
7
- **@metreeca/rack** collects the build, test and documentation tooling shared across @metreeca packages, so each package
8
- consumes a single maintained copy instead of carrying its own. Tools are grouped by the toolchain they extend:
7
+ **@metreeca/rack** collects build, test and documentation tooling for recurring npm package development needs, so each
8
+ package consumes a single maintained copy instead of carrying its own. Tools are grouped by the toolchain they extend:
9
9
 
10
- | Subpath | Contents |
11
- |-----------------------|--------------|
12
- | `@metreeca/rack/vite` | Vite plugins |
10
+ | Subpath | Contents |
11
+ |-----------------------------|--------------|
12
+ | [@metreeca/rack/tool][tool] | CLI tools |
13
+ | [@metreeca/rack/vite][vite] | Vite plugins |
14
+
15
+ [tool]: https://metreeca.github.io/rack/modules/tool.html
16
+
17
+ [vite]: https://metreeca.github.io/rack/modules/vite.html
13
18
 
14
19
  # Installation
15
20
 
@@ -0,0 +1,30 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Copies the files matching a set of glob patterns into a target directory, keeping their relative layout.
4
+ *
5
+ * Each match lands under `target` at its path relative to the base of its pattern, that is the leading segments free
6
+ * of glob characters: `src/**\/*.css` copies `src/a/b.css` to `<target>/a/b.css`, and a plain file path copies the
7
+ * file to the top of `target`. Missing directories are created and existing files are overwritten.
8
+ *
9
+ * Directories matched by a pattern are skipped: only files are copied.
10
+ *
11
+ * Each copied file is printed on stdout, one per line, by its source path as the pattern matched it.
12
+ *
13
+ * @example
14
+ *
15
+ * Shipping stylesheets next to the compiled modules, from an npm script:
16
+ *
17
+ * ```shell
18
+ * cp dist 'src/**\/*.css'
19
+ * ```
20
+ *
21
+ * Patterns are quoted so that they reach the tool unexpanded, as shells differ in their glob support.
22
+ *
23
+ * @param target The directory receiving the copies
24
+ * @param patterns The glob patterns selecting the files to copy
25
+ *
26
+ * @throws TypeError If `target` is empty or if no pattern is provided
27
+ * @throws Error If a pattern matches no file, in which case no file is copied for any of the patterns
28
+ */
29
+ export declare function cp(target: string, ...patterns: readonly string[]): void;
30
+ //# sourceMappingURL=cp.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cp.d.ts","sourceRoot":"","sources":["../../src/tool/cp.ts"],"names":[],"mappings":";AA8BA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,QAAQ,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI,CAiCvE"}
@@ -0,0 +1,74 @@
1
+ #!/usr/bin/env node
2
+ /*
3
+ * Copyright © 2026 Metreeca srl
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ import { copyFileSync, globSync, mkdirSync, statSync } from "node:fs";
18
+ import { dirname, join, relative } from "node:path";
19
+ import { cli } from "./index.core.js";
20
+ if (import.meta.main) {
21
+ process.exitCode = cli(cp, "<target> <pattern>...");
22
+ }
23
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
24
+ /**
25
+ * Copies the files matching a set of glob patterns into a target directory, keeping their relative layout.
26
+ *
27
+ * Each match lands under `target` at its path relative to the base of its pattern, that is the leading segments free
28
+ * of glob characters: `src/**\/*.css` copies `src/a/b.css` to `<target>/a/b.css`, and a plain file path copies the
29
+ * file to the top of `target`. Missing directories are created and existing files are overwritten.
30
+ *
31
+ * Directories matched by a pattern are skipped: only files are copied.
32
+ *
33
+ * Each copied file is printed on stdout, one per line, by its source path as the pattern matched it.
34
+ *
35
+ * @example
36
+ *
37
+ * Shipping stylesheets next to the compiled modules, from an npm script:
38
+ *
39
+ * ```shell
40
+ * cp dist 'src/**\/*.css'
41
+ * ```
42
+ *
43
+ * Patterns are quoted so that they reach the tool unexpanded, as shells differ in their glob support.
44
+ *
45
+ * @param target The directory receiving the copies
46
+ * @param patterns The glob patterns selecting the files to copy
47
+ *
48
+ * @throws TypeError If `target` is empty or if no pattern is provided
49
+ * @throws Error If a pattern matches no file, in which case no file is copied for any of the patterns
50
+ */
51
+ export function cp(target, ...patterns) {
52
+ if (!target) {
53
+ throw new TypeError("missing target operand");
54
+ }
55
+ if (!patterns.length) {
56
+ throw new TypeError("missing pattern operand");
57
+ }
58
+ patterns.flatMap(copies).forEach(({ source, destination }) => {
59
+ mkdirSync(dirname(destination), { recursive: true });
60
+ copyFileSync(source, destination);
61
+ console.log(source);
62
+ });
63
+ function copies(pattern) {
64
+ const sources = globSync(pattern).filter(source => statSync(source).isFile());
65
+ if (!sources.length) {
66
+ throw new Error(`unmatched pattern <${pattern}>`);
67
+ }
68
+ const segments = pattern.split("/");
69
+ const wild = segments.findIndex(segment => /[*?[\]{}]/.test(segment));
70
+ const base = wild < 0 ? dirname(pattern) : segments.slice(0, wild).join("/") || ".";
71
+ return sources.map(source => ({ source, destination: join(target, relative(base, source)) }));
72
+ }
73
+ }
74
+ //# sourceMappingURL=cp.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cp.js","sourceRoot":"","sources":["../../src/tool/cp.ts"],"names":[],"mappings":";AAEA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACtE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AACpD,OAAO,EAAE,GAAG,EAAE,MAAM,iBAAiB,CAAC;AAGtC,IAAK,MAAM,CAAC,IAAI,CAAC,IAAI,EAAG,CAAC;IACxB,OAAO,CAAC,QAAQ,GAAG,GAAG,CAAC,EAAE,EAAE,uBAAuB,CAAC,CAAC;AACrD,CAAC;AAGD,wHAAwH;AAExH;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,MAAM,UAAU,EAAE,CAAC,MAAc,EAAE,GAAG,QAA2B;IAEhE,IAAK,CAAC,MAAM,EAAG,CAAC;QACf,MAAM,IAAI,SAAS,CAAC,wBAAwB,CAAC,CAAC;IAC/C,CAAC;IAED,IAAK,CAAC,QAAQ,CAAC,MAAM,EAAG,CAAC;QACxB,MAAM,IAAI,SAAS,CAAC,yBAAyB,CAAC,CAAC;IAChD,CAAC;IAED,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,CAAC,EAAE,MAAM,EAAE,WAAW,EAAE,EAAE,EAAE;QAC5D,SAAS,CAAC,OAAO,CAAC,WAAW,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACrD,YAAY,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;QAClC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IACrB,CAAC,CAAC,CAAC;IAGH,SAAS,MAAM,CAAC,OAAe;QAE9B,MAAM,OAAO,GAAG,QAAQ,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC;QAE9E,IAAK,CAAC,OAAO,CAAC,MAAM,EAAG,CAAC;YACvB,MAAM,IAAI,KAAK,CAAC,sBAAsB,OAAO,GAAG,CAAC,CAAC;QACnD,CAAC;QAED,MAAM,QAAQ,GAAG,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QACpC,MAAM,IAAI,GAAG,QAAQ,CAAC,SAAS,CAAC,OAAO,CAAC,EAAE,CAAC,WAAW,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC;QACtE,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC;QAEpF,OAAO,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,WAAW,EAAE,IAAI,CAAC,MAAM,EAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAE/F,CAAC;AAEF,CAAC"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Runs a tool on a command line, with GNU-style help and error messages.
3
+ *
4
+ * The tool receives the operands as its arguments, with `undefined` in place of a missing one, and throws a
5
+ * `TypeError` on a command line it cannot act on, so that the error message also points to the help.
6
+ *
7
+ * `--help` and `-h` print the usage line to stdout and skip the tool. Errors go to stderr prefixed with the tool name;
8
+ * usage errors, including unrecognized options, also point to `--help`. Operands starting with `-` follow `--`.
9
+ *
10
+ * @param tool The tool to run on the operands
11
+ * @param help The operand synopsis printed by `--help` after the tool name, such as `<target> <pattern>...`
12
+ * @param name The tool name prefixing messages; defaults to the script name without extension, else the `tool` name
13
+ * @param args The command line arguments after the script path; defaults to those the process was launched with
14
+ *
15
+ * @returns The process exit status: `0` on success, `1` on failure
16
+ */
17
+ export declare function cli(tool: (...args: string[]) => void, help: string, name?: string, args?: readonly string[]): number;
18
+ //# sourceMappingURL=index.core.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.core.d.ts","sourceRoot":"","sources":["../../src/tool/index.core.ts"],"names":[],"mappings":"AAoBA;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,GAAG,CAClB,IAAI,EAAE,CAAC,GAAG,IAAI,EAAE,MAAM,EAAE,KAAK,IAAI,EACjC,IAAI,EAAE,MAAM,EACZ,IAAI,GAAE,MAAgF,EACtF,IAAI,GAAE,SAAS,MAAM,EAA0B,GAC7C,MAAM,CAoDR"}
@@ -0,0 +1,66 @@
1
+ /*
2
+ * Copyright © 2026 Metreeca srl
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+ import { parse } from "node:path";
17
+ import { parseArgs } from "node:util";
18
+ /**
19
+ * Runs a tool on a command line, with GNU-style help and error messages.
20
+ *
21
+ * The tool receives the operands as its arguments, with `undefined` in place of a missing one, and throws a
22
+ * `TypeError` on a command line it cannot act on, so that the error message also points to the help.
23
+ *
24
+ * `--help` and `-h` print the usage line to stdout and skip the tool. Errors go to stderr prefixed with the tool name;
25
+ * usage errors, including unrecognized options, also point to `--help`. Operands starting with `-` follow `--`.
26
+ *
27
+ * @param tool The tool to run on the operands
28
+ * @param help The operand synopsis printed by `--help` after the tool name, such as `<target> <pattern>...`
29
+ * @param name The tool name prefixing messages; defaults to the script name without extension, else the `tool` name
30
+ * @param args The command line arguments after the script path; defaults to those the process was launched with
31
+ *
32
+ * @returns The process exit status: `0` on success, `1` on failure
33
+ */
34
+ export function cli(tool, help, name = process.argv[1] !== undefined ? parse(process.argv[1]).name : tool.name, args = process.argv.slice(2)) {
35
+ const { values, positionals, tokens } = parseArgs({
36
+ args: [...args],
37
+ options: { help: { type: "boolean", short: "h" } },
38
+ allowPositionals: true,
39
+ strict: false, // unknown options are rejected below, with a GNU-style message
40
+ tokens: true
41
+ });
42
+ const unknown = tokens.find(token => token.kind === "option" && token.name !== "help");
43
+ if (unknown?.kind === "option") {
44
+ return fail(`unrecognized option <${unknown.rawName}>`, true);
45
+ }
46
+ else if (values.help) {
47
+ console.log(`Usage: ${name} ${help}`);
48
+ return 0;
49
+ }
50
+ else {
51
+ try {
52
+ tool(...positionals);
53
+ return 0;
54
+ }
55
+ catch (error) {
56
+ return fail(error instanceof Error ? error.message : String(error), error instanceof TypeError);
57
+ }
58
+ }
59
+ function fail(message, usage) {
60
+ console.error(usage
61
+ ? `${name}: ${message}\nTry '${name} --help' for more information.`
62
+ : `${name}: ${message}`);
63
+ return 1;
64
+ }
65
+ }
66
+ //# sourceMappingURL=index.core.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.core.js","sourceRoot":"","sources":["../../src/tool/index.core.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,KAAK,EAAE,MAAM,WAAW,CAAC;AAClC,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAGtC;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,GAAG,CAClB,IAAiC,EACjC,IAAY,EACZ,OAAe,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,EACtF,OAA0B,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;IAG/C,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC;QACjD,IAAI,EAAE,CAAC,GAAG,IAAI,CAAC;QACf,OAAO,EAAE,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE;QAClD,gBAAgB,EAAE,IAAI;QACtB,MAAM,EAAE,KAAK,EAAE,+DAA+D;QAC9E,MAAM,EAAE,IAAI;KACZ,CAAC,CAAC;IAEH,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CACnC,KAAK,CAAC,IAAI,KAAK,QAAQ,IAAI,KAAK,CAAC,IAAI,KAAK,MAAM,CAChD,CAAC;IAEF,IAAK,OAAO,EAAE,IAAI,KAAK,QAAQ,EAAG,CAAC;QAElC,OAAO,IAAI,CAAC,wBAAwB,OAAO,CAAC,OAAO,GAAG,EAAE,IAAI,CAAC,CAAC;IAE/D,CAAC;SAAM,IAAK,MAAM,CAAC,IAAI,EAAG,CAAC;QAE1B,OAAO,CAAC,GAAG,CAAC,UAAU,IAAI,IAAI,IAAI,EAAE,CAAC,CAAC;QAEtC,OAAO,CAAC,CAAC;IAEV,CAAC;SAAM,CAAC;QAEP,IAAI,CAAC;YAEJ,IAAI,CAAC,GAAG,WAAW,CAAC,CAAC;YAErB,OAAO,CAAC,CAAC;QAEV,CAAC;QAAC,OAAQ,KAAK,EAAG,CAAC;YAElB,OAAO,IAAI,CAAC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,KAAK,YAAY,SAAS,CAAC,CAAC;QAEjG,CAAC;IAEF,CAAC;IAGD,SAAS,IAAI,CAAC,OAAe,EAAE,KAAc;QAE5C,OAAO,CAAC,KAAK,CAAC,KAAK;YAClB,CAAC,CAAC,GAAG,IAAI,KAAK,OAAO,UAAU,IAAI,gCAAgC;YACnE,CAAC,CAAC,GAAG,IAAI,KAAK,OAAO,EAAE,CACvB,CAAC;QAEF,OAAO,CAAC,CAAC;IAEV,CAAC;AAEF,CAAC"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Unix-like command line tools for npm package scripts.
3
+ *
4
+ * Each tool is installed as a command alongside the package and behaves the same on every platform and shell. Inside
5
+ * npm scripts, these commands take precedence over system commands with the same name, such as the Unix `cp`.
6
+ *
7
+ * Every tool lists the paths it acts on to stdout, one per line, so that a script log shows what changed on disk. It
8
+ * prints its usage with `--help`, and exits with a non-zero status after printing an error message to stderr.
9
+ *
10
+ * | Command | Purpose |
11
+ * |---------------------------------------|--------------------------------------------------------------------------|
12
+ * | {@link cp `cp <target> <pattern>...`} | Copies files matching glob patterns to a directory, keeping their layout |
13
+ * | {@link rm `rm <pattern>...`} | Removes files and directories matching glob patterns, with their content |
14
+ * | {@link ln `ln [<deps>]...`} | Links dependencies to their local checkouts instead of registry releases |
15
+ *
16
+ * Build scripts can call the same tools as functions, passing the command operands as arguments: a function throws on
17
+ * failure instead of printing an error message to stderr and setting a non-zero exit status for the process.
18
+ *
19
+ * @module
20
+ */
21
+ export { cp } from "./cp.js";
22
+ export { ln } from "./ln.js";
23
+ export { rm } from "./rm.js";
24
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/tool/index.ts"],"names":[],"mappings":"AAgBA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAAE,EAAE,EAAE,MAAM,SAAS,CAAC;AAC7B,OAAO,EAAE,EAAE,EAAE,MAAM,SAAS,CAAC;AAC7B,OAAO,EAAE,EAAE,EAAE,MAAM,SAAS,CAAC"}
@@ -0,0 +1,39 @@
1
+ /*
2
+ * Copyright © 2026 Metreeca srl
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+ /**
17
+ * Unix-like command line tools for npm package scripts.
18
+ *
19
+ * Each tool is installed as a command alongside the package and behaves the same on every platform and shell. Inside
20
+ * npm scripts, these commands take precedence over system commands with the same name, such as the Unix `cp`.
21
+ *
22
+ * Every tool lists the paths it acts on to stdout, one per line, so that a script log shows what changed on disk. It
23
+ * prints its usage with `--help`, and exits with a non-zero status after printing an error message to stderr.
24
+ *
25
+ * | Command | Purpose |
26
+ * |---------------------------------------|--------------------------------------------------------------------------|
27
+ * | {@link cp `cp <target> <pattern>...`} | Copies files matching glob patterns to a directory, keeping their layout |
28
+ * | {@link rm `rm <pattern>...`} | Removes files and directories matching glob patterns, with their content |
29
+ * | {@link ln `ln [<deps>]...`} | Links dependencies to their local checkouts instead of registry releases |
30
+ *
31
+ * Build scripts can call the same tools as functions, passing the command operands as arguments: a function throws on
32
+ * failure instead of printing an error message to stderr and setting a non-zero exit status for the process.
33
+ *
34
+ * @module
35
+ */
36
+ export { cp } from "./cp.js";
37
+ export { ln } from "./ln.js";
38
+ export { rm } from "./rm.js";
39
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/tool/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAAE,EAAE,EAAE,MAAM,SAAS,CAAC;AAC7B,OAAO,EAAE,EAAE,EAAE,MAAM,SAAS,CAAC;AAC7B,OAAO,EAAE,EAAE,EAAE,MAAM,SAAS,CAAC"}
@@ -0,0 +1,47 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Links the local checkouts of the dependencies of the package in the current folder into its `node_modules`.
4
+ *
5
+ * The current package, and in a monorepo each of its workspace members, then builds and runs against the working
6
+ * trees of its dependencies instead of their registry releases. Dependencies are matched to checkouts by package name.
7
+ *
8
+ * - **Deps**: each folder searched is either a package or a folder holding packages as direct children; a monorepo
9
+ * found there contributes its workspace members as well, wherever its `workspaces` patterns place them
10
+ * - **Selection**: links every package named in the `dependencies`, `devDependencies`, `peerDependencies` or
11
+ * `optionalDependencies` of the current package or its members. Each linked package then brings in the checkouts
12
+ * named in its own `dependencies`, `peerDependencies` or `optionalDependencies`. A package shared along the chain
13
+ * is thus loaded once from its working tree, never also as a registry copy with distinct classes and symbols
14
+ * - **Copies**: installed copies of a linked package are replaced with links at any depth, both in the current
15
+ * package and in linked checkouts, since a linked package resolves its own imports from its checkout
16
+ * - **Commands**: the `bin` commands of each linked package are linked into the `.bin` folder next to it and made
17
+ * executable, so that npm scripts run the working tree version of a command rather than its registry release
18
+ * - **Precedence**: where a name is found in more than one folder, the folder listed first wins
19
+ * - **Versions**: version ranges are ignored, as a working tree may carry a version that is not released yet
20
+ *
21
+ * Links replace whatever the current package has installed under the same name. Inside a linked checkout, they only
22
+ * replace installed copies and never add new packages. Missing folders are skipped, so the same command runs
23
+ * unchanged on every machine, including machines where some of the named folders hold no local checkouts at all.
24
+ *
25
+ * Each package link is printed on stdout, relative to the current package, such as `node_modules/@scope/name`; links
26
+ * to `bin` commands are created silently, since they follow from the package links already listed.
27
+ *
28
+ * @example
29
+ *
30
+ * Linking the sibling checkouts of a package after a clean install, from an npm script:
31
+ *
32
+ * ```shell
33
+ * npm ci && ln
34
+ * ```
35
+ *
36
+ * Linking from a folder of local dependencies elsewhere:
37
+ *
38
+ * ```shell
39
+ * npm ci && ln ~/Metreeca/Packages
40
+ * ```
41
+ *
42
+ * @param deps The folders searched for local dependencies, in order of precedence; defaults to the parent folder (`..`)
43
+ *
44
+ * @throws Error If the current folder holds no `package.json`
45
+ */
46
+ export declare function ln(...deps: readonly string[]): void;
47
+ //# sourceMappingURL=ln.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ln.d.ts","sourceRoot":"","sources":["../../src/tool/ln.ts"],"names":[],"mappings":";AA0CA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AACH,wBAAgB,EAAE,CAAC,GAAG,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI,CAiLnD"}
@@ -0,0 +1,187 @@
1
+ #!/usr/bin/env node
2
+ /*
3
+ * Copyright © 2026 Metreeca srl
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ import { chmodSync, existsSync, globSync, lstatSync, mkdirSync, readdirSync, readFileSync, realpathSync, rmSync, statSync, symlinkSync } from "node:fs";
18
+ import { dirname, join, relative, resolve } from "node:path";
19
+ import { cli } from "./index.core.js";
20
+ if (import.meta.main) {
21
+ process.exitCode = cli(ln, "[<deps>]...");
22
+ }
23
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
24
+ /**
25
+ * Links the local checkouts of the dependencies of the package in the current folder into its `node_modules`.
26
+ *
27
+ * The current package, and in a monorepo each of its workspace members, then builds and runs against the working
28
+ * trees of its dependencies instead of their registry releases. Dependencies are matched to checkouts by package name.
29
+ *
30
+ * - **Deps**: each folder searched is either a package or a folder holding packages as direct children; a monorepo
31
+ * found there contributes its workspace members as well, wherever its `workspaces` patterns place them
32
+ * - **Selection**: links every package named in the `dependencies`, `devDependencies`, `peerDependencies` or
33
+ * `optionalDependencies` of the current package or its members. Each linked package then brings in the checkouts
34
+ * named in its own `dependencies`, `peerDependencies` or `optionalDependencies`. A package shared along the chain
35
+ * is thus loaded once from its working tree, never also as a registry copy with distinct classes and symbols
36
+ * - **Copies**: installed copies of a linked package are replaced with links at any depth, both in the current
37
+ * package and in linked checkouts, since a linked package resolves its own imports from its checkout
38
+ * - **Commands**: the `bin` commands of each linked package are linked into the `.bin` folder next to it and made
39
+ * executable, so that npm scripts run the working tree version of a command rather than its registry release
40
+ * - **Precedence**: where a name is found in more than one folder, the folder listed first wins
41
+ * - **Versions**: version ranges are ignored, as a working tree may carry a version that is not released yet
42
+ *
43
+ * Links replace whatever the current package has installed under the same name. Inside a linked checkout, they only
44
+ * replace installed copies and never add new packages. Missing folders are skipped, so the same command runs
45
+ * unchanged on every machine, including machines where some of the named folders hold no local checkouts at all.
46
+ *
47
+ * Each package link is printed on stdout, relative to the current package, such as `node_modules/@scope/name`; links
48
+ * to `bin` commands are created silently, since they follow from the package links already listed.
49
+ *
50
+ * @example
51
+ *
52
+ * Linking the sibling checkouts of a package after a clean install, from an npm script:
53
+ *
54
+ * ```shell
55
+ * npm ci && ln
56
+ * ```
57
+ *
58
+ * Linking from a folder of local dependencies elsewhere:
59
+ *
60
+ * ```shell
61
+ * npm ci && ln ~/Metreeca/Packages
62
+ * ```
63
+ *
64
+ * @param deps The folders searched for local dependencies, in order of precedence; defaults to the parent folder (`..`)
65
+ *
66
+ * @throws Error If the current folder holds no `package.json`
67
+ */
68
+ export function ln(...deps) {
69
+ const home = resolve(".");
70
+ const local = join(home, "package.json");
71
+ if (!existsSync(local)) {
72
+ throw new Error(`missing package manifest <${local}>`);
73
+ }
74
+ const own = packages(home);
75
+ const names = new Set(own.map(path => manifest(path).name));
76
+ const index = new Map((deps.length ? deps : [".."])
77
+ .map(dep => resolve(dep))
78
+ .flatMap(dep => existsSync(join(dep, "package.json"))
79
+ ? [dep]
80
+ : children(dep).map(name => join(dep, name)).filter(folder))
81
+ .flatMap(packages)
82
+ .map((path) => [manifest(path).name, path])
83
+ .filter(([name]) => !names.has(name))
84
+ .reverse() // later entries override earlier ones, so that the folder listed first wins
85
+ );
86
+ const linked = closure(own.flatMap(path => dependencies(path, true)));
87
+ const roots = new Set([home, ...[...linked.values()].map(path => checkout(path))]);
88
+ linked.forEach((path, name) => link({ modules: join(home, "node_modules"), name, path }));
89
+ [...roots]
90
+ .flatMap(packages)
91
+ .flatMap(path => copies(join(path, "node_modules")))
92
+ .forEach(link);
93
+ function link({ modules, name, path }) {
94
+ const target = join(modules, name);
95
+ replace(target, path);
96
+ console.log(relative(home, target));
97
+ commands(path, name).forEach(([command, script]) => {
98
+ replace(join(modules, ".bin", command), join(target, script));
99
+ if (existsSync(join(path, script))) {
100
+ chmodSync(join(path, script), 0o755); // as npm does, since builds such as tsc emit scripts without the mode
101
+ }
102
+ });
103
+ }
104
+ function replace(link, target) {
105
+ rmSync(link, { recursive: true, force: true }); // removes links without following them
106
+ mkdirSync(dirname(link), { recursive: true });
107
+ symlinkSync(relative(dirname(link), target), link, "junction"); // type ignored outside Windows
108
+ }
109
+ function commands(path, name) {
110
+ const { bin } = manifest(path);
111
+ if (process.platform === "win32") {
112
+ return []; // npm exposes commands through generated shims rather than links
113
+ }
114
+ else if (typeof bin === "string") {
115
+ return [[name.replace(/^@[^/]+\//, ""), bin]];
116
+ }
117
+ else {
118
+ return Object.entries(bin ?? {});
119
+ }
120
+ }
121
+ function copies(modules) {
122
+ return children(modules)
123
+ .filter(name => !name.startsWith("."))
124
+ .flatMap(name => name.startsWith("@")
125
+ ? children(join(modules, name)).map(member => `${name}/${member}`)
126
+ : [name])
127
+ .flatMap(name => {
128
+ const copy = join(modules, name);
129
+ const path = linked.get(name);
130
+ if (path !== undefined) {
131
+ return existsSync(copy) && realpathSync(copy) === realpathSync(path) ? [] : [{ modules, name, path }];
132
+ }
133
+ else if (lstatSync(copy).isSymbolicLink()) {
134
+ return []; // a link into another checkout, inspected as a root of its own when linked
135
+ }
136
+ else {
137
+ return copies(join(copy, "node_modules"));
138
+ }
139
+ });
140
+ }
141
+ function packages(path) {
142
+ return existsSync(join(path, "package.json")) ? [path, ...(manifest(path).workspaces ?? [])
143
+ .flatMap(pattern => globSync(pattern, { cwd: path }))
144
+ .map(member => join(path, member))
145
+ .filter(member => existsSync(join(member, "package.json")))
146
+ ] : [];
147
+ }
148
+ function checkout(path, parent = dirname(path)) {
149
+ if (parent === dirname(parent)) {
150
+ return path;
151
+ }
152
+ else if (existsSync(join(parent, "package.json"))) {
153
+ return packages(parent).includes(path) ? parent : path;
154
+ }
155
+ else {
156
+ return checkout(path, dirname(parent));
157
+ }
158
+ }
159
+ function children(path) {
160
+ return folder(path) ? readdirSync(path) : [];
161
+ }
162
+ function folder(path) {
163
+ return statSync(path, { throwIfNoEntry: false })?.isDirectory() ?? false;
164
+ }
165
+ function closure(required, closed = new Map()) {
166
+ const fresh = new Map(required.flatMap((name) => {
167
+ const path = index.get(name);
168
+ return path !== undefined && !closed.has(name) ? [[name, path]] : [];
169
+ }));
170
+ return fresh.size
171
+ ? closure([...fresh.values()].flatMap(path => dependencies(path)), new Map([...closed, ...fresh]))
172
+ : closed;
173
+ }
174
+ function dependencies(path, dev = false) {
175
+ const { dependencies, devDependencies, peerDependencies, optionalDependencies } = manifest(path);
176
+ return Object.keys({
177
+ ...dependencies,
178
+ ...(dev ? devDependencies : {}),
179
+ ...peerDependencies,
180
+ ...optionalDependencies
181
+ });
182
+ }
183
+ function manifest(path) {
184
+ return JSON.parse(readFileSync(join(path, "package.json"), "utf8")); // trusted local file, not validated
185
+ }
186
+ }
187
+ //# sourceMappingURL=ln.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ln.js","sourceRoot":"","sources":["../../src/tool/ln.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Removes the files and directories matching a set of glob patterns, with their whole content.
4
+ *
5
+ * Patterns matching nothing are ignored, so that the same command can run again on an already clean tree.
6
+ *
7
+ * Each removed file or directory is printed on stdout once, relative to the current folder. Matches inside a removed
8
+ * directory are not listed, even where a pattern selects them explicitly, as they go along with their ancestor.
9
+ *
10
+ * @example
11
+ *
12
+ * Removing build outputs, from an npm script:
13
+ *
14
+ * ```shell
15
+ * rm dist docs 'src/**\/*.tsbuildinfo'
16
+ * ```
17
+ *
18
+ * Patterns are quoted so that they reach the tool unexpanded, as shells differ in their glob support.
19
+ *
20
+ * @param patterns The glob patterns selecting the files and directories to remove
21
+ *
22
+ * @throws TypeError If no pattern is provided
23
+ */
24
+ export declare function rm(...patterns: readonly string[]): void;
25
+ //# sourceMappingURL=rm.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rm.d.ts","sourceRoot":"","sources":["../../src/tool/rm.ts"],"names":[],"mappings":";AA8BA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,EAAE,CAAC,GAAG,QAAQ,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI,CAcvD"}
@@ -0,0 +1,57 @@
1
+ #!/usr/bin/env node
2
+ /*
3
+ * Copyright © 2026 Metreeca srl
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ import { globSync, rmSync } from "node:fs";
18
+ import { relative, resolve, sep } from "node:path";
19
+ import { cli } from "./index.core.js";
20
+ if (import.meta.main) {
21
+ process.exitCode = cli(rm, "<pattern>...");
22
+ }
23
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
24
+ /**
25
+ * Removes the files and directories matching a set of glob patterns, with their whole content.
26
+ *
27
+ * Patterns matching nothing are ignored, so that the same command can run again on an already clean tree.
28
+ *
29
+ * Each removed file or directory is printed on stdout once, relative to the current folder. Matches inside a removed
30
+ * directory are not listed, even where a pattern selects them explicitly, as they go along with their ancestor.
31
+ *
32
+ * @example
33
+ *
34
+ * Removing build outputs, from an npm script:
35
+ *
36
+ * ```shell
37
+ * rm dist docs 'src/**\/*.tsbuildinfo'
38
+ * ```
39
+ *
40
+ * Patterns are quoted so that they reach the tool unexpanded, as shells differ in their glob support.
41
+ *
42
+ * @param patterns The glob patterns selecting the files and directories to remove
43
+ *
44
+ * @throws TypeError If no pattern is provided
45
+ */
46
+ export function rm(...patterns) {
47
+ if (!patterns.length) {
48
+ throw new TypeError("missing pattern operand");
49
+ }
50
+ const matches = [...new Set(patterns.flatMap(pattern => globSync(pattern)).map(path => resolve(path)))];
51
+ const paths = matches.filter(path => !matches.some(match => path.startsWith(match + sep))); // gone with ancestors
52
+ paths.forEach(path => {
53
+ rmSync(path, { recursive: true, force: true });
54
+ console.log(relative(".", path));
55
+ });
56
+ }
57
+ //# sourceMappingURL=rm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rm.js","sourceRoot":"","sources":["../../src/tool/rm.ts"],"names":[],"mappings":";AAEA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAC3C,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AACnD,OAAO,EAAE,GAAG,EAAE,MAAM,iBAAiB,CAAC;AAGtC,IAAK,MAAM,CAAC,IAAI,CAAC,IAAI,EAAG,CAAC;IACxB,OAAO,CAAC,QAAQ,GAAG,GAAG,CAAC,EAAE,EAAE,cAAc,CAAC,CAAC;AAC5C,CAAC;AAGD,wHAAwH;AAExH;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,UAAU,EAAE,CAAC,GAAG,QAA2B;IAEhD,IAAK,CAAC,QAAQ,CAAC,MAAM,EAAG,CAAC;QACxB,MAAM,IAAI,SAAS,CAAC,yBAAyB,CAAC,CAAC;IAChD,CAAC;IAED,MAAM,OAAO,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;IACxG,MAAM,KAAK,GAAG,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,KAAK,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,sBAAsB;IAElH,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE;QACpB,MAAM,CAAC,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QAC/C,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC,CAAC;IAClC,CAAC,CAAC,CAAC;AAEJ,CAAC"}
@@ -1,7 +1,11 @@
1
1
  /**
2
- * Vite plugins for building and testing `@metreeca` packages.
2
+ * Vite plugins for building and testing npm packages.
3
3
  *
4
- * @module vite
4
+ * | Plugin | Purpose |
5
+ * |------------------|---------------------------------------------------------------------------------|
6
+ * | {@link symlinks} | Runs tests against the live source of locally linked packages, with no rebuild |
7
+ *
8
+ * @module
5
9
  */
6
- export {};
10
+ export { symlinks } from "./symlinks.js";
7
11
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/vite/index.ts"],"names":[],"mappings":"AAgBA;;;;GAIG;AAEH,OAAO,EAAE,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/vite/index.ts"],"names":[],"mappings":"AAgBA;;;;;;;;GAQG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC"}
@@ -13,5 +13,14 @@
13
13
  * See the License for the specific language governing permissions and
14
14
  * limitations under the License.
15
15
  */
16
- export {};
16
+ /**
17
+ * Vite plugins for building and testing npm packages.
18
+ *
19
+ * | Plugin | Purpose |
20
+ * |------------------|---------------------------------------------------------------------------------|
21
+ * | {@link symlinks} | Runs tests against the live source of locally linked packages, with no rebuild |
22
+ *
23
+ * @module
24
+ */
25
+ export { symlinks } from "./symlinks.js";
17
26
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/vite/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/vite/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH;;;;;;;;GAQG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC"}
@@ -0,0 +1,48 @@
1
+ import type { Plugin } from "vite";
2
+ /**
3
+ * Creates a plugin running tests against the live source of locally linked packages, with no rebuild.
4
+ *
5
+ * Imports of workspace members and of sibling checkouts linked into `node_modules` resolve to their TypeScript source,
6
+ * so edits are picked up as they are saved. Packages installed from the registry keep resolving to their build.
7
+ *
8
+ * Packages are looked up as Node does, from the `node_modules` nearest to the importer up through its ancestors, so
9
+ * links hoisted to a workspace root are found from any member; imports with no importer start from the Vite root.
10
+ *
11
+ * The plugin replaces `resolve.conditions`, which breaks registry packages whose exports name a condition without
12
+ * publishing its target: Vite does not fall back to `default` when that target is missing from the installed files.
13
+ *
14
+ * @example
15
+ *
16
+ * Linked packages map each exported subpath to its source under a custom condition, next to the build:
17
+ *
18
+ * ```json
19
+ * {
20
+ * "exports": {
21
+ * ".": { "source": "./src/index.ts", "default": "./dist/index.js" }
22
+ * }
23
+ * }
24
+ * ```
25
+ *
26
+ * The consuming project registers the plugin with the same condition in its Vite or Vitest configuration:
27
+ *
28
+ * ```typescript
29
+ * import { symlinks } from "@metreeca/rack/vite";
30
+ * import { defineConfig } from "vitest/config";
31
+ *
32
+ * export default defineConfig({
33
+ * plugins: [symlinks({ conditions: "source" })]
34
+ * });
35
+ * ```
36
+ *
37
+ * @param options The resolution settings
38
+ *
39
+ * @returns A plugin resolving linked imports to source and leaving all others to the default resolution
40
+ */
41
+ export declare function symlinks({ conditions }: {
42
+ /**
43
+ * The export conditions mapping each subpath to its source file, as listed in `customConditions`; where a package
44
+ * matches more than one, the key order of its own `exports` decides which applies.
45
+ */
46
+ conditions: string | readonly string[];
47
+ }): Plugin;
48
+ //# sourceMappingURL=symlinks.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"symlinks.d.ts","sourceRoot":"","sources":["../../src/vite/symlinks.ts"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AAGnC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,wBAAgB,QAAQ,CAAC,EAExB,UAAU,EAEV,EAAE;IAEF;;;OAGG;IACH,UAAU,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CAAA;CAEtC,GAAG,MAAM,CAyET"}
@@ -0,0 +1,102 @@
1
+ /*
2
+ * Copyright © 2026 Metreeca srl
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+ import { lstatSync, readFileSync, realpathSync } from "node:fs";
17
+ import { createRequire } from "node:module";
18
+ import { dirname, isAbsolute, join, resolve, sep } from "node:path";
19
+ import { exports } from "resolve.exports";
20
+ /**
21
+ * Creates a plugin running tests against the live source of locally linked packages, with no rebuild.
22
+ *
23
+ * Imports of workspace members and of sibling checkouts linked into `node_modules` resolve to their TypeScript source,
24
+ * so edits are picked up as they are saved. Packages installed from the registry keep resolving to their build.
25
+ *
26
+ * Packages are looked up as Node does, from the `node_modules` nearest to the importer up through its ancestors, so
27
+ * links hoisted to a workspace root are found from any member; imports with no importer start from the Vite root.
28
+ *
29
+ * The plugin replaces `resolve.conditions`, which breaks registry packages whose exports name a condition without
30
+ * publishing its target: Vite does not fall back to `default` when that target is missing from the installed files.
31
+ *
32
+ * @example
33
+ *
34
+ * Linked packages map each exported subpath to its source under a custom condition, next to the build:
35
+ *
36
+ * ```json
37
+ * {
38
+ * "exports": {
39
+ * ".": { "source": "./src/index.ts", "default": "./dist/index.js" }
40
+ * }
41
+ * }
42
+ * ```
43
+ *
44
+ * The consuming project registers the plugin with the same condition in its Vite or Vitest configuration:
45
+ *
46
+ * ```typescript
47
+ * import { symlinks } from "@metreeca/rack/vite";
48
+ * import { defineConfig } from "vitest/config";
49
+ *
50
+ * export default defineConfig({
51
+ * plugins: [symlinks({ conditions: "source" })]
52
+ * });
53
+ * ```
54
+ *
55
+ * @param options The resolution settings
56
+ *
57
+ * @returns A plugin resolving linked imports to source and leaving all others to the default resolution
58
+ */
59
+ export function symlinks({ conditions }) {
60
+ const $conditions = typeof conditions === "string" ? [conditions] : conditions;
61
+ const manifests = new Map(); // memoised by link real path
62
+ return {
63
+ name: "symlinks",
64
+ enforce: "pre",
65
+ resolveId(id, importer) {
66
+ const name = id.match(/^(@[^/]+\/)?[^/]+/)?.[0];
67
+ const base = importer && isAbsolute(importer) ? dirname(importer) : this.environment.config.root;
68
+ const link = name && lookup(name, base);
69
+ if (link) {
70
+ try {
71
+ const path = realpathSync(link);
72
+ const target = exports(manifest(path), id, { conditions: $conditions })?.[0];
73
+ return target === undefined ? null : resolve(path, target);
74
+ }
75
+ catch { // subpath not exported or excluded by a null target
76
+ return null;
77
+ }
78
+ }
79
+ else {
80
+ return null;
81
+ }
82
+ }
83
+ };
84
+ function lookup(name, directory) {
85
+ const path = (createRequire(join(directory, sep)).resolve.paths(name) ?? [])
86
+ .map(modules => join(modules, name))
87
+ .find(candidate => lstatSync(candidate, { throwIfNoEntry: false }));
88
+ return path !== undefined && lstatSync(path).isSymbolicLink() ? path : undefined;
89
+ }
90
+ function manifest(path) {
91
+ const cached = manifests.get(path);
92
+ if (cached === undefined) {
93
+ const loaded = JSON.parse(readFileSync(join(path, "package.json"), "utf8"));
94
+ manifests.set(path, loaded);
95
+ return loaded;
96
+ }
97
+ else {
98
+ return cached;
99
+ }
100
+ }
101
+ }
102
+ //# sourceMappingURL=symlinks.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"symlinks.js","sourceRoot":"","sources":["../../src/vite/symlinks.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@metreeca/rack",
3
- "version": "0.1.0",
4
- "description": "Shared development tooling for @metreeca packages.",
3
+ "version": "0.3.0",
4
+ "description": "Everyday tooling for npm package development.",
5
5
  "keywords": [
6
6
  "typescript",
7
7
  "tooling",
@@ -25,8 +25,13 @@
25
25
  "type": "module",
26
26
  "sideEffects": false,
27
27
  "exports": {
28
- ".": "./dist/index.js",
29
- "./vite": "./dist/vite/index.js"
28
+ "./vite": "./dist/vite/index.js",
29
+ "./tool": "./dist/tool/index.js"
30
+ },
31
+ "bin": {
32
+ "cp": "./dist/tool/cp.js",
33
+ "ln": "./dist/tool/ln.js",
34
+ "rm": "./dist/tool/rm.js"
30
35
  },
31
36
  "files": [
32
37
  "README.md",
@@ -44,7 +49,11 @@
44
49
  "check": "npx vitest --typecheck run",
45
50
  "proof": "npx typedoc && (npx typedoc --watch & npx wds --root-dir docs --watch --open)"
46
51
  },
52
+ "dependencies": {
53
+ "resolve.exports": "^2.0.4"
54
+ },
47
55
  "devDependencies": {
56
+ "@types/node": "^22.20.5",
48
57
  "@web/dev-server": "^1.0.0",
49
58
  "typedoc": "^0.28.14",
50
59
  "typescript": "^5.9.3",
@@ -54,6 +63,11 @@
54
63
  "peerDependencies": {
55
64
  "vite": "^8.0.0"
56
65
  },
66
+ "peerDependenciesMeta": {
67
+ "vite": {
68
+ "optional": true
69
+ }
70
+ },
57
71
  "allowScripts": {
58
72
  "fsevents": false
59
73
  }
package/dist/index.d.ts DELETED
@@ -1,9 +0,0 @@
1
- /**
2
- * Shared development tooling for `@metreeca` packages.
3
- *
4
- * Tools are grouped by the toolchain they extend and imported from its subpath, such as `@metreeca/rack/vite`.
5
- *
6
- * @module index
7
- */
8
- export {};
9
- //# sourceMappingURL=index.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAgBA;;;;;;GAMG;AAEH,OAAO,EAAE,CAAC"}
package/dist/index.js DELETED
@@ -1,17 +0,0 @@
1
- /*
2
- * Copyright © 2025-2026 Metreeca srl
3
- *
4
- * Licensed under the Apache License, Version 2.0 (the "License");
5
- * you may not use this file except in compliance with the License.
6
- * You may obtain a copy of the License at
7
- *
8
- * http://www.apache.org/licenses/LICENSE-2.0
9
- *
10
- * Unless required by applicable law or agreed to in writing, software
11
- * distributed under the License is distributed on an "AS IS" BASIS,
12
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
- * See the License for the specific language governing permissions and
14
- * limitations under the License.
15
- */
16
- export {};
17
- //# sourceMappingURL=index.js.map
package/dist/index.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG"}