@metreeca/rack 0.0.0-stage → 0.2.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/LICENSE ADDED
@@ -0,0 +1,201 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or
95
+ Derivative Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding those notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+ 6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for reasonable and customary use in describing the
141
+ origin of the Work and reproducing the content of the NOTICE file.
142
+
143
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+ 8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+ 9. Accepting Warranty or Additional Liability. While redistributing
166
+ the Work or Derivative Works thereof, You may choose to offer,
167
+ and charge a fee for, acceptance of support, warranty, indemnity,
168
+ or other liability obligations and/or rights consistent with this
169
+ License. However, in accepting such obligations, You may act only
170
+ on Your own behalf and on Your sole responsibility, not on behalf
171
+ of any other Contributor, and only if You agree to indemnify,
172
+ defend, and hold each Contributor harmless for any liability
173
+ incurred by, or claims asserted against, such Contributor by reason
174
+ of your accepting any such warranty or additional liability.
175
+
176
+ END OF TERMS AND CONDITIONS
177
+
178
+ APPENDIX: How to apply the Apache License to your work.
179
+
180
+ To apply the Apache License to your work, attach the following
181
+ boilerplate notice, with the fields enclosed by brackets "[]"
182
+ replaced with your own identifying information. (Don't include
183
+ the brackets!) The text should be enclosed in the appropriate
184
+ comment syntax for the file format. We also recommend that a
185
+ file or class name and description of purpose be included on the
186
+ same "printed page" as the copyright notice for easier
187
+ identification within third-party archives.
188
+
189
+ Copyright [yyyy] [name of copyright owner]
190
+
191
+ Licensed under the Apache License, Version 2.0 (the "License");
192
+ you may not use this file except in compliance with the License.
193
+ You may obtain a copy of the License at
194
+
195
+ http://www.apache.org/licenses/LICENSE-2.0
196
+
197
+ Unless required by applicable law or agreed to in writing, software
198
+ distributed under the License is distributed on an "AS IS" BASIS,
199
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
+ See the License for the specific language governing permissions and
201
+ limitations under the License.
package/README.md CHANGED
@@ -1,3 +1,38 @@
1
- # Temporary Holding Version
1
+ # @metreeca/rack
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ [![npm](https://img.shields.io/npm/v/@metreeca/rack)](https://www.npmjs.com/package/@metreeca/rack)
4
+
5
+ Everyday tooling for npm package development.
6
+
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
+
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
18
+
19
+ # Installation
20
+
21
+ ```shell
22
+ npm install --save-dev @metreeca/rack
23
+ ```
24
+
25
+ > [!WARNING]
26
+ >
27
+ > TypeScript consumers must use `"moduleResolution": "nodenext"/"node16"/"bundler"` in `tsconfig.json`.
28
+ > The legacy `"node"` resolver is not supported.
29
+
30
+ # Support
31
+
32
+ - open an [issue](https://github.com/metreeca/rack/issues) to report a problem or to suggest a new feature
33
+ - start a [discussion](https://github.com/metreeca/rack/discussions) to ask a how-to question or to share an idea
34
+
35
+ # License
36
+
37
+ This project is licensed under the Apache 2.0 License –
38
+ see [LICENSE](https://github.com/metreeca/rack?tab=Apache-2.0-1-ov-file) file for details.
@@ -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,19 @@
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
+ * | Tool | Purpose |
11
+ * |----------------------------|-----------------------------------------------------------------------------|
12
+ * | `cp <target> <pattern>...` | Copies files matching glob patterns into a directory, keeping their layout |
13
+ * | `ln [<deps>]...` | Links dependencies to their local checkouts instead of registry releases |
14
+ * | `rm <pattern>...` | Removes files and directories matching glob patterns, with their content |
15
+ *
16
+ * @module
17
+ */
18
+ export {};
19
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/tool/index.ts"],"names":[],"mappings":"AAgBA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAE,CAAC"}
@@ -0,0 +1,17 @@
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
+ export {};
17
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/tool/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG"}
@@ -0,0 +1,47 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Links the local checkouts of the dependencies of the current package 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 current package 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"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Vite plugins for building and testing npm packages.
3
+ *
4
+ * | Plugin | Purpose |
5
+ * |------------------|---------------------------------------------------------------------------------|
6
+ * | {@link symlinks} | Runs tests against the live source of locally linked packages, with no rebuild |
7
+ *
8
+ * @module
9
+ */
10
+ export { symlinks } from "./symlinks.js";
11
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
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"}
@@ -0,0 +1,26 @@
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
+ * 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";
26
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
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,6 +1,69 @@
1
1
  {
2
2
  "name": "@metreeca/rack",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.2.0",
4
+ "description": "Everyday tooling for npm package development.",
5
+ "keywords": [
6
+ "typescript",
7
+ "tooling",
8
+ "vite"
9
+ ],
10
+ "homepage": "https://metreeca.github.io/rack/",
11
+ "bugs": {
12
+ "url": "https://github.com/metreeca/rack/issues",
13
+ "email": "info@metreeca.com"
14
+ },
15
+ "license": "Apache-2.0",
16
+ "author": {
17
+ "name": "Metreeca",
18
+ "email": "info@metreeca.com",
19
+ "url": "https://www.metreeca.com/"
20
+ },
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "git+https://github.com/metreeca/rack.git"
24
+ },
25
+ "type": "module",
26
+ "sideEffects": false,
27
+ "exports": {
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"
35
+ },
36
+ "files": [
37
+ "README.md",
38
+ "LICENSE",
39
+ "dist"
40
+ ],
41
+ "engines": {
42
+ "node": ">=22.22.0"
43
+ },
44
+ "scripts": {
45
+ "clean": "rm -rf dist docs node_modules",
46
+ "prime": "npm ci",
47
+ "setup": "npm ci",
48
+ "build": "rm -rf dist docs && tsc -p tsconfig.build.json && npx typedoc",
49
+ "check": "npx vitest --typecheck run",
50
+ "proof": "npx typedoc && (npx typedoc --watch & npx wds --root-dir docs --watch --open)"
51
+ },
52
+ "dependencies": {
53
+ "resolve.exports": "^2.0.4"
54
+ },
55
+ "devDependencies": {
56
+ "@types/node": "^22.20.5",
57
+ "@web/dev-server": "^1.0.0",
58
+ "typedoc": "^0.28.14",
59
+ "typescript": "^5.9.3",
60
+ "vite": "^8.3.0",
61
+ "vitest": "^4.0.13"
62
+ },
63
+ "peerDependencies": {
64
+ "vite": "^8.0.0"
65
+ },
66
+ "allowScripts": {
67
+ "fsevents": false
68
+ }
69
+ }