filedist 0.21.2

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.
Files changed (154) hide show
  1. package/README.md +631 -0
  2. package/dist/cli/actions/check.d.ts +6 -0
  3. package/dist/cli/actions/check.d.ts.map +1 -0
  4. package/dist/cli/actions/check.js +36 -0
  5. package/dist/cli/actions/check.js.map +1 -0
  6. package/dist/cli/actions/extract.d.ts +7 -0
  7. package/dist/cli/actions/extract.d.ts.map +1 -0
  8. package/dist/cli/actions/extract.js +60 -0
  9. package/dist/cli/actions/extract.js.map +1 -0
  10. package/dist/cli/actions/init.d.ts +6 -0
  11. package/dist/cli/actions/init.d.ts.map +1 -0
  12. package/dist/cli/actions/init.js +30 -0
  13. package/dist/cli/actions/init.js.map +1 -0
  14. package/dist/cli/actions/list.d.ts +7 -0
  15. package/dist/cli/actions/list.d.ts.map +1 -0
  16. package/dist/cli/actions/list.js +30 -0
  17. package/dist/cli/actions/list.js.map +1 -0
  18. package/dist/cli/actions/presets.d.ts +7 -0
  19. package/dist/cli/actions/presets.d.ts.map +1 -0
  20. package/dist/cli/actions/presets.js +34 -0
  21. package/dist/cli/actions/presets.js.map +1 -0
  22. package/dist/cli/actions/purge.d.ts +6 -0
  23. package/dist/cli/actions/purge.d.ts.map +1 -0
  24. package/dist/cli/actions/purge.js +39 -0
  25. package/dist/cli/actions/purge.js.map +1 -0
  26. package/dist/cli/argv.d.ts +52 -0
  27. package/dist/cli/argv.d.ts.map +1 -0
  28. package/dist/cli/argv.js +172 -0
  29. package/dist/cli/argv.js.map +1 -0
  30. package/dist/cli/bin.d.ts +3 -0
  31. package/dist/cli/bin.d.ts.map +1 -0
  32. package/dist/cli/bin.js +10 -0
  33. package/dist/cli/bin.js.map +1 -0
  34. package/dist/cli/binpkg.d.ts +13 -0
  35. package/dist/cli/binpkg.d.ts.map +1 -0
  36. package/dist/cli/binpkg.js +38 -0
  37. package/dist/cli/binpkg.js.map +1 -0
  38. package/dist/cli/cli.d.ts +11 -0
  39. package/dist/cli/cli.d.ts.map +1 -0
  40. package/dist/cli/cli.js +118 -0
  41. package/dist/cli/cli.js.map +1 -0
  42. package/dist/cli/progress.d.ts +6 -0
  43. package/dist/cli/progress.d.ts.map +1 -0
  44. package/dist/cli/progress.js +12 -0
  45. package/dist/cli/progress.js.map +1 -0
  46. package/dist/cli/usage.d.ts +6 -0
  47. package/dist/cli/usage.d.ts.map +1 -0
  48. package/dist/cli/usage.js +151 -0
  49. package/dist/cli/usage.js.map +1 -0
  50. package/dist/fileset/check.d.ts +15 -0
  51. package/dist/fileset/check.d.ts.map +1 -0
  52. package/dist/fileset/check.js +70 -0
  53. package/dist/fileset/check.js.map +1 -0
  54. package/dist/fileset/constants.d.ts +18 -0
  55. package/dist/fileset/constants.d.ts.map +1 -0
  56. package/dist/fileset/constants.js +30 -0
  57. package/dist/fileset/constants.js.map +1 -0
  58. package/dist/fileset/diff.d.ts +16 -0
  59. package/dist/fileset/diff.d.ts.map +1 -0
  60. package/dist/fileset/diff.js +111 -0
  61. package/dist/fileset/diff.js.map +1 -0
  62. package/dist/fileset/execute.d.ts +29 -0
  63. package/dist/fileset/execute.d.ts.map +1 -0
  64. package/dist/fileset/execute.js +153 -0
  65. package/dist/fileset/execute.js.map +1 -0
  66. package/dist/fileset/gitignore.d.ts +20 -0
  67. package/dist/fileset/gitignore.d.ts.map +1 -0
  68. package/dist/fileset/gitignore.js +94 -0
  69. package/dist/fileset/gitignore.js.map +1 -0
  70. package/dist/fileset/index.d.ts +5 -0
  71. package/dist/fileset/index.d.ts.map +1 -0
  72. package/dist/fileset/index.js +22 -0
  73. package/dist/fileset/index.js.map +1 -0
  74. package/dist/fileset/markers.d.ts +22 -0
  75. package/dist/fileset/markers.d.ts.map +1 -0
  76. package/dist/fileset/markers.js +74 -0
  77. package/dist/fileset/markers.js.map +1 -0
  78. package/dist/fileset/package-files.d.ts +21 -0
  79. package/dist/fileset/package-files.d.ts.map +1 -0
  80. package/dist/fileset/package-files.js +102 -0
  81. package/dist/fileset/package-files.js.map +1 -0
  82. package/dist/fileset/purge.d.ts +18 -0
  83. package/dist/fileset/purge.d.ts.map +1 -0
  84. package/dist/fileset/purge.js +94 -0
  85. package/dist/fileset/purge.js.map +1 -0
  86. package/dist/fileset/test-utils.d.ts +21 -0
  87. package/dist/fileset/test-utils.d.ts.map +1 -0
  88. package/dist/fileset/test-utils.js +94 -0
  89. package/dist/fileset/test-utils.js.map +1 -0
  90. package/dist/index.d.ts +14 -0
  91. package/dist/index.d.ts.map +1 -0
  92. package/dist/index.js +19 -0
  93. package/dist/index.js.map +1 -0
  94. package/dist/package/action-check.d.ts +20 -0
  95. package/dist/package/action-check.d.ts.map +1 -0
  96. package/dist/package/action-check.js +65 -0
  97. package/dist/package/action-check.js.map +1 -0
  98. package/dist/package/action-extract.d.ts +20 -0
  99. package/dist/package/action-extract.d.ts.map +1 -0
  100. package/dist/package/action-extract.js +280 -0
  101. package/dist/package/action-extract.js.map +1 -0
  102. package/dist/package/action-init.d.ts +13 -0
  103. package/dist/package/action-init.d.ts.map +1 -0
  104. package/dist/package/action-init.js +83 -0
  105. package/dist/package/action-init.js.map +1 -0
  106. package/dist/package/action-list.d.ts +11 -0
  107. package/dist/package/action-list.d.ts.map +1 -0
  108. package/dist/package/action-list.js +12 -0
  109. package/dist/package/action-list.js.map +1 -0
  110. package/dist/package/action-purge.d.ts +19 -0
  111. package/dist/package/action-purge.d.ts.map +1 -0
  112. package/dist/package/action-purge.js +135 -0
  113. package/dist/package/action-purge.js.map +1 -0
  114. package/dist/package/calculate-diff.d.ts +12 -0
  115. package/dist/package/calculate-diff.d.ts.map +1 -0
  116. package/dist/package/calculate-diff.js +129 -0
  117. package/dist/package/calculate-diff.js.map +1 -0
  118. package/dist/package/config-merge.d.ts +19 -0
  119. package/dist/package/config-merge.d.ts.map +1 -0
  120. package/dist/package/config-merge.js +65 -0
  121. package/dist/package/config-merge.js.map +1 -0
  122. package/dist/package/config.d.ts +24 -0
  123. package/dist/package/config.d.ts.map +1 -0
  124. package/dist/package/config.js +86 -0
  125. package/dist/package/config.js.map +1 -0
  126. package/dist/package/content-replacements.d.ts +21 -0
  127. package/dist/package/content-replacements.d.ts.map +1 -0
  128. package/dist/package/content-replacements.js +94 -0
  129. package/dist/package/content-replacements.js.map +1 -0
  130. package/dist/package/index.d.ts +10 -0
  131. package/dist/package/index.d.ts.map +1 -0
  132. package/dist/package/index.js +16 -0
  133. package/dist/package/index.js.map +1 -0
  134. package/dist/package/resolve-files.d.ts +29 -0
  135. package/dist/package/resolve-files.d.ts.map +1 -0
  136. package/dist/package/resolve-files.js +225 -0
  137. package/dist/package/resolve-files.js.map +1 -0
  138. package/dist/package/source.d.ts +20 -0
  139. package/dist/package/source.d.ts.map +1 -0
  140. package/dist/package/source.js +193 -0
  141. package/dist/package/source.js.map +1 -0
  142. package/dist/package/symlinks.d.ts +25 -0
  143. package/dist/package/symlinks.d.ts.map +1 -0
  144. package/dist/package/symlinks.js +203 -0
  145. package/dist/package/symlinks.js.map +1 -0
  146. package/dist/types.d.ts +345 -0
  147. package/dist/types.d.ts.map +1 -0
  148. package/dist/types.js +3 -0
  149. package/dist/types.js.map +1 -0
  150. package/dist/utils.d.ts +60 -0
  151. package/dist/utils.d.ts.map +1 -0
  152. package/dist/utils.js +341 -0
  153. package/dist/utils.js.map +1 -0
  154. package/package.json +58 -0
package/README.md ADDED
@@ -0,0 +1,631 @@
1
+ # filedist
2
+
3
+ Publish folders as npm packages or git repositories and extract them in any workspace. Use it to distribute shared assets — ML datasets, documentation, ADRs, configuration files — across multiple projects through any npm-compatible registry or directly from git.
4
+
5
+ ## How it works
6
+
7
+ - **Publisher**: a project that has folders to share. Running `init` prepares its `package.json` so those folders are included when the package is published.
8
+ - **Consumer**: any project that installs that package and runs `extract` to download the files locally. A `.filedist` marker file is written alongside the managed files to track ownership and enable safe updates.
9
+
10
+ ## Extraction patterns
11
+
12
+ There are three ways to extract data with `filedist`. Choose the one that fits your situation:
13
+
14
+ ### Pattern 1 — Ad-hoc CLI extraction
15
+
16
+ Use `npx filedist extract` directly from the command line whenever you need to pull files from a package without any prior setup.
17
+
18
+ ```sh
19
+ npx filedist extract --packages my-shared-assets@^2.0.0 --output ./data
20
+
21
+ # or use a git repository as the source (auto-detected from the URL-like spec)
22
+ npx filedist extract --packages https://github.com/flaviostutz/xdrs-core@1.3.0 --output ./xdrs
23
+ ```
24
+
25
+ Git sources are detected automatically when `package` uses a URL-like git spec (`https://`, `ssh://`, `git://`, `file://`, or `git@...`). Set `source: "git"` only when you want to force git resolution explicitly.
26
+
27
+ ### Pattern 2 — Data packages with embedded configuration
28
+
29
+ Create a dedicated npm package whose `package.json` declares an `filedist` config block. That config encodes the extraction sources, output directories, filtering rules, and any combination of upstream packages. Consumers install the data package and run its bundled script — they don't need to know the internals.
30
+
31
+ **Publisher** — add an `filedist` block to the data package's `package.json`:
32
+
33
+ ```json
34
+ {
35
+ "name": "my-org-configs",
36
+ "version": "1.0.0",
37
+ "filedist": {
38
+ "sets": [
39
+ {
40
+ "package": "base-datasets@^3.0.0",
41
+ "selector": { "files": ["datasets/**"] },
42
+ "output": { "path": "./data/base" }
43
+ },
44
+ {
45
+ "package": "org-configs@^1.2.0",
46
+ "selector": { "contentRegexes": ["env: production"] },
47
+ "output": { "path": "./configs" }
48
+ },
49
+ {
50
+ "package": "https://github.com/flaviostutz/xdrs-core@1.3.0",
51
+ "selector": { "files": ["docs/**"] },
52
+ "output": { "path": "./xdrs" }
53
+ }
54
+ ]
55
+ }
56
+ }
57
+ ```
58
+
59
+ Run `pnpm dlx filedist init` in that package and then `npm publish` to release it.
60
+
61
+ **Consumer** — just install and run:
62
+
63
+ ```sh
64
+ npx my-org-configs extract --output ./local-data
65
+ ```
66
+
67
+ No knowledge of the upstream packages or transformation rules is required.
68
+
69
+ **When to use:** When an intermediary team (a platform, infrastructure, or data team) wants to bundle, curate, and version a collection of data from multiple sources and hand it to consumers as a single, opinionated package. Consumers get a stable, self-describing interface; producers control all the complexity.
70
+
71
+ ### Pattern 3 — Config file mode
72
+
73
+ Add an `filedist` configuration directly to a project's own `package.json` (or a `.filedistrc` file) and then run `filedist extract` without `--packages`. The CLI automatically loads the configuration and runs every entry, reusing the same runner logic as data packages.
74
+
75
+ **Consumer** — declare the config inline in `package.json`:
76
+
77
+ ```json
78
+ {
79
+ "name": "my-project",
80
+ "filedist": {
81
+ "sets": [
82
+ {
83
+ "package": "base-datasets@^3.0.0",
84
+ "selector": { "files": ["datasets/**"] },
85
+ "output": { "path": "./data" }
86
+ },
87
+ {
88
+ "package": "https://github.com/flaviostutz/xdrs-core@1.3.0",
89
+ "selector": { "files": ["docs/**"] },
90
+ "output": { "path": "./xdrs" }
91
+ }
92
+ ]
93
+ }
94
+ }
95
+ ```
96
+
97
+ Or write a standalone `.filedistrc` (JSON object at the top level):
98
+
99
+ ```json
100
+ {
101
+ "sets": [
102
+ {
103
+ "package": "base-datasets@^3.0.0",
104
+ "selector": { "files": ["datasets/**"] },
105
+ "output": { "path": "./data" }
106
+ },
107
+ {
108
+ "package": "file:///absolute/path/to/local-repo@v2.0.0",
109
+ "selector": { "files": ["conf/**"] },
110
+ "output": { "path": "./local-conf" }
111
+ }
112
+ ]
113
+ }
114
+ ```
115
+
116
+ Then run any command without `--packages`:
117
+
118
+ ```sh
119
+ npx filedist # same as 'npx filedist extract'
120
+ npx filedist extract # reads config, extracts all entries
121
+ npx filedist check # checks all entries
122
+ npx filedist purge # purges all entries
123
+ ```
124
+
125
+ Config is resolved using [cosmiconfig](https://github.com/cosmiconfig/cosmiconfig). Sources searched in order from the current directory:
126
+
127
+ | Source | Key / format |
128
+ |---|---|
129
+ | `package.json` | `"filedist"` key — object with `"sets"` array |
130
+ | `.filedistrc` | JSON or YAML object with `"sets"` array |
131
+ | `.filedistrc.json` | JSON object with `"sets"` array |
132
+ | `.filedistrc.yaml` / `.filedistrc.yml` | YAML object with `"sets"` array |
133
+ | `filedist.config.js` | CommonJS module exporting object with `sets` array |
134
+
135
+ All runner flags (`--dry-run`, `--silent`, `--verbose`, `--gitignore=false`, `--managed=false`, `--presets`, `--output`) work as usual.
136
+
137
+ Config-file mode can mix npm packages and git repositories in the same `sets` array. Git entries can omit `source` when the package spec is URL-like; filedist auto-detects them as git.
138
+
139
+ **When to use:** When a consuming project wants to pin and automate a set of data extractions locally without publishing a separate data package. This is the lightest-weight approach — no extra package, no `init` step, just a config block and a single CLI call.
140
+
141
+ ---
142
+
143
+ ## Quick start
144
+
145
+ ### 1. Prepare the publisher package
146
+
147
+ In the project whose folders you want to share:
148
+
149
+ ```sh
150
+ # share specific folders by glob pattern (required)
151
+ pnpm dlx filedist init --files "docs/**,data/**,configs/**"
152
+
153
+ # also bundle an additional package so consumers get data from both sources
154
+ pnpm dlx filedist init --files "docs/**" --packages shared-configs@^1.0.0
155
+
156
+ # share multiple upstream sources, including git
157
+ pnpm dlx filedist init --files "docs/**" --packages "shared-configs@^1.0.0,https://github.com/flaviostutz/xdrs-core@1.3.0"
158
+
159
+ ```
160
+
161
+ `init` updates `package.json` with the right `files`, `bin`, and `dependencies` fields so those folders are included when the package is published, and writes a thin `bin/filedist.js` entry point. Then publish normally:
162
+
163
+ ```sh
164
+ npm publish
165
+ ```
166
+
167
+ ### 2. Extract files in a consumer project
168
+
169
+ ```sh
170
+ # npm package examples
171
+ npx filedist extract --packages my-shared-assets --output ./data
172
+ npx filedist extract --packages my-shared-assets@^2.0.0 --output ./data
173
+ npx filedist extract --packages "my-shared-assets@^2.0.0,another-pkg@1.x" --output ./data
174
+ npx filedist extract --packages my-shared-assets --files "**/*.md" --output ./docs
175
+ npx filedist extract --packages my-shared-assets --content-regex "env: production" --output ./configs
176
+ npx filedist extract --packages my-shared-assets --output ./data --force
177
+ npx filedist extract --packages my-shared-assets --output ./data --gitignore=false
178
+ npx filedist extract --packages my-shared-assets --output ./data --managed=false
179
+ npx filedist extract --packages my-shared-assets --output ./data --dry-run
180
+ npx filedist extract --packages my-shared-assets@latest --output ./data --upgrade
181
+
182
+ # git source examples
183
+ npx filedist extract --packages https://github.com/flaviostutz/xdrs-core@1.3.0 --output ./xdrs
184
+ npx filedist extract --packages https://github.com/flaviostutz/xdrs-core@main --output ./xdrs
185
+ npx filedist extract --packages "https://github.com/org/repo-a@v1.0.0,file:///tmp/repo-b@main" --output ./git-data
186
+ npx filedist extract --packages https://github.com/flaviostutz/xdrs-core@1.3.0 --files "docs/**/*.md" --output ./docs
187
+ npx filedist extract --packages https://github.com/flaviostutz/xdrs-core@1.3.0 --content-regex "Decision Outcome" --output ./filtered-docs
188
+ npx filedist extract --packages https://github.com/flaviostutz/xdrs-core@1.3.0 --output ./xdrs --force
189
+ npx filedist extract --packages https://github.com/flaviostutz/xdrs-core@1.3.0 --output ./xdrs --gitignore=false
190
+ npx filedist extract --packages https://github.com/flaviostutz/xdrs-core@1.3.0 --output ./xdrs --managed=false
191
+ npx filedist extract --packages https://github.com/flaviostutz/xdrs-core@1.3.0 --output ./xdrs --dry-run
192
+ npx filedist extract --packages https://github.com/flaviostutz/xdrs-core@main --output ./xdrs --upgrade
193
+ ```
194
+
195
+ `extract` logs every file change as it happens:
196
+
197
+ ```
198
+ A data/users-dataset/user1.json
199
+ M data/configs/app.config.json
200
+ D data/old-file.json
201
+ ```
202
+
203
+ If the published package includes its own bin script (normally when it's prepared using "init") you can also call it directly so it extracts data that is inside the package itself:
204
+
205
+ ```sh
206
+ npx my-shared-assets extract --output ./data
207
+ npx my-shared-assets check --output ./data
208
+ ```
209
+
210
+ When the data package defines multiple `filedist` entries in its `package.json`, you can limit which entries are processed using the `--presets` option. Only entries whose `presets` list includes at least one of the requested presets will be extracted; entries with no presets are skipped when a preset filter is active.
211
+
212
+ ```sh
213
+ # run only entries tagged with "prod"
214
+ npx my-shared-assets --presets prod
215
+
216
+ # run entries tagged with either "prod" or "staging"
217
+ npx my-shared-assets --presets prod,staging
218
+ ```
219
+
220
+ To use presets, add a `presets` array to each `filedist` entry in the data package's `package.json`:
221
+
222
+ ```json
223
+ {
224
+ "filedist": {
225
+ "sets": [
226
+ { "package": "my-shared-assets", "output": { "path": "./data" }, "presets": ["prod"] },
227
+ { "package": "my-dev-assets", "output": { "path": "./dev-data" }, "presets": ["dev", "staging"] }
228
+ ]
229
+ }
230
+ }
231
+ ```
232
+
233
+ Check the /examples folder to see this in action
234
+
235
+ ### Data package CLI options
236
+
237
+ When calling the bin script bundled in a data package, the following options are accepted. Options that overlap with per-entry settings override every entry globally, regardless of what is set in `package.json`.
238
+
239
+ | Option | Description |
240
+ |---|---|
241
+ | `--output, -o <dir>` | Base directory for resolving all `output.path` values (default: cwd). |
242
+ | `--presets <preset1,preset2>` | Limit to entries whose `presets` overlap with the given list (comma-separated). |
243
+ | `--nosync [bool]` | Keep stale managed files on disk during extract instead of deleting them. `check` still reports them as extra drift. |
244
+ | `--gitignore [bool]` | Disable `.gitignore` management for every entry when set to `false`, overriding each entry's `gitignore` field. |
245
+ | `--managed [bool]` | Run every entry in unmanaged mode when set to `false`, overriding each entry's `managed` field. Files are written without a `.filedist` marker, without `.gitignore` updates, and without being made read-only. |
246
+ | `--dry-run` | Simulate changes without writing or deleting any files. |
247
+ | `--verbose, -v` | Print detailed progress information for each step. |
248
+
249
+ ```sh
250
+ # disable gitignore management across all entries
251
+ npx my-shared-assets --gitignore=false
252
+
253
+ # keep stale managed files on disk during extract
254
+ npx my-shared-assets --nosync
255
+
256
+ # write all files as not-managed (editable, not tracked)
257
+ npx my-shared-assets --managed=false
258
+
259
+ # combine overrides
260
+ npx my-shared-assets --gitignore=false --managed=false --dry-run
261
+ ```
262
+
263
+ ### filedist entry options reference
264
+
265
+ Each entry in the `filedist.sets` array in `package.json` supports the following options:
266
+
267
+ | Option | Type | Default | Description |
268
+ |---|---|---|---|
269
+ | `package` | `string` | required | Source spec to install and extract. Either npm (`my-pkg`, `my-pkg@^1.2.3`) or git (`https://host/org/repo@ref`). |
270
+ | `source` | `auto \| npm \| git` | `auto` | Force the source type. `auto` detects git from URL-like specs and otherwise uses npm. |
271
+ | `output.path` | `string` | `.` (cwd) | Directory where files will be extracted, relative to where the consumer runs the command. |
272
+ | `selector.files` | `string[]` | all files | Glob patterns to filter which files are extracted (e.g. `["data/**", "*.json"]`). |
273
+ | `selector.exclude` | `string[]` | `["package.json","bin/**","README.md","node_modules/**"]` (when `files` is unset), none otherwise | Glob patterns to exclude files even when they match `selector.files` (e.g. `["test/**", "**/*.test.*"]`). |
274
+ | `selector.contentRegexes` | `string[]` | none | Regex patterns (as strings) to filter files by content. Only files matching at least one pattern are extracted. |
275
+ | `output.force` | `boolean` | `false` | Allow overwriting existing files or files owned by a different package. |
276
+ | `output.keepExisting` | `boolean` | `false` | Skip files that already exist but create them when absent. Cannot be combined with `force`. |
277
+ | `output.noSync` | `boolean` | `false` | Keep stale managed files on disk during extract instead of deleting them. `check` still reports them as extra drift until they are removed or synced. |
278
+ | `output.gitignore` | `boolean` | `true` | Create/update a `.gitignore` file alongside each `.filedist` marker file. Set to `false` to disable. |
279
+ | `output.managed` | `boolean` | `true` | Write files with a `.filedist` marker, `.gitignore` update, and read-only flag. Set to `false` to skip tracking. Existing files are skipped when set to `false`. |
280
+ | `output.dryRun` | `boolean` | `false` | Simulate extraction without writing anything to disk. |
281
+ | `selector.upgrade` | `boolean` | `false` | Force a fresh install of the package even when a satisfying version is already installed. |
282
+ | `silent` | `boolean` | `false` | Suppress per-file output, printing only the final result line. |
283
+ | `presets` | `string[]` | none | Presets used to group and selectively run entries with `--presets`. |
284
+ | `output.symlinks` | `SymlinkConfig[]` | none | Post-extract symlink operations (see below). |
285
+ | `output.contentReplacements` | `ContentReplacementConfig[]` | none | Post-extract content-replacement operations (see below). |
286
+
287
+ #### SymlinkConfig
288
+
289
+ After extraction, for each config the runner resolves all files/directories inside `output.path` that match `source` and creates a corresponding symlink inside `target`. Stale symlinks pointing into `output.path` but no longer matched are removed automatically.
290
+
291
+ | Field | Type | Description |
292
+ |---|---|---|
293
+ | `source` | `string` | Glob pattern relative to `output.path`. Every matching file or directory gets a symlink in `target`. Example: `"**\/skills\/**"` |
294
+ | `target` | `string` | Directory where symlinks are created, relative to the project root. Example: `".github/skills"` |
295
+
296
+ #### ContentReplacementConfig
297
+
298
+ After extraction, for each config the runner finds workspace files matching `files` and applies the regex replacement to their contents.
299
+
300
+ | Field | Type | Description |
301
+ |---|---|---|
302
+ | `files` | `string` | Glob pattern (relative to the project root) selecting workspace files to modify. Example: `"docs/**\/*.md"` |
303
+ | `match` | `string` | Regex string locating the text to replace. Applied globally to all non-overlapping occurrences. Example: `"<!-- version: .* -->"` |
304
+ | `replace` | `string` | Replacement string. May contain regex back-references such as `$1`. Example: `"<!-- version: 1.2.3 -->"` |
305
+
306
+ Example with multiple options:
307
+
308
+ ```json
309
+ {
310
+ "filedist": {
311
+ "sets": [
312
+ {
313
+ "package": "my-shared-assets@^2.0.0",
314
+ "selector": {
315
+ "files": ["docs/**", "configs/*.json"],
316
+ "upgrade": true
317
+ },
318
+ "output": {
319
+ "path": "./data",
320
+ "gitignore": true,
321
+ "symlinks": [
322
+ { "source": "**\/skills\/**", "target": ".github/skills" }
323
+ ],
324
+ "contentReplacements": [
325
+ { "files": "docs/**\/*.md", "match": "<!-- version: .* -->", "replace": "<!-- version: 2.0.0 -->" }
326
+ ]
327
+ },
328
+ "presets": ["prod"]
329
+ },
330
+ {
331
+ "package": "https://github.com/flaviostutz/xdrs-core@1.3.0",
332
+ "selector": {
333
+ "files": ["docs/**"],
334
+ "upgrade": true
335
+ },
336
+ "output": {
337
+ "path": "./xdrs",
338
+ "gitignore": false
339
+ },
340
+ "presets": ["prod"]
341
+ }
342
+ ]
343
+ }
344
+ }
345
+ ```
346
+
347
+ ### 3. Check files are in sync
348
+
349
+ Verifies that every file in the output directory matches what is currently in the published package. When the target package itself declares `filedist.sets`, check recurses into those transitive dependencies — reporting drift at every level of the hierarchy without downloading anything new beyond what is already installed. Use `selector.presets` on an entry to restrict which of the target's sets are checked.
350
+
351
+ ```sh
352
+ npx filedist check --packages my-shared-assets --output ./data
353
+ # exit 0 = in sync, exit 1 = drift or error
354
+
355
+ # check multiple packages
356
+ npx filedist check --packages "my-shared-assets,another-pkg" --output ./data
357
+ ```
358
+
359
+ The check command reports differences per package:
360
+
361
+ ```
362
+ my-shared-assets@2.1.0: out of sync
363
+ - missing: data/new-file.json
364
+ ~ modified: data/configs/app.config.json
365
+ + extra: data/old-file.json
366
+ ```
367
+
368
+ ### 4. List managed files
369
+
370
+ ```sh
371
+ # list all files managed by filedist in an output directory
372
+ npx filedist list --output ./data
373
+ ```
374
+
375
+ Output is grouped by package:
376
+
377
+ ```
378
+ my-shared-assets@2.1.0
379
+ data/users-dataset/user1.json
380
+ data/configs/app.config.json
381
+
382
+ another-pkg@1.0.0
383
+ data/other-file.txt
384
+ ```
385
+
386
+ ### 5. Purge managed files
387
+
388
+ Remove all files previously extracted by one or more packages without touching any other files in the output directory. No network access or package installation is required — only the local `.filedist` marker state is used. When the target package itself declares `filedist.sets`, purge recurses into those transitive dependencies and removes their managed files too, mirroring what extract originally created.
389
+
390
+ ```sh
391
+ # remove all files managed by a package
392
+ npx filedist purge --packages my-shared-assets --output ./data
393
+
394
+ # purge multiple packages at once
395
+ npx filedist purge --packages "my-shared-assets,another-pkg" --output ./data
396
+
397
+ # preview what would be deleted without removing anything
398
+ npx filedist purge --packages my-shared-assets --output ./data --dry-run
399
+ ```
400
+
401
+ After a purge, the corresponding entries are removed from the `.filedist` marker file and any empty directories are cleaned up. `.gitignore` sections written by `extract` are also removed.
402
+
403
+ ## Hierarchical package resolution
404
+
405
+ `extract`, `check`, and `purge` are all hierarchy-aware: when a target package or git repository carries its own `filedist.sets` block in its `package.json` or `.filedistrc*`, the command automatically recurses into those transitive dependencies.
406
+
407
+ This lets you build layered data package chains:
408
+
409
+ ```
410
+ consumer project
411
+ └─ my-org-configs (npm package with filedist.sets)
412
+ ├─ base-datasets (another npm package with its own files)
413
+ └─ org-templates (another npm package with its own files)
414
+ └─ raw-assets (leaf package)
415
+ ```
416
+
417
+ Running `npx filedist extract --packages my-org-configs --output ./data` will extract files from every package in the chain, not just `my-org-configs` itself.
418
+
419
+ When the source is git, filedist clones repositories into `.filedist-tmp` inside the working directory, adds that folder to `.gitignore` if needed, resolves nested config from the cloned repository, and removes `.filedist-tmp` when the command ends.
420
+
421
+ ### Output path resolution
422
+
423
+ Each level's `output.path` is resolved relative to the caller's own `output.path`. A package at depth 1 with `output.path: "./configs"` and a transitive dependency with `output.path: "./shared"` will land at `./configs/shared`.
424
+
425
+ ### Caller overrides (extract only)
426
+
427
+ When `extract` recurses, the caller's `output` flags are inherited by every transitive dependency, with caller-defined values always winning:
428
+
429
+ | Caller sets | Effect on transitive entries |
430
+ |---|---|
431
+ | `force: true` | Transitive entries also overwrite unmanaged / foreign files |
432
+ | `dryRun: true` | No files are written anywhere in the hierarchy |
433
+ | `keepExisting: true` | Existing files are skipped at every level |
434
+ | `gitignore: false` | No `.gitignore` entries are created anywhere |
435
+ | `managed: false` | All transitive files are written without a marker or read-only flag |
436
+ | `symlinks` / `contentReplacements` | Appended to each transitive entry's own lists |
437
+
438
+ Settings that are undefined on the caller are left as-is so the transitive package's own defaults apply.
439
+
440
+ ### Filtering transitive sets with `selector.presets`
441
+
442
+ Set `selector.presets` on an entry to control which sets inside the target package are recursed into. Only sets whose `presets` tag overlaps with the filter are processed; sets with no `presets` are skipped when a filter is active.
443
+
444
+ ```json
445
+ {
446
+ "filedist": {
447
+ "sets": [
448
+ {
449
+ "package": "my-org-configs@^2.0.0",
450
+ "output": { "path": "./data" },
451
+ "selector": { "presets": ["prod"] }
452
+ }
453
+ ]
454
+ }
455
+ }
456
+ ```
457
+
458
+ The same filtering is applied during `check` and `purge` so they stay in sync with what `extract` originally wrote.
459
+
460
+ ### Circular dependency detection
461
+
462
+ If a package chain references itself (directly or transitively), the command stops immediately with an error rather than looping forever. Sibling packages — entries already being processed at the same level — are also skipped to prevent double-processing.
463
+
464
+ ## CLI reference
465
+
466
+ ```
467
+ Usage:
468
+ npx filedist [init|extract|check|list|purge] [options]
469
+
470
+ Commands:
471
+ init Set up publishing configuration in a package
472
+ extract Extract files from a published package into a local directory
473
+ check Verify local files are in sync with the published package
474
+ list List all files managed by filedist in an output directory
475
+ purge Remove all managed files previously extracted by given packages
476
+
477
+ Global options:
478
+ --help, -h Show help
479
+ --version Show version
480
+
481
+ Init options:
482
+ --files <patterns> Comma-separated glob patterns of files to publish
483
+ e.g. "docs/**,data/**,configs/*.json"
484
+ --packages <specs> Comma-separated additional package specs to bundle as data sources.
485
+ Each spec is "name" or "name@version", e.g.
486
+ "shared-configs@^1.0.0,base-templates@2.x".
487
+ Added to `dependencies` so consumers pull data from all of them.
488
+ --output, -o <dir> Directory to scaffold into (default: current directory)
489
+
490
+ Extract options:
491
+ --packages <specs> Comma-separated package specs.
492
+ --source <kind> Source kind: auto, npm, or git.
493
+ When omitted, filedist searches for a configuration file
494
+ (package.json "filedist" key, .filedistrc, etc.) and runs all
495
+ entries defined there.
496
+ Each spec is "name" or "name@version", e.g.
497
+ "my-pkg@^1.0.0,other-pkg@2.x"
498
+ --output, -o <dir> Output directory (default: current directory)
499
+ --force Overwrite existing files or files owned by a different package
500
+ --keep-existing Skip files that already exist; create them when absent. Cannot be
501
+ combined with --force
502
+ --gitignore [bool] Disable .gitignore management when set to false (enabled by default)
503
+ --managed [bool] Set to false to write files without a .filedist marker, .gitignore
504
+ update, or read-only flag. Existing files are skipped. Files can be
505
+ freely edited afterwards and are not tracked by filedist.
506
+ --files <patterns> Comma-separated glob patterns to filter files
507
+ --content-regex <regex> Regex to filter files by content
508
+ --dry-run Preview changes without writing any files
509
+ --upgrade Reinstall the package even if already present
510
+ --silent Print only the final result line, suppressing per-file output
511
+ --verbose, -v Print detailed progress information for each step
512
+
513
+ Check options:
514
+ --packages <specs> Same format as extract.
515
+ When omitted, reads from a configuration file (see Pattern 3).
516
+ --output, -o <dir> Output directory to check (default: current directory)
517
+
518
+ Purge options:
519
+ --packages <specs> Comma-separated package names whose managed files should be removed.
520
+ When omitted, reads from a configuration file (see Pattern 3).
521
+ --output, -o <dir> Output directory to purge from (default: current directory)
522
+ --dry-run Simulate purge without removing any files
523
+ --silent Suppress per-file output
524
+
525
+ List options:
526
+ --output, -o <dir> Output directory to inspect (default: current directory)
527
+ ```
528
+
529
+ ## Library usage
530
+
531
+ `filedist` also exports a programmatic API:
532
+
533
+ ```typescript
534
+ import { actionExtract, actionCheck, actionList, actionPurge } from 'filedist';
535
+ import type { FiledistExtractEntry, ProgressEvent } from 'filedist';
536
+
537
+ const entries: FiledistExtractEntry[] = [
538
+ { package: 'my-shared-assets@^2.0.0', output: { path: './data' } },
539
+ ];
540
+ const cwd = process.cwd();
541
+
542
+ // extract files
543
+ const result = await actionExtract({ entries, cwd });
544
+ console.log(result.added, result.modified, result.deleted);
545
+
546
+ // dry-run: preview changes without writing files
547
+ const dryResult = await actionExtract({ entries: entries.map(e => ({ ...e, output: { ...e.output, dryRun: true } })), cwd });
548
+ console.log('Would add', dryResult.added, 'files');
549
+
550
+ // track progress file-by-file
551
+ await actionExtract({
552
+ entries,
553
+ cwd,
554
+ onProgress: (event: ProgressEvent) => {
555
+ if (event.type === 'file-added') console.log('A', event.file);
556
+ if (event.type === 'file-modified') console.log('M', event.file);
557
+ if (event.type === 'file-deleted') console.log('D', event.file);
558
+ },
559
+ });
560
+
561
+ // check sync status
562
+ const summary = await actionCheck({ entries, cwd });
563
+ const hasDrift = summary.missing.length > 0 || summary.modified.length > 0 || summary.extra.length > 0;
564
+ if (hasDrift) {
565
+ console.log('Missing:', summary.missing);
566
+ console.log('Modified:', summary.modified);
567
+ console.log('Extra:', summary.extra);
568
+ }
569
+
570
+ // remove all managed files (no network required)
571
+ await actionPurge({ entries, config: null, cwd });
572
+
573
+ // list all files managed by filedist in an output directory
574
+ const managed = await actionList({ entries, config: null, cwd });
575
+ // ManagedFileMetadata[]: Array<{ path: string; packageName: string; packageVersion: string }>
576
+ ```
577
+
578
+ ### `ProgressEvent` type
579
+
580
+ ```typescript
581
+ type ProgressEvent =
582
+ | { type: 'package-start'; packageName: string; packageVersion: string }
583
+ | { type: 'package-end'; packageName: string; packageVersion: string }
584
+ | { type: 'file-added'; packageName: string; file: string }
585
+ | { type: 'file-modified'; packageName: string; file: string }
586
+ | { type: 'file-deleted'; packageName: string; file: string }
587
+ | { type: 'file-skipped'; packageName: string; file: string };
588
+ ```
589
+
590
+ See the root [README.md](../README.md) for the full documentation.
591
+
592
+ ## Managed file tracking
593
+
594
+ Extracted files are set read-only (`444`) and tracked in a `.filedist` marker file in each output directory. On subsequent extractions:
595
+
596
+ - Unchanged files are skipped.
597
+ - Updated files are overwritten.
598
+ - Files removed from the package are deleted locally.
599
+
600
+ The marker file uses a `|`-delimited format; files written by older versions of `filedist` using the comma-delimited format are read correctly for backward compatibility.
601
+
602
+ Multiple packages can coexist in the same output directory; each owns its own files.
603
+
604
+ ## Developer Notes
605
+
606
+ ### Module overview
607
+
608
+ | Folder / file | Purpose |
609
+ |---|---|
610
+ | `src/cli/` | CLI entry-points: argument parsing, help text, config loading, per-command handlers |
611
+ | `src/package/` | Package-level orchestration: config resolution, fileset iteration, purge and init coordination |
612
+ | `src/fileset/` | File-level extraction, diff, check, and sync logic |
613
+ | `src/types.ts` | Shared TypeScript types |
614
+ | `src/utils.ts` | Low-level utilities: package install, glob/hash helpers, package manager detection |
615
+ | `src/index.ts` | Public API surface |
616
+
617
+ ### Marker file (`.filedist`)
618
+
619
+ Each output directory that contains managed files gets a `.filedist` CSV file. Columns: `path`, `packageName`, `packageVersion` — one row per file, no header. This is the source of truth for ownership tracking and clean removal.
620
+
621
+ ### Key design decisions
622
+
623
+ - File identity is tracked by path + hash, not by timestamp, to be deterministic across machines.
624
+ - Extract uses a two-phase diff + execute model: compute all changes first, then apply them, enabling conflict detection and rollback before any file is written.
625
+ - The bin shim generated by `filedist init` contains no logic; all behaviour is versioned inside this library.
626
+
627
+ ### Dev workflow
628
+
629
+ ```
630
+ make build lint-fix test
631
+ ```
@@ -0,0 +1,6 @@
1
+ import { FiledistConfig } from '../../types';
2
+ /**
3
+ * `check` CLI action handler.
4
+ */
5
+ export declare function runCheck(config: FiledistConfig | null, argv: string[], cwd: string): Promise<void>;
6
+ //# sourceMappingURL=check.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"check.d.ts","sourceRoot":"","sources":["../../../src/cli/actions/check.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAK7C;;GAEG;AACH,wBAAsB,QAAQ,CAC5B,MAAM,EAAE,cAAc,GAAG,IAAI,EAC7B,IAAI,EAAE,MAAM,EAAE,EACd,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,IAAI,CAAC,CA0Bf"}
@@ -0,0 +1,36 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.runCheck = runCheck;
4
+ const argv_1 = require("../argv");
5
+ const usage_1 = require("../usage");
6
+ const action_check_1 = require("../../package/action-check");
7
+ /**
8
+ * `check` CLI action handler.
9
+ */
10
+ async function runCheck(config, argv, cwd) {
11
+ if (argv.includes('--help')) {
12
+ (0, usage_1.printUsage)('check');
13
+ return;
14
+ }
15
+ const parsed = (0, argv_1.parseArgv)(argv);
16
+ const entries = (0, argv_1.resolveEntriesFromConfigAndArgs)(config, argv);
17
+ const summary = await (0, action_check_1.actionCheck)({
18
+ entries,
19
+ cwd,
20
+ verbose: parsed.verbose,
21
+ });
22
+ const hasDrift = summary.missing.length > 0 || summary.conflict.length > 0 || summary.extra.length > 0;
23
+ if (hasDrift) {
24
+ for (const f of summary.missing)
25
+ console.log(`missing: ${f}`);
26
+ for (const f of summary.conflict)
27
+ console.log(`conflict: ${f}`);
28
+ for (const f of summary.extra)
29
+ console.log(`extra: ${f}`);
30
+ throw new Error('Check failed: some managed files are out of sync');
31
+ }
32
+ else {
33
+ console.log('All managed files are in sync');
34
+ }
35
+ }
36
+ //# sourceMappingURL=check.js.map