compose-paths 1.2.2 → 1.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/CHANGELOG.md CHANGED
@@ -2,16 +2,62 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
- The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
- and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
5
+ The format is based on
6
+ [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this
7
+ project adheres to
8
+ [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
9
 
8
10
  ## [Unreleased]
9
11
 
12
+ ## [1.3.0] - 2026-07-19
13
+
14
+ ### Added
15
+
16
+ - TypeScript: infer alias property names from static path-chart string
17
+ literals and `as const` line arrays (e.g. `paths.TEMPLATES`
18
+ type-checks; unknown aliases error)
19
+
20
+ - TypeScript: declare `zip`, default export, `TPathChart`,
21
+ `TZipOptions`, and related helper types in `index.d.ts`
22
+
23
+ - Tests: broader API coverage (zip modes, array input, comments, CRLF,
24
+ URL schemes); `test:types` and `test:coverage` scripts
25
+
26
+ ### Fixed
27
+
28
+ - `//` comment stripping no longer treats the `//` in URL schemes (e.g.
29
+ `https://example.com`) as a comment
30
+
31
+ - Full-line `//` comments are dropped cleanly (no leftover empty
32
+ segments / `.` path pieces)
33
+
34
+ ### Changed
35
+
36
+ - `package.json` `exports` now include a `types` condition; `main`
37
+ points at `dist/cjs/…`
38
+
39
+ - Document Node.js `>=20` as the supported engine (dev tooling and
40
+ `node:test`)
41
+
42
+ - Tooling: native `node:test` instead of Jest; ESLint 10 flat config;
43
+ dependency upgrades (`url-join` 5, TypeScript 7 for type tests, etc.)
44
+
45
+ - README: note TypeScript alias inference for static charts
46
+
47
+ ## [1.2.3] - 2023-06-06
48
+
49
+ ### Updated
50
+
51
+ - README tweaks
52
+ - Update build dependencies
53
+
10
54
  ## [1.2.2] - 2022-04-15
11
55
 
12
56
  ### Removed / Fixed
13
57
 
14
- - Remove `browser` setting from `package.json`; seems to cause issues with CodeSandbox build process, and isn't really necessary since the browser-build is something that's interacted with manually.
58
+ - Remove `browser` setting from `package.json`; seems to cause issues
59
+ with CodeSandbox build process, and isn't really necessary since the
60
+ browser-build is something that's interacted with manually.
15
61
 
16
62
  ## [1.2.1] - 2022-02-07
17
63
 
@@ -47,7 +93,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
47
93
 
48
94
  ### Fixed
49
95
 
50
- - Fix ESM build (import { ... } from 'compose-paths' was not working right)
96
+ - Fix ESM build (import { ... } from 'compose-paths' was not working
97
+ right)
51
98
 
52
99
  ## [1.0.8] - 2021-06-04
53
100
 
package/README.md CHANGED
@@ -18,15 +18,7 @@
18
18
  /></a>
19
19
  </p>
20
20
 
21
- ```sh
22
- $ pnpm i compose-paths
23
- ```
24
-
25
- `compose-paths` looks at the indentation-level of its input as the cue to concatenate lines together. Either `tabs` or `spaces` should be fine, so long as you're consistent.
26
-
27
- ## Usage
28
-
29
- ### `Path` &rarr; `Alias`
21
+ Quickly throw together a few local paths and have them assigned to aliases or routes.
30
22
 
31
23
  ```js
32
24
  const { composePaths } = require('compose-paths')
@@ -54,7 +46,18 @@ paths.aliases
54
46
  // ["TEMPLATES", "PAGES", "PUBLIC", "IMAGES"]
55
47
  ```
56
48
 
57
- ### `Path` &rarr; `Route` (via aliases)
49
+ `compose-paths` looks at the indentation-level of its input as the cue to
50
+ concatenate lines together. Either `tabs` or `spaces` should be fine, so long
51
+ as you're consistent.
52
+
53
+ **TypeScript:** when the path chart is a string literal or template (or an `as
54
+ const` array of lines), alias names after `=` are inferred on the result — so
55
+ `paths.TEMPLATES` type-checks and unknown aliases are errors. Dynamic `string`
56
+ inputs stay loosely typed.
57
+
58
+ ## More examples
59
+
60
+ ### `Path` &rarr; `Route` (via aliases and `zip`)
58
61
 
59
62
  ```js
60
63
  const { composePaths, zip } = require('compose-paths')
@@ -76,7 +79,6 @@ const paths = composePaths(`
76
79
 
77
80
  const staticRoutes = zip(routes, paths)
78
81
 
79
- // Then, for example:
80
82
  staticRoutes.forEach(([route, path]) => {
81
83
  app.get(route, sendFile(path))
82
84
  })
@@ -1,7 +1,7 @@
1
1
  /*
2
2
  * compose-paths
3
- * v1.2.2
3
+ * v1.3.0
4
4
  * https://github.com/shuckster/compose-paths
5
5
  * License: MIT
6
6
  */
7
- var composePaths=(()=>{var a=(n,e)=>()=>(e||n((e={exports:{}}).exports,e),e.exports);var h=a((p,u)=>{(function(n,e,t){typeof u!="undefined"&&u.exports?u.exports=t():typeof define=="function"&&define.amd?define(t):e[n]=t()})("urljoin",p,function(){function n(e){var t=[];if(e.length===0)return"";if(typeof e[0]!="string")throw new TypeError("Url must be a string. Received "+e[0]);if(e[0].match(/^[^/:]+:\/*$/)&&e.length>1){var i=e.shift();e[0]=i+e[0]}e[0].match(/^file:\/\/\//)?e[0]=e[0].replace(/^([^/:]+):\/*/,"$1:///"):e[0]=e[0].replace(/^([^/:]+):\/*/,"$1://");for(var o=0;o<e.length;o++){var s=e[o];if(typeof s!="string")throw new TypeError("Url must be a string. Received "+s);s!==""&&(o>0&&(s=s.replace(/^[\/]+/,"")),o<e.length-1?s=s.replace(/[\/]+$/,""):s=s.replace(/[\/]+$/,"/"),t.push(s))}var l=t.join("/");l=l.replace(/\/(\?|&|#[^!])/g,"$1");var c=l.split("?");return l=c.shift()+(c.length>0?"?":"")+c.join("&"),l}return function(){var e;return typeof arguments[0]=="object"?e=arguments[0]:e=[].slice.call(arguments),n(e)}})});var B=a((N,m)=>{m.exports={composePaths:v,zip:I};var g=h(),d="aliases";function v(n){let t=O(n).map(L),i=t.reduce(E,1/0),o=t.map(R(i)),s=_(o),l=x(s),c=s.map(r=>r==null?void 0:r.name).filter(Boolean),f=l;return s.forEach(w(f)),Object.defineProperty(f,d,{value:c,enumerable:!1})}function I(n,e,t){let{aliases:i=[],ignoreAliases:o=!1}=t||{};return(i.length&&!o?i:n[d]).reduce((l,c)=>(o&&i.includes(c)||l.push([n[c],e[c]]),l),[])}var j=/[\r\n]/,P=/(\s*\/\/[^\n\r]*)/,S=/^(\s*)([^$]*)/,$=/\s*=\s*([^$]+)/,b=/^\s*$/;function E(n,{indent:e}){return Math.min(n,e)}function R(n){return({indent:e,content:t})=>({indent:e-n,content:t})}function w(n){return({name:e,index:t})=>{if(!e)return n;let i=n[t];Object.defineProperty(n,e,{value:i,enumerable:!1})}}function x(n){let e=[],t=[],i=-1,o=-1;return n.forEach(({indent:s,content:l})=>{if(o<=0)o=s;else if(s<=i){let c=1+(i-s)/o;for(;c--;)e.pop()}e.push(l),t.push(g(...e)),i=s}),t}function _(n){return n.map((e,t)=>{let{content:i,indent:o}=e,s=i.match($);return s?{index:t,indent:o,content:i.slice(0,i.length-s[0].length),name:s[1]}:{index:t,indent:o,content:i}})}function L(n){var i;let e=[n,"",n],t=(i=n.match(S))!=null?i:e;return{indent:t[1].length,content:t[2]}}function O(n){return[n].flat().reduce((e,t)=>[...e,...t.split(j)],[]).filter(z).map(e=>e.replace(P,""))}function z(n){return!b.test(n)}});return B();})();
7
+ var composePaths=(()=>{var r=Object.defineProperty;var m=Object.getOwnPropertyDescriptor;var v=Object.getOwnPropertyNames;var I=Object.prototype.hasOwnProperty;var j=(e,t,n)=>()=>{if(n)throw n[0];try{return e&&(t=e(e=0)),t}catch(s){throw n=[s],s}};var P=(e,t)=>()=>{try{return t||e((t={exports:{}}).exports,t),t.exports}catch(n){throw t=0,n}},S=(e,t)=>{for(var n in t)r(e,n,{get:t[n],enumerable:!0})},$=(e,t,n,s)=>{if(t&&typeof t=="object"||typeof t=="function")for(let i of v(t))!I.call(e,i)&&i!==n&&r(e,i,{get:()=>t[i],enumerable:!(s=m(t,i))||s.enumerable});return e};var b=e=>$(r({},"__esModule",{value:!0}),e);var p={};S(p,{default:()=>f});function E(e){var t=[];if(e.length===0)return"";if(typeof e[0]!="string")throw new TypeError("Url must be a string. Received "+e[0]);if(e[0].match(/^[^/:]+:\/*$/)&&e.length>1){var n=e.shift();e[0]=n+e[0]}e[0].match(/^file:\/\/\//)?e[0]=e[0].replace(/^([^/:]+):\/*/,"$1:///"):e[0]=e[0].replace(/^([^/:]+):\/*/,"$1://");for(var s=0;s<e.length;s++){var i=e[s];if(typeof i!="string")throw new TypeError("Url must be a string. Received "+i);i!==""&&(s>0&&(i=i.replace(/^[\/]+/,"")),s<e.length-1?i=i.replace(/[\/]+$/,""):i=i.replace(/[\/]+$/,"/"),t.push(i))}var l=t.join("/");l=l.replace(/\/(\?|&|#[^!])/g,"$1");var o=l.split("?");return l=o.shift()+(o.length>0?"?":"")+o.join("&"),l}function f(){var e;return typeof arguments[0]=="object"?e=arguments[0]:e=[].slice.call(arguments),E(e)}var h=j(()=>{});var D=P((G,g)=>{g.exports={composePaths:w,zip:x};var R=(h(),b(p)).default,d="aliases";function w(e){let n=W(e).map(F),s=n.reduce(C,1/0),i=n.map(J(s)),l=U(i),o=T(l),c=l.map(u=>u==null?void 0:u.name).filter(Boolean),a=o;return l.forEach(N(a)),Object.defineProperty(a,d,{value:c,enumerable:!1})}function x(e,t,n){let{aliases:s=[],ignoreAliases:i=!1}=n||{};return(s.length&&!i?s:e[d]).reduce((o,c)=>(i&&s.includes(c)||o.push([e[c],t[c]]),o),[])}var _=/[\r\n]/,L=/(^\s*\/\/[^\n\r]*|\s+\/\/[^\n\r]*)/,O=/^(\s*)([^$]*)/,z=/\s*=\s*([^$]+)/,B=/^\s*$/;function C(e,{indent:t}){return Math.min(e,t)}function J(e){return({indent:t,content:n})=>({indent:t-e,content:n})}function N(e){return({name:t,index:n})=>{if(!t)return e;let s=e[n];Object.defineProperty(e,t,{value:s,enumerable:!1})}}function T(e){let t=[],n=[],s=-1,i=-1;return e.forEach(({indent:l,content:o})=>{if(i<=0)i=l;else if(l<=s){let c=1+(s-l)/i;for(;c--;)t.pop()}t.push(o),n.push(R(...t)),s=l}),n}function U(e){return e.map((t,n)=>{let{content:s,indent:i}=t,l=s.match(z);return l?{index:n,indent:i,content:s.slice(0,s.length-l[0].length),name:l[1]}:{index:n,indent:i,content:s}})}function F(e){var s;let t=[e,"",e],n=(s=e.match(O))!=null?s:t;return{indent:n[1].length,content:n[2]}}function W(e){return[e].flat().reduce((t,n)=>[...t,...n.split(_)],[]).map(t=>t.replace(L,"")).filter(q)}function q(e){return!B.test(e)}});return D();})();
@@ -1,7 +1,7 @@
1
1
  /*
2
2
  * compose-paths
3
- * v1.2.2
3
+ * v1.3.0
4
4
  * https://github.com/shuckster/compose-paths
5
5
  * License: MIT
6
6
  */
7
- module.exports={composePaths:f,zip:h};const p=require("path").join,a="aliases";function f(n){const e=x(n).map(v),s=e.reduce(S,1/0),i=e.map(g(s)),o=b(i),r=_(o),c=o.map(u=>u==null?void 0:u.name).filter(Boolean),l=r;return o.forEach(j(l)),Object.defineProperty(l,a,{value:c,enumerable:!1})}function h(n,t,e){const{aliases:s=[],ignoreAliases:i=!1}=e||{};return(s.length&&!i?s:n[a]).reduce((r,c)=>(i&&s.includes(c)||r.push([n[c],t[c]]),r),[])}const m=/[\r\n]/,d=/(\s*\/\/[^\n\r]*)/,I=/^(\s*)([^$]*)/,A=/\s*=\s*([^$]+)/,P=/^\s*$/;function S(n,{indent:t}){return Math.min(n,t)}function g(n){return({indent:t,content:e})=>({indent:t-n,content:e})}function j(n){return({name:t,index:e})=>{if(!t)return n;const s=n[e];Object.defineProperty(n,t,{value:s,enumerable:!1})}}function _(n){const t=[],e=[];let s=-1,i=-1;return n.forEach(({indent:o,content:r})=>{if(i<=0)i=o;else if(o<=s){let c=1+(s-o)/i;for(;c--;)t.pop()}t.push(r),e.push(p(...t)),s=o}),e}function b(n){return n.map((t,e)=>{const{content:s,indent:i}=t,o=s.match(A);return o?{index:e,indent:i,content:s.slice(0,s.length-o[0].length),name:o[1]}:{index:e,indent:i,content:s}})}function v(n){var s;const t=[n,"",n],e=(s=n.match(I))!=null?s:t;return{indent:e[1].length,content:e[2]}}function x(n){return[n].flat().reduce((t,e)=>[...t,...e.split(m)],[]).filter(E).map(t=>t.replace(d,""))}function E(n){return!P.test(n)}
7
+ module.exports={composePaths:p,zip:h};const f=require("path").join,a="aliases";function p(n){const e=x(n).map(v),s=e.reduce(S,1/0),i=e.map(g(s)),o=b(i),r=_(o),c=o.map(u=>u==null?void 0:u.name).filter(Boolean),l=r;return o.forEach(j(l)),Object.defineProperty(l,a,{value:c,enumerable:!1})}function h(n,t,e){const{aliases:s=[],ignoreAliases:i=!1}=e||{};return(s.length&&!i?s:n[a]).reduce((r,c)=>(i&&s.includes(c)||r.push([n[c],t[c]]),r),[])}const d=/[\r\n]/,m=/(^\s*\/\/[^\n\r]*|\s+\/\/[^\n\r]*)/,I=/^(\s*)([^$]*)/,A=/\s*=\s*([^$]+)/,P=/^\s*$/;function S(n,{indent:t}){return Math.min(n,t)}function g(n){return({indent:t,content:e})=>({indent:t-n,content:e})}function j(n){return({name:t,index:e})=>{if(!t)return n;const s=n[e];Object.defineProperty(n,t,{value:s,enumerable:!1})}}function _(n){const t=[],e=[];let s=-1,i=-1;return n.forEach(({indent:o,content:r})=>{if(i<=0)i=o;else if(o<=s){let c=1+(s-o)/i;for(;c--;)t.pop()}t.push(r),e.push(f(...t)),s=o}),e}function b(n){return n.map((t,e)=>{const{content:s,indent:i}=t,o=s.match(A);return o?{index:e,indent:i,content:s.slice(0,s.length-o[0].length),name:o[1]}:{index:e,indent:i,content:s}})}function v(n){var s;const t=[n,"",n],e=(s=n.match(I))!=null?s:t;return{indent:e[1].length,content:e[2]}}function x(n){return[n].flat().reduce((t,e)=>[...t,...e.split(d)],[]).map(t=>t.replace(m,"")).filter(E)}function E(n){return!P.test(n)}
@@ -1,7 +1,7 @@
1
1
  /*
2
2
  * compose-paths
3
- * v1.2.2
3
+ * v1.3.0
4
4
  * https://github.com/shuckster/compose-paths
5
5
  * License: MIT
6
6
  */
7
- var a=(n,e)=>()=>(e||n((e={exports:{}}).exports,e),e.exports);var h=a((p,u)=>{(function(n,e,t){typeof u!="undefined"&&u.exports?u.exports=t():typeof define=="function"&&define.amd?define(t):e[n]=t()})("urljoin",p,function(){function n(e){var t=[];if(e.length===0)return"";if(typeof e[0]!="string")throw new TypeError("Url must be a string. Received "+e[0]);if(e[0].match(/^[^/:]+:\/*$/)&&e.length>1){var i=e.shift();e[0]=i+e[0]}e[0].match(/^file:\/\/\//)?e[0]=e[0].replace(/^([^/:]+):\/*/,"$1:///"):e[0]=e[0].replace(/^([^/:]+):\/*/,"$1://");for(var o=0;o<e.length;o++){var s=e[o];if(typeof s!="string")throw new TypeError("Url must be a string. Received "+s);s!==""&&(o>0&&(s=s.replace(/^[\/]+/,"")),o<e.length-1?s=s.replace(/[\/]+$/,""):s=s.replace(/[\/]+$/,"/"),t.push(s))}var l=t.join("/");l=l.replace(/\/(\?|&|#[^!])/g,"$1");var c=l.split("?");return l=c.shift()+(c.length>0?"?":"")+c.join("&"),l}return function(){var e;return typeof arguments[0]=="object"?e=arguments[0]:e=[].slice.call(arguments),n(e)}})});var B=a((N,m)=>{m.exports={composePaths:v,zip:I};var g=h(),d="aliases";function v(n){let t=O(n).map(L),i=t.reduce(E,1/0),o=t.map(R(i)),s=_(o),l=x(s),c=s.map(r=>r==null?void 0:r.name).filter(Boolean),f=l;return s.forEach(w(f)),Object.defineProperty(f,d,{value:c,enumerable:!1})}function I(n,e,t){let{aliases:i=[],ignoreAliases:o=!1}=t||{};return(i.length&&!o?i:n[d]).reduce((l,c)=>(o&&i.includes(c)||l.push([n[c],e[c]]),l),[])}var j=/[\r\n]/,P=/(\s*\/\/[^\n\r]*)/,S=/^(\s*)([^$]*)/,$=/\s*=\s*([^$]+)/,b=/^\s*$/;function E(n,{indent:e}){return Math.min(n,e)}function R(n){return({indent:e,content:t})=>({indent:e-n,content:t})}function w(n){return({name:e,index:t})=>{if(!e)return n;let i=n[t];Object.defineProperty(n,e,{value:i,enumerable:!1})}}function x(n){let e=[],t=[],i=-1,o=-1;return n.forEach(({indent:s,content:l})=>{if(o<=0)o=s;else if(s<=i){let c=1+(i-s)/o;for(;c--;)e.pop()}e.push(l),t.push(g(...e)),i=s}),t}function _(n){return n.map((e,t)=>{let{content:i,indent:o}=e,s=i.match($);return s?{index:t,indent:o,content:i.slice(0,i.length-s[0].length),name:s[1]}:{index:t,indent:o,content:i}})}function L(n){var i;let e=[n,"",n],t=(i=n.match(S))!=null?i:e;return{indent:t[1].length,content:t[2]}}function O(n){return[n].flat().reduce((e,t)=>[...e,...t.split(j)],[]).filter(z).map(e=>e.replace(P,""))}function z(n){return!b.test(n)}});export default B();
7
+ var r=Object.defineProperty;var m=Object.getOwnPropertyDescriptor;var v=Object.getOwnPropertyNames;var I=Object.prototype.hasOwnProperty;var j=(e,t,n)=>()=>{if(n)throw n[0];try{return e&&(t=e(e=0)),t}catch(s){throw n=[s],s}};var P=(e,t)=>()=>{try{return t||e((t={exports:{}}).exports,t),t.exports}catch(n){throw t=0,n}},S=(e,t)=>{for(var n in t)r(e,n,{get:t[n],enumerable:!0})},$=(e,t,n,s)=>{if(t&&typeof t=="object"||typeof t=="function")for(let i of v(t))!I.call(e,i)&&i!==n&&r(e,i,{get:()=>t[i],enumerable:!(s=m(t,i))||s.enumerable});return e};var b=e=>$(r({},"__esModule",{value:!0}),e);var p={};S(p,{default:()=>f});function E(e){var t=[];if(e.length===0)return"";if(typeof e[0]!="string")throw new TypeError("Url must be a string. Received "+e[0]);if(e[0].match(/^[^/:]+:\/*$/)&&e.length>1){var n=e.shift();e[0]=n+e[0]}e[0].match(/^file:\/\/\//)?e[0]=e[0].replace(/^([^/:]+):\/*/,"$1:///"):e[0]=e[0].replace(/^([^/:]+):\/*/,"$1://");for(var s=0;s<e.length;s++){var i=e[s];if(typeof i!="string")throw new TypeError("Url must be a string. Received "+i);i!==""&&(s>0&&(i=i.replace(/^[\/]+/,"")),s<e.length-1?i=i.replace(/[\/]+$/,""):i=i.replace(/[\/]+$/,"/"),t.push(i))}var l=t.join("/");l=l.replace(/\/(\?|&|#[^!])/g,"$1");var o=l.split("?");return l=o.shift()+(o.length>0?"?":"")+o.join("&"),l}function f(){var e;return typeof arguments[0]=="object"?e=arguments[0]:e=[].slice.call(arguments),E(e)}var h=j(()=>{});var D=P((G,g)=>{g.exports={composePaths:w,zip:x};var R=(h(),b(p)).default,d="aliases";function w(e){let n=W(e).map(F),s=n.reduce(C,1/0),i=n.map(J(s)),l=U(i),o=T(l),c=l.map(u=>u==null?void 0:u.name).filter(Boolean),a=o;return l.forEach(N(a)),Object.defineProperty(a,d,{value:c,enumerable:!1})}function x(e,t,n){let{aliases:s=[],ignoreAliases:i=!1}=n||{};return(s.length&&!i?s:e[d]).reduce((o,c)=>(i&&s.includes(c)||o.push([e[c],t[c]]),o),[])}var _=/[\r\n]/,L=/(^\s*\/\/[^\n\r]*|\s+\/\/[^\n\r]*)/,O=/^(\s*)([^$]*)/,z=/\s*=\s*([^$]+)/,B=/^\s*$/;function C(e,{indent:t}){return Math.min(e,t)}function J(e){return({indent:t,content:n})=>({indent:t-e,content:n})}function N(e){return({name:t,index:n})=>{if(!t)return e;let s=e[n];Object.defineProperty(e,t,{value:s,enumerable:!1})}}function T(e){let t=[],n=[],s=-1,i=-1;return e.forEach(({indent:l,content:o})=>{if(i<=0)i=l;else if(l<=s){let c=1+(s-l)/i;for(;c--;)t.pop()}t.push(o),n.push(R(...t)),s=l}),n}function U(e){return e.map((t,n)=>{let{content:s,indent:i}=t,l=s.match(z);return l?{index:n,indent:i,content:s.slice(0,s.length-l[0].length),name:l[1]}:{index:n,indent:i,content:s}})}function F(e){var s;let t=[e,"",e],n=(s=e.match(O))!=null?s:t;return{indent:n[1].length,content:n[2]}}function W(e){return[e].flat().reduce((t,n)=>[...t,...n.split(_)],[]).map(t=>t.replace(L,"")).filter(q)}function q(e){return!B.test(e)}});export default D();
package/index.d.ts CHANGED
@@ -1,32 +1,205 @@
1
- type TComposedPaths = Array<string> & {
2
- aliases: Array<string>
3
- [key: string]: string
1
+ // ---------------------------------------------------------------------------
2
+ // Public path-chart types
3
+ // ---------------------------------------------------------------------------
4
+
5
+ export type TPathChart = string | readonly string[]
6
+
7
+ /**
8
+ * Every composed path string, in chart order.
9
+ *
10
+ * Also has non-enumerable `.aliases` and one non-enumerable property per alias
11
+ * (e.g. `paths.TEMPLATES`). With a static chart string, those alias keys are
12
+ * inferred by TypeScript (see `composePaths` overloads).
13
+ */
14
+ export interface TComposedPaths extends Array<string> {
15
+ /** Alias names in declaration order (non-enumerable at runtime). */
16
+ readonly aliases: readonly string[]
4
17
  }
5
18
 
6
- declare module 'compose-paths' {
19
+ export interface TZipOptions {
20
+ /**
21
+ * Alias names to include. If `ignoreAliases` is true, these are excluded
22
+ * instead and every other alias on `keys` is kept.
23
+ */
24
+ aliases?: readonly string[]
7
25
  /**
8
- * @example
9
- * const paths = composePaths(`
10
- *
11
- * ${__dirname}
12
- * /src
13
- * /html
14
- * /templates = TEMPLATES
15
- * /pages = PAGES
16
- *
17
- * /public = PUBLIC
18
- * /images = IMAGES
19
- *
20
- * `)
21
- *
22
- * paths.TEMPLATES
23
- * // "/dir/name/src/html/templates"
24
- *
25
- * paths.PUBLIC
26
- * // "/dir/name/public"
27
- *
28
- * paths.aliases
29
- * // ["TEMPLATES", "PAGES", "PUBLIC", "IMAGES"]
26
+ * When true, `aliases` is treated as an exclusion list.
27
+ * @default false
30
28
  */
31
- export function composePaths(pathChart: string | string[]): TComposedPaths
29
+ ignoreAliases?: boolean
30
+ }
31
+
32
+ // ---------------------------------------------------------------------------
33
+ // Type-level chart DSL (alias-name extraction only)
34
+ // ---------------------------------------------------------------------------
35
+
36
+ type TTrimLeft<S extends string> = S extends ` ${infer R}`
37
+ ? TTrimLeft<R>
38
+ : S extends `\t${infer R}`
39
+ ? TTrimLeft<R>
40
+ : S
41
+
42
+ type TTrimRight<S extends string> = S extends `${infer R} `
43
+ ? TTrimRight<R>
44
+ : S extends `${infer R}\t`
45
+ ? TTrimRight<R>
46
+ : S
47
+
48
+ type TTrim<S extends string> = TTrimLeft<TTrimRight<S>>
49
+
50
+ /** Normalize CRLF so line splitting is `\n`-only. */
51
+ type TNormalizeNewlines<S extends string> = S extends `${infer A}\r\n${infer B}`
52
+ ? `${A}\n${TNormalizeNewlines<B>}`
53
+ : S
54
+
55
+ /**
56
+ * Strip `//` comments the same way as runtime: at line start (optional indent)
57
+ * or after whitespace — not the `//` inside `https://…`.
58
+ */
59
+ type TStripLineComment<S extends string> = TTrimLeft<S> extends `//${string}`
60
+ ? ''
61
+ : S extends `${infer Code} //${string}`
62
+ ? Code
63
+ : S extends `${infer Code}\t//${string}`
64
+ ? Code
65
+ : S
66
+
67
+ /**
68
+ * Split on newlines. Depth is unbounded in theory; real path charts stay small.
69
+ * Empty segments from trailing newlines become `never` aliases and drop out.
70
+ */
71
+ type TSplitLines<S extends string> = TNormalizeNewlines<S> extends `${infer Line}\n${infer Rest}`
72
+ ? [Line, ...TSplitLines<Rest>]
73
+ : [TNormalizeNewlines<S>]
74
+
75
+ /**
76
+ * Alias text after `=` on a chart line, or `never` if the line has no assignment.
77
+ * Distributes over unions of lines (`L extends unknown ? …`) so chart-wide
78
+ * `TSplitLines[number]` extracts each line’s alias correctly.
79
+ */
80
+ export type TAliasFromLine<L extends string> = L extends unknown
81
+ ? TStripLineComment<L> extends `${string}=${infer Alias}`
82
+ ? TTrim<TStripLineComment<Alias>> extends infer A extends string
83
+ ? A extends ''
84
+ ? never
85
+ : A
86
+ : never
87
+ : never
88
+ : never
89
+
90
+ /**
91
+ * Union of alias names in a static chart string.
92
+ * Dynamic `string` inputs fall back to `string`.
93
+ */
94
+ export type TAliasesFromChart<S extends string> = string extends S
95
+ ? string
96
+ : TAliasFromLine<TSplitLines<S>[number]>
97
+
98
+ /**
99
+ * Alias names from a `as const` array of chart lines.
100
+ */
101
+ export type TAliasesFromLines<T extends readonly string[]> = string extends T[number]
102
+ ? string
103
+ : TAliasFromLine<T[number]>
104
+
105
+ type TAliasPropsFromUnion<A extends string> = string extends A
106
+ ? { readonly [key: string]: string }
107
+ : { readonly [K in A]: string }
108
+
109
+ export type TAliasPropsFromChart<S extends string> = TAliasPropsFromUnion<
110
+ TAliasesFromChart<S>
111
+ >
112
+
113
+ export type TAliasPropsFromLines<T extends readonly string[]> = TAliasPropsFromUnion<
114
+ TAliasesFromLines<T>
115
+ >
116
+
117
+ export type TComposedPathsFromChart<S extends string> = TComposedPaths &
118
+ TAliasPropsFromChart<S> & {
119
+ readonly aliases: ReadonlyArray<TAliasesFromChart<S>>
120
+ }
121
+
122
+ export type TComposedPathsFromLines<T extends readonly string[]> = TComposedPaths &
123
+ TAliasPropsFromLines<T> & {
124
+ readonly aliases: ReadonlyArray<TAliasesFromLines<T>>
125
+ }
126
+
127
+ // ---------------------------------------------------------------------------
128
+ // composePaths
129
+ // ---------------------------------------------------------------------------
130
+
131
+ /**
132
+ * Compose indented path charts into a list of full paths + named aliases.
133
+ *
134
+ * When `pathChart` is a string literal / template, TypeScript infers alias
135
+ * property names from `= ALIAS` markers in the chart.
136
+ *
137
+ * @example
138
+ * const paths = composePaths(`
139
+ * ${__dirname}
140
+ * /src
141
+ * /html
142
+ * /templates = TEMPLATES
143
+ * /pages = PAGES
144
+ *
145
+ * /public = PUBLIC
146
+ * /images = IMAGES
147
+ * `)
148
+ *
149
+ * paths.TEMPLATES
150
+ * // "/dir/name/src/html/templates"
151
+ *
152
+ * paths.PUBLIC
153
+ * // "/dir/name/public"
154
+ *
155
+ * paths.aliases
156
+ * // ["TEMPLATES", "PAGES", "PUBLIC", "IMAGES"]
157
+ */
158
+ export function composePaths<const S extends string>(
159
+ pathChart: S
160
+ ): TComposedPathsFromChart<S>
161
+
162
+ /**
163
+ * Compose from an `as const` array of chart lines (alias keys inferred).
164
+ *
165
+ * @example
166
+ * composePaths(['/src', ' /x = X'] as const).X
167
+ */
168
+ export function composePaths<const T extends readonly string[]>(
169
+ pathChart: T
170
+ ): TComposedPathsFromLines<T>
171
+
172
+ /**
173
+ * Compose when the chart type is a wide `string` or `string[]` (no key inference).
174
+ */
175
+ export function composePaths(pathChart: TPathChart): TComposedPaths
176
+
177
+ // ---------------------------------------------------------------------------
178
+ // zip
179
+ // ---------------------------------------------------------------------------
180
+
181
+ /**
182
+ * Pair aliases from two composed charts (e.g. routes → filesystem paths).
183
+ *
184
+ * @returns `[keys[alias], values[alias]]` tuples in alias order
185
+ *
186
+ * @example
187
+ * zip(routes, paths)
188
+ * zip(routes, paths, { aliases: ['HOME', 'ABOUT'] })
189
+ * zip(routes, paths, { aliases: ['DEBUG'], ignoreAliases: true })
190
+ */
191
+ export function zip<K extends TComposedPaths, V extends TComposedPaths>(
192
+ keys: K,
193
+ values: V,
194
+ options?: TZipOptions
195
+ ): Array<[string, string]>
196
+
197
+ // ---------------------------------------------------------------------------
198
+ // Default export (CJS/ESM interop shape)
199
+ // ---------------------------------------------------------------------------
200
+
201
+ declare const _default: {
202
+ composePaths: typeof composePaths
203
+ zip: typeof zip
32
204
  }
205
+ export default _default
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "compose-paths",
3
3
  "browserGlobalName": "composePaths",
4
- "version": "1.2.2",
4
+ "version": "1.3.0",
5
5
  "description": "Define paths + routes without the repetition.",
6
6
  "author": "Conan Theobald",
7
7
  "license": "MIT",
@@ -12,13 +12,16 @@
12
12
  "routes",
13
13
  "concatenation"
14
14
  ],
15
- "types": "index.d.ts",
16
- "main": "./dist/compose-paths.cjs.js",
15
+ "types": "./index.d.ts",
16
+ "main": "./dist/cjs/compose-paths.cjs.js",
17
17
  "module": "index.mjs",
18
18
  "exports": {
19
- "import": "./index.mjs",
20
- "require": "./dist/cjs/compose-paths.cjs.js",
21
- "default": "./dist/cjs/compose-paths.cjs.js"
19
+ ".": {
20
+ "types": "./index.d.ts",
21
+ "import": "./index.mjs",
22
+ "require": "./dist/cjs/compose-paths.cjs.js",
23
+ "default": "./dist/cjs/compose-paths.cjs.js"
24
+ }
22
25
  },
23
26
  "files": [
24
27
  "dist/**/*.js",
@@ -30,7 +33,7 @@
30
33
  "LICENSE"
31
34
  ],
32
35
  "engines": {
33
- "node": ">=10"
36
+ "node": ">=20"
34
37
  },
35
38
  "homepage": "https://github.com/shuckster/compose-paths",
36
39
  "repository": {
@@ -42,24 +45,25 @@
42
45
  "email": "bugs+compose-paths@conans.co.uk"
43
46
  },
44
47
  "devDependencies": {
45
- "concurrently": "^7.1.0",
46
- "cross-env": "^7.0.3",
47
- "esbuild": "^0.14.36",
48
- "eslint": "^8.13.0",
49
- "eslint-plugin-jest": "^26.1.4",
50
- "jest": "^27.5.1",
51
- "match-iz": "^3.3.1",
52
- "rimraf": "^3.0.2",
53
- "typescript": "^4.6.3",
54
- "url-join": "^4.0.1"
48
+ "@eslint/js": "^10.0.1",
49
+ "concurrently": "^10.0.3",
50
+ "cross-env": "^10.1.0",
51
+ "esbuild": "^0.28.1",
52
+ "eslint": "^10.7.0",
53
+ "globals": "^17.7.0",
54
+ "match-iz": "^5.0.8",
55
+ "rimraf": "^6.1.3",
56
+ "typescript": "^7.0.2",
57
+ "url-join": "^5.0.0"
55
58
  },
56
59
  "scripts": {
57
- "test": "jest",
58
- "lint": "pnpx eslint src/*.js tests/*.js",
60
+ "test": "node --test tests/*.test.js",
61
+ "test:coverage": "node --test --experimental-test-coverage --test-coverage-include='src/**' tests/*.test.js",
62
+ "test:types": "tsc --strict --noEmit --module NodeNext --moduleResolution NodeNext tests/types-fixture.ts",
63
+ "lint": "eslint src/*.js tests/*.js build-config/*.js eslint.config.js",
59
64
  "build": "cross-env NODE_ENV=production pnpm run build:esbuild",
60
65
  "build:clean": "rimraf dist/; mkdir -p dist/{esm,cjs,browser}",
61
66
  "build:all": "pnpm run build:clean ; cross-env NODE_ENV=production pnpm run build",
62
67
  "build:esbuild": "node ./build-config/esbuild.js"
63
- },
64
- "readme": "<h1 align=\"center\"><code>compose-paths</code> 🛣</h1>\n\n<p align=\"center\">\n <a href=\"https://github.com/shuckster/compose-paths/blob/master/LICENSE\">\n <img\n alt=\"MIT license\"\n src=\"https://img.shields.io/npm/l/compose-paths?style=plastic\"\n /></a>\n <a href=\"https://bundlephobia.com/result?p=compose-paths\">\n <img\n alt=\"npm bundle size\"\n src=\"https://img.shields.io/bundlephobia/minzip/compose-paths?style=plastic\"\n /></a>\n <a href=\"https://www.npmjs.com/package/compose-paths\">\n <img\n alt=\"Version\"\n src=\"https://img.shields.io/npm/v/compose-paths?style=plastic\"\n /></a>\n</p>\n\n```sh\n$ pnpm i compose-paths\n```\n\n`compose-paths` looks at the indentation-level of its input as the cue to concatenate lines together. Either `tabs` or `spaces` should be fine, so long as you're consistent.\n\n## Usage\n\n### `Path` &rarr; `Alias`\n\n```js\nconst { composePaths } = require('compose-paths')\n\nconst paths = composePaths(`\n\n ${__dirname}\n /src\n /html\n /templates = TEMPLATES\n /pages = PAGES\n\n /public = PUBLIC\n /images = IMAGES\n\n`)\n\npaths.TEMPLATES\n// \"/dir/name/src/html/templates\"\n\npaths.PUBLIC\n// \"/dir/name/public\"\n\npaths.aliases\n// [\"TEMPLATES\", \"PAGES\", \"PUBLIC\", \"IMAGES\"]\n```\n\n### `Path` &rarr; `Route` (via aliases)\n\n```js\nconst { composePaths, zip } = require('compose-paths')\n\nconst routes = composePaths(`\n / = HOME\n /about = ABOUT\n /contact = CONTACT\n`)\n\nconst paths = composePaths(`\n ${__dirname}\n /src\n /html/pages\n /index.html = HOME\n /about.html = ABOUT\n /contact.html = CONTACT\n`)\n\nconst staticRoutes = zip(routes, paths)\n\n// Then, for example:\nstaticRoutes.forEach(([route, path]) => {\n app.get(route, sendFile(path))\n})\n```\n\n### `Path` &rarr; `Route` (directly)\n\n```js\nconst { composePaths } = require('compose-paths')\n\nconst pathFromRoute = composePaths(`\n\n ${__dirname}\n /src/html/pages\n /index.html = /\n /about.html = /about\n /contact.html = /contact\n\n`)\n\npathFromRoute['/']\n// \"/dir/name/src/html/pages/index.html\"\n\npathFromRoute['/contact']\n// \"/dir/name/src/html/pages/contact.html\"\n\npathFromRoute.aliases.forEach(route => {\n console.log(pathFromRoute[route])\n})\n```\n\nThat's it!\n\n# Credits\n\n`compose-paths` was written by [Conan Theobald](https://github.com/shuckster/).\n\nDid you find this useful? If so, I like [coffee ☕️](https://www.buymeacoffee.com/shuckster) :)\n\n## License\n\nMIT licensed: See [LICENSE](LICENSE)\n"
68
+ }
65
69
  }