@ryanatkn/gro 0.112.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 +21 -0
- package/README.md +257 -0
- package/dist/args.d.ts +59 -0
- package/dist/args.js +132 -0
- package/dist/args.test.d.ts +1 -0
- package/dist/args.test.js +43 -0
- package/dist/build.task.d.ts +11 -0
- package/dist/build.task.js +24 -0
- package/dist/changelog.d.ts +8 -0
- package/dist/changelog.js +47 -0
- package/dist/changelog.test.d.ts +1 -0
- package/dist/changelog.test.js +118 -0
- package/dist/changeset.task.d.ts +49 -0
- package/dist/changeset.task.js +141 -0
- package/dist/check.task.d.ts +47 -0
- package/dist/check.task.js +77 -0
- package/dist/clean.task.d.ts +26 -0
- package/dist/clean.task.js +41 -0
- package/dist/clean_fs.d.ts +9 -0
- package/dist/clean_fs.js +27 -0
- package/dist/cli.d.ts +11 -0
- package/dist/cli.js +25 -0
- package/dist/commit.task.d.ts +11 -0
- package/dist/commit.task.js +22 -0
- package/dist/config.d.ts +21 -0
- package/dist/config.js +42 -0
- package/dist/config.test.d.ts +1 -0
- package/dist/config.test.js +8 -0
- package/dist/deploy.task.d.ts +47 -0
- package/dist/deploy.task.js +198 -0
- package/dist/dev.task.d.ts +22 -0
- package/dist/dev.task.js +32 -0
- package/dist/docs/README.gen.md.d.ts +5 -0
- package/dist/docs/README.gen.md.js +53 -0
- package/dist/docs/README.md +20 -0
- package/dist/docs/build.md +41 -0
- package/dist/docs/config.md +162 -0
- package/dist/docs/deploy.md +32 -0
- package/dist/docs/dev.md +40 -0
- package/dist/docs/gen.md +241 -0
- package/dist/docs/gro_plugin_sveltekit_frontend.md +97 -0
- package/dist/docs/package_json.md +29 -0
- package/dist/docs/plugin.md +50 -0
- package/dist/docs/publish.md +144 -0
- package/dist/docs/task.md +377 -0
- package/dist/docs/tasks.gen.md.d.ts +2 -0
- package/dist/docs/tasks.gen.md.js +60 -0
- package/dist/docs/tasks.md +35 -0
- package/dist/docs/test.md +52 -0
- package/dist/env.d.ts +10 -0
- package/dist/env.js +47 -0
- package/dist/esbuild_helpers.d.ts +14 -0
- package/dist/esbuild_helpers.js +36 -0
- package/dist/esbuild_plugin_external_worker.d.ts +22 -0
- package/dist/esbuild_plugin_external_worker.js +49 -0
- package/dist/esbuild_plugin_svelte.d.ts +9 -0
- package/dist/esbuild_plugin_svelte.js +49 -0
- package/dist/esbuild_plugin_sveltekit_local_imports.d.ts +7 -0
- package/dist/esbuild_plugin_sveltekit_local_imports.js +30 -0
- package/dist/esbuild_plugin_sveltekit_shim_alias.d.ts +6 -0
- package/dist/esbuild_plugin_sveltekit_shim_alias.js +16 -0
- package/dist/esbuild_plugin_sveltekit_shim_app.d.ts +8 -0
- package/dist/esbuild_plugin_sveltekit_shim_app.js +23 -0
- package/dist/esbuild_plugin_sveltekit_shim_env.d.ts +10 -0
- package/dist/esbuild_plugin_sveltekit_shim_env.js +18 -0
- package/dist/format.task.d.ts +11 -0
- package/dist/format.task.js +24 -0
- package/dist/format_directory.d.ts +2 -0
- package/dist/format_directory.js +27 -0
- package/dist/format_file.d.ts +8 -0
- package/dist/format_file.js +42 -0
- package/dist/format_file.test.d.ts +1 -0
- package/dist/format_file.test.js +16 -0
- package/dist/fs.d.ts +7 -0
- package/dist/fs.js +19 -0
- package/dist/fs.test.d.ts +1 -0
- package/dist/fs.test.js +16 -0
- package/dist/gen.d.ts +57 -0
- package/dist/gen.js +81 -0
- package/dist/gen.task.d.ts +14 -0
- package/dist/gen.task.js +103 -0
- package/dist/gen.test.d.ts +1 -0
- package/dist/gen.test.js +239 -0
- package/dist/gen_module.d.ts +46 -0
- package/dist/gen_module.js +54 -0
- package/dist/gen_module.test.d.ts +1 -0
- package/dist/gen_module.test.js +30 -0
- package/dist/git.d.ts +76 -0
- package/dist/git.js +200 -0
- package/dist/git.test.d.ts +1 -0
- package/dist/git.test.js +18 -0
- package/dist/github.d.ts +35 -0
- package/dist/github.js +32 -0
- package/dist/gro.config.default.d.ts +12 -0
- package/dist/gro.config.default.js +31 -0
- package/dist/gro.d.ts +2 -0
- package/dist/gro.js +19 -0
- package/dist/gro_helpers.d.ts +43 -0
- package/dist/gro_helpers.js +79 -0
- package/dist/gro_plugin_gen.d.ts +6 -0
- package/dist/gro_plugin_gen.js +80 -0
- package/dist/gro_plugin_server.d.ts +77 -0
- package/dist/gro_plugin_server.js +152 -0
- package/dist/gro_plugin_sveltekit_app.d.ts +27 -0
- package/dist/gro_plugin_sveltekit_app.js +180 -0
- package/dist/gro_plugin_sveltekit_library.d.ts +4 -0
- package/dist/gro_plugin_sveltekit_library.js +42 -0
- package/dist/hash.d.ts +5 -0
- package/dist/hash.js +14 -0
- package/dist/hash.test.d.ts +1 -0
- package/dist/hash.test.js +25 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +3 -0
- package/dist/input_path.d.ts +48 -0
- package/dist/input_path.js +161 -0
- package/dist/input_path.test.d.ts +1 -0
- package/dist/input_path.test.js +106 -0
- package/dist/invoke.d.ts +1 -0
- package/dist/invoke.js +18 -0
- package/dist/invoke_task.d.ts +20 -0
- package/dist/invoke_task.js +140 -0
- package/dist/lint.task.d.ts +11 -0
- package/dist/lint.task.js +29 -0
- package/dist/loader.d.ts +4 -0
- package/dist/loader.js +153 -0
- package/dist/module.d.ts +3 -0
- package/dist/module.js +6 -0
- package/dist/module.test.d.ts +1 -0
- package/dist/module.test.js +41 -0
- package/dist/modules.d.ts +60 -0
- package/dist/modules.js +103 -0
- package/dist/modules.test.d.ts +1 -0
- package/dist/modules.test.js +182 -0
- package/dist/package.d.ts +939 -0
- package/dist/package.gen.d.ts +7 -0
- package/dist/package.gen.js +26 -0
- package/dist/package.js +887 -0
- package/dist/package_json.d.ts +342 -0
- package/dist/package_json.js +212 -0
- package/dist/package_json.test.d.ts +1 -0
- package/dist/package_json.test.js +77 -0
- package/dist/path.d.ts +12 -0
- package/dist/path.js +8 -0
- package/dist/paths.d.ts +60 -0
- package/dist/paths.js +128 -0
- package/dist/paths.test.d.ts +1 -0
- package/dist/paths.test.js +49 -0
- package/dist/plugin.d.ts +36 -0
- package/dist/plugin.js +80 -0
- package/dist/plugin.test.d.ts +1 -0
- package/dist/plugin.test.js +54 -0
- package/dist/print_task.d.ts +4 -0
- package/dist/print_task.js +124 -0
- package/dist/publish.task.d.ts +32 -0
- package/dist/publish.task.js +125 -0
- package/dist/release.task.d.ts +5 -0
- package/dist/release.task.js +18 -0
- package/dist/resolve_node_specifier.d.ts +8 -0
- package/dist/resolve_node_specifier.js +39 -0
- package/dist/resolve_node_specifier.test.d.ts +1 -0
- package/dist/resolve_node_specifier.test.js +21 -0
- package/dist/resolve_specifier.d.ts +15 -0
- package/dist/resolve_specifier.js +51 -0
- package/dist/resolve_specifier.test.d.ts +1 -0
- package/dist/resolve_specifier.test.js +66 -0
- package/dist/run.task.d.ts +11 -0
- package/dist/run.task.js +31 -0
- package/dist/run_gen.d.ts +6 -0
- package/dist/run_gen.js +74 -0
- package/dist/run_gen.test.d.ts +1 -0
- package/dist/run_gen.test.js +182 -0
- package/dist/run_task.d.ts +13 -0
- package/dist/run_task.js +44 -0
- package/dist/run_task.test.d.ts +1 -0
- package/dist/run_task.test.js +63 -0
- package/dist/search_fs.d.ts +11 -0
- package/dist/search_fs.js +22 -0
- package/dist/search_fs.test.d.ts +1 -0
- package/dist/search_fs.test.js +46 -0
- package/dist/src_json.d.ts +256 -0
- package/dist/src_json.js +110 -0
- package/dist/src_json.test.d.ts +1 -0
- package/dist/src_json.test.js +52 -0
- package/dist/sveltekit_config.d.ts +36 -0
- package/dist/sveltekit_config.js +51 -0
- package/dist/sveltekit_shim_app.d.ts +10 -0
- package/dist/sveltekit_shim_app.js +31 -0
- package/dist/sveltekit_shim_app_environment.d.ts +10 -0
- package/dist/sveltekit_shim_app_environment.js +12 -0
- package/dist/sveltekit_shim_app_forms.d.ts +5 -0
- package/dist/sveltekit_shim_app_forms.js +13 -0
- package/dist/sveltekit_shim_app_navigation.d.ts +10 -0
- package/dist/sveltekit_shim_app_navigation.js +11 -0
- package/dist/sveltekit_shim_app_paths.d.ts +11 -0
- package/dist/sveltekit_shim_app_paths.js +6 -0
- package/dist/sveltekit_shim_app_stores.d.ts +6 -0
- package/dist/sveltekit_shim_app_stores.js +17 -0
- package/dist/sveltekit_shim_env.d.ts +4 -0
- package/dist/sveltekit_shim_env.js +23 -0
- package/dist/sync.task.d.ts +30 -0
- package/dist/sync.task.js +45 -0
- package/dist/task.d.ts +29 -0
- package/dist/task.js +17 -0
- package/dist/task.test.d.ts +1 -0
- package/dist/task.test.js +22 -0
- package/dist/task_module.d.ts +14 -0
- package/dist/task_module.js +19 -0
- package/dist/task_module.test.d.ts +1 -0
- package/dist/task_module.test.js +70 -0
- package/dist/test.task.d.ts +20 -0
- package/dist/test.task.js +43 -0
- package/dist/throttle.d.ts +16 -0
- package/dist/throttle.js +59 -0
- package/dist/throttle.test.d.ts +1 -0
- package/dist/throttle.test.js +49 -0
- package/dist/typecheck.task.d.ts +5 -0
- package/dist/typecheck.task.js +38 -0
- package/dist/upgrade.task.d.ts +14 -0
- package/dist/upgrade.task.js +37 -0
- package/dist/watch_dir.d.ts +30 -0
- package/dist/watch_dir.js +59 -0
- package/package.json +422 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) Ryan Atkinson <mail@ryanatkn.com> (https://ryanatkn.com)
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
# gro <img src="static/favicon.png" width="32" height="32">
|
|
2
|
+
|
|
3
|
+
<img src="static/favicon.png" align="right" width="192" height="192">
|
|
4
|
+
|
|
5
|
+
> task runner and toolkit extending SvelteKit - [gro.ryanatkn.com](https://gro.ryanatkn.com)
|
|
6
|
+
|
|
7
|
+
[`npm i -D @ryanatkn/gro`](https://www.npmjs.com/package/@ryanatkn/gro)
|
|
8
|
+
|
|
9
|
+
limitations:
|
|
10
|
+
|
|
11
|
+
- Gro has been actively used since 2019 but it has few users,
|
|
12
|
+
so you'll likely encounter problems and undesirable limitations --
|
|
13
|
+
please open issues!
|
|
14
|
+
- [Windows won't be supported](https://github.com/ryanatkn/gro/issues/319)
|
|
15
|
+
|
|
16
|
+
## about
|
|
17
|
+
|
|
18
|
+
Gro is a task runner and toolkit
|
|
19
|
+
extending [SvelteKit](https://github.com/sveltejs/kit),
|
|
20
|
+
[Vite](https://github.com/vitejs/vite),
|
|
21
|
+
and [esbuild](https://github.com/evanw/esbuild)
|
|
22
|
+
for making web frontends, servers, and libraries with TypeScript.
|
|
23
|
+
It includes:
|
|
24
|
+
|
|
25
|
+
- [task runner](/src/lib/docs/task.md) that uses the filesystem convention `*.task.ts`
|
|
26
|
+
- lots of [common builtin tasks](/src/lib/docs/tasks.md) that users can easily override and compose
|
|
27
|
+
- tools and patterns for
|
|
28
|
+
[developing](/src/lib/docs/dev.md),
|
|
29
|
+
[building](/src/lib/docs/build.md),
|
|
30
|
+
[testing](/src/lib/docs/test.md),
|
|
31
|
+
[deploying](/src/lib/docs/deploy.md),
|
|
32
|
+
and [publishing](/src/lib/docs/publish.md)
|
|
33
|
+
[SvelteKit](https://github.com/sveltejs/kit) apps, library packages, and Node servers
|
|
34
|
+
- integrated [TypeScript](https://github.com/microsoft/typescript),
|
|
35
|
+
[Svelte](https://github.com/sveltejs/svelte),
|
|
36
|
+
and [SvelteKit](https://github.com/sveltejs/kit)
|
|
37
|
+
- defers to SvelteKit and Vite for the frontend and
|
|
38
|
+
[`@sveltejs/package`](https://kit.svelte.dev/docs/packaging) for the library
|
|
39
|
+
- uses [Changesets](https://github.com/changesets/changesets) for versioning and changelogs
|
|
40
|
+
- provides a [Node loader](/src/lib/loader.ts) and
|
|
41
|
+
[esbuild plugins for the server](/src/lib/gro_plugin_server.ts)
|
|
42
|
+
- supports importing TypeScript, JSON, and SSR'd Svelte files in tests and tasks
|
|
43
|
+
- supports [SvelteKit module imports](https://kit.svelte.dev/docs/modules) for
|
|
44
|
+
`$lib`, `$env`, and `$app` in tasks, tests, Node servers,
|
|
45
|
+
and other code outside of the SvelteKit frontend,
|
|
46
|
+
so you can use SvelteKit patterns everywhere
|
|
47
|
+
(these are best-effort shims, not perfect)
|
|
48
|
+
- supports running TypeScript files directly without a task via `gro run a.ts`
|
|
49
|
+
- [configurable plugins](/src/lib/docs/plugin.md)
|
|
50
|
+
to support SvelteKit, auto-restarting Node servers, and other external build processes
|
|
51
|
+
- see the [Gro config docs](/src/lib/docs/config.md) and
|
|
52
|
+
[the default config](https://github.com/ryanatkn/gro/blob/main/src/lib/gro.config.default.ts)
|
|
53
|
+
- see [`fuz_template`](https://github.com/fuz-dev/fuz_template)
|
|
54
|
+
for a simple starter project example, and
|
|
55
|
+
[`@feltjs/felt`](https://github.com/feltjs/felt) for a more complex example with custom tasks
|
|
56
|
+
- [testing](/src/lib/docs/test.md) with [`uvu`](https://github.com/lukeed/uvu)
|
|
57
|
+
- codegen by convention with [`gen`](/src/lib/docs/gen.md)
|
|
58
|
+
- linting with [ESLint](https://github.com/eslint/eslint)
|
|
59
|
+
(I also maintain [`@feltjs/eslint-config`](https://github.com/feltjs/eslint-config))
|
|
60
|
+
- formatting with [Prettier](https://github.com/prettier/prettier)
|
|
61
|
+
(it's not always pretty but it saves time writing and reading code,
|
|
62
|
+
my time is more precious than my formatting style)
|
|
63
|
+
|
|
64
|
+
## docs
|
|
65
|
+
|
|
66
|
+
- developing web frontends, servers, and libraries
|
|
67
|
+
- [config](/src/lib/docs/config.md)
|
|
68
|
+
- [dev](/src/lib/docs/dev.md)
|
|
69
|
+
- [build](/src/lib/docs/build.md) for production
|
|
70
|
+
- [deploy](/src/lib/docs/deploy.md) to a branch, like for GitHub pages
|
|
71
|
+
- [publish](/src/lib/docs/publish.md) to npm
|
|
72
|
+
- [`Task`](/src/lib/docs/task.md) runner
|
|
73
|
+
- builtin [tasks](/src/lib/docs/tasks.md) list
|
|
74
|
+
- [testing](/src/lib/docs/test.md) with [`uvu`](https://github.com/lukeed/uvu)
|
|
75
|
+
- [`gen`](/src/lib/docs/gen.md) code generation
|
|
76
|
+
- [`public` package](/src/lib/docs/package_json.md#public-packages) features (nonstandard)
|
|
77
|
+
- full [docs index](/src/lib/docs#readme)
|
|
78
|
+
|
|
79
|
+
## install
|
|
80
|
+
|
|
81
|
+
> depends on node >=20.10
|
|
82
|
+
|
|
83
|
+
Typical usage installs [@ryanatkn/gro](https://www.npmjs.com/package/@ryanatkn/gro)
|
|
84
|
+
as a dev dependency:
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
npm i -D @ryanatkn/gro
|
|
88
|
+
npx gro
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
It's handy to install globally too:
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
npm i -g @ryanatkn/gro
|
|
95
|
+
gro
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## usage
|
|
99
|
+
|
|
100
|
+
Gro has a task runner that discovers and runs TypeScript modules with the `.task.` subextension.
|
|
101
|
+
Running `gro` with no args prints the tasks
|
|
102
|
+
it finds in the current directory along with its builtin tasks:
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
gro # prints available tasks - defers to any local gro installation
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
```
|
|
109
|
+
Run a task: gro [name]
|
|
110
|
+
View help: gro [name] --help
|
|
111
|
+
|
|
112
|
+
17 tasks in gro:
|
|
113
|
+
|
|
114
|
+
build build the project
|
|
115
|
+
changeset call changeset with gro patterns
|
|
116
|
+
check check that everything is ready to commit
|
|
117
|
+
clean remove temporary dev and build files, and optionally prune git branches
|
|
118
|
+
commit commit and push to a new branch
|
|
119
|
+
deploy deploy to a branch
|
|
120
|
+
dev start SvelteKit and other dev plugins
|
|
121
|
+
exports write the "exports" property of package.json and copy the file to .well-known
|
|
122
|
+
format format source files
|
|
123
|
+
gen run code generation scripts
|
|
124
|
+
lint run eslint
|
|
125
|
+
publish bump version, publish to npm, and git push
|
|
126
|
+
release publish and deploy
|
|
127
|
+
sync run `gro gen`, `gro exports`, and optionally `npm i` to sync up
|
|
128
|
+
test run tests with uvu
|
|
129
|
+
typecheck run tsc on the project without emitting any files
|
|
130
|
+
upgrade upgrade deps
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Gro matches your CLI input against its filesystem conventions.
|
|
134
|
+
It tries to do the right thing, where right is helpful but not surprising,
|
|
135
|
+
with some magic but not too much:
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
gro # print all available tasks, those matching `src/lib/**/*.task.ts` and Gro's builtins
|
|
139
|
+
gro some/dir # list all tasks inside `src/lib/some/dir`
|
|
140
|
+
gro some/file # run `src/lib/some/file.task.ts`
|
|
141
|
+
gro some/file.task.ts # same as above
|
|
142
|
+
gro a # run `src/lib/a.task.ts` if it exists, falling back to Gro's builtin
|
|
143
|
+
gro a --help # print info about the "a" task; works for every task
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Gro has a number of builtin tasks that you can run with the CLI.
|
|
147
|
+
To learn more [see the task docs](/src/lib/docs/task.md)
|
|
148
|
+
and [the generated task index](/src/lib/docs/tasks.md).
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
gro dev # start developing in watch mode
|
|
152
|
+
gro dev -- vite --port 3003 # forward args by separating sections with --
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
gro build # build everything for production
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
[Testing](/src/lib/docs/test.md) with [`uvu`](https://github.com/lukeed/uvu),
|
|
160
|
+
including shims for [SvelteKit modules](https://kit.svelte.dev/docs/modules):
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
gro test # run all tests for `*.test.ts` files with `uvu`
|
|
164
|
+
gro test filepattern1 some.test another.test
|
|
165
|
+
gro test -- uvu --forwarded_args 'to uvu'
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Check all the things:
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
gro check # does all of the following:
|
|
172
|
+
gro typecheck # typecheck JS/TypeScript and Svelte
|
|
173
|
+
gro test # run tests
|
|
174
|
+
gro gen --check # ensure generated files are current
|
|
175
|
+
gro format --check # ensure everything is formatted
|
|
176
|
+
gro lint # eslint
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
For a usage example see [the `check.yml` CI config](.github/workflows/check.yml).
|
|
180
|
+
|
|
181
|
+
Formatting with [`prettier`](https://github.com/prettier/prettier):
|
|
182
|
+
|
|
183
|
+
```bash
|
|
184
|
+
gro format # format all of the source files using Prettier
|
|
185
|
+
gro format --check # check that all source files are formatted
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
Codegen with [`gen`](/src/lib/docs/gen.md):
|
|
189
|
+
|
|
190
|
+
```bash
|
|
191
|
+
gro gen # run codegen for all `*.gen.*` files
|
|
192
|
+
gro gen --check # error if any generated files are new or different
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
To deploy: (also see [`src/lib/docs/deploy.md`](/src/lib/docs/deploy.md))
|
|
196
|
+
|
|
197
|
+
```bash
|
|
198
|
+
gro deploy # build and push to the `deploy` branch
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
To publish: (also see [`src/lib/docs/publish.md`](/src/lib/docs/publish.md))
|
|
202
|
+
|
|
203
|
+
```bash
|
|
204
|
+
gro publish # flush changeset to changelog, bump version, publish to npm, and git push
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
Etc:
|
|
208
|
+
|
|
209
|
+
```bash
|
|
210
|
+
gro clean # delete all build artifacts from the filesystem
|
|
211
|
+
gro clean --sveltekit --nodemodules --git # also deletes dirs and prunes git branches
|
|
212
|
+
gro upgrade excluded-dep-1 excluded-dep-2 # npm updates to the latest everything
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
```bash
|
|
216
|
+
gro --version # print the Gro version
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
For more see [`src/lib/docs/task.md`](/src/lib/docs/task.md) and [`src/lib/docs`](/src/lib/docs).
|
|
220
|
+
|
|
221
|
+
## develop
|
|
222
|
+
|
|
223
|
+
```bash
|
|
224
|
+
npm i
|
|
225
|
+
npm run build # build and link `gro` - needed only once
|
|
226
|
+
gro build # same as `npm run build` when the `gro` CLI is available
|
|
227
|
+
gro test # make sure everything looks good - same as `npm test`
|
|
228
|
+
gro test some.test another.test
|
|
229
|
+
|
|
230
|
+
# use your development version of `gro` locally in another project:
|
|
231
|
+
gro build # updates the `gro` CLI, same as `npm run build`
|
|
232
|
+
cd ../otherproject
|
|
233
|
+
npm link ../gro # from `otherproject/`
|
|
234
|
+
gro build # from `../gro` on changes
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
## credits 🐢<sub>🐢</sub><sub><sub>🐢</sub></sub>
|
|
238
|
+
|
|
239
|
+
Gro builds on
|
|
240
|
+
[TypeScript](https://github.com/microsoft/TypeScript) ∙
|
|
241
|
+
[Svelte](https://github.com/sveltejs/svelte) ∙
|
|
242
|
+
[SvelteKit](https://github.com/sveltejs/kit) ∙
|
|
243
|
+
[Vite](https://github.com/vitejs/vite) ∙
|
|
244
|
+
[esbuild](https://github.com/evanw/esbuild) ∙
|
|
245
|
+
[uvu](https://github.com/lukeed/uvu) ∙
|
|
246
|
+
[mri](https://github.com/lukeed/mri) ∙
|
|
247
|
+
[chokidar](https://github.com/paulmillr/chokidar) ∙
|
|
248
|
+
[zod](https://github.com/colinhacks/zod) ∙
|
|
249
|
+
[@ryanatkn/belt](https://github.com/ryanatkn/belt) ∙
|
|
250
|
+
[ESLint](https://github.com/eslint/eslint) ∙
|
|
251
|
+
[Prettier](https://github.com/prettier/prettier) ∙
|
|
252
|
+
[svelte-check](https://github.com/sveltejs/language-tools/tree/master/packages/svelte-check) &
|
|
253
|
+
[more](package.json)
|
|
254
|
+
|
|
255
|
+
## license [🐦](https://wikipedia.org/wiki/Free_and_open-source_software)
|
|
256
|
+
|
|
257
|
+
[MIT](LICENSE)
|
package/dist/args.d.ts
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { z } from 'zod';
|
|
2
|
+
/**
|
|
3
|
+
* These extend the CLI args for tasks.
|
|
4
|
+
* Anything can be assigned to a task's `args`. It's just a mutable POJO dictionary.
|
|
5
|
+
* Downstream tasks will see args that upstream events mutate,
|
|
6
|
+
* unless `invoke_task` is called with modified args.
|
|
7
|
+
* Upstream tasks can use listeners to respond to downstream events and values.
|
|
8
|
+
* It's a beautiful mutable spaghetti mess. cant get enough
|
|
9
|
+
* The raw CLI ares are handled by `mri` - https://github.com/lukeed/mri
|
|
10
|
+
*/
|
|
11
|
+
export interface Args {
|
|
12
|
+
_?: string[];
|
|
13
|
+
help?: boolean;
|
|
14
|
+
[key: string]: Arg_Value;
|
|
15
|
+
}
|
|
16
|
+
export type Arg_Value = string | number | boolean | undefined | Array<string | number | boolean>;
|
|
17
|
+
export interface Arg_Schema {
|
|
18
|
+
type: string;
|
|
19
|
+
default: Arg_Value;
|
|
20
|
+
description: string;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Parses user input args with a Zod schema.
|
|
24
|
+
* Sets the correct source of truth for `no-` versions of args,
|
|
25
|
+
* to the opposite of the unprefixed versions when not included in `unparsed_args`.
|
|
26
|
+
* This is needed because CLI args don't have a normal way of setting falsy values,
|
|
27
|
+
* so instead the args parser `mri` will pass through the truthy versions of args
|
|
28
|
+
* without the `no-` prefix.
|
|
29
|
+
* When we declare task args schemas,
|
|
30
|
+
* we need include both versions with their defaults to get correct `--help` output.
|
|
31
|
+
* Parsing like this also ensures data consistency for both versions because `mri` only creates one.
|
|
32
|
+
* A simpler implementation could replace `mri`, but it handles some finicky details well.
|
|
33
|
+
*/
|
|
34
|
+
export declare const parse_args: <TOutput extends Record<string, Arg_Value> = Args, TInput extends Record<string, Arg_Value> = Args>(unparsed_args: TInput, schema: z.ZodType<TOutput, z.ZodTypeDef, TInput>) => z.SafeParseReturnType<TInput, TOutput>;
|
|
35
|
+
/**
|
|
36
|
+
* Serializes parsed `Args` for CLI commands.
|
|
37
|
+
*/
|
|
38
|
+
export declare const serialize_args: (args: Args) => string[];
|
|
39
|
+
/**
|
|
40
|
+
* Parses `task_name` and `args` from `process.argv` using `mri`,
|
|
41
|
+
* ignoring anything after any `--`.
|
|
42
|
+
*/
|
|
43
|
+
export declare const to_task_args: (argv?: string[]) => {
|
|
44
|
+
task_name: string;
|
|
45
|
+
args: Args;
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* Gets the array of raw string args starting with the first `--`, if any.
|
|
49
|
+
*/
|
|
50
|
+
export declare const to_raw_rest_args: (argv?: string[]) => string[];
|
|
51
|
+
/**
|
|
52
|
+
* Parses `process.argv` for the specified `command`, so given
|
|
53
|
+
* `gro taskname arg1 --arg2 -- eslint eslintarg1 --eslintarg2 -- tsc --tscarg1 --tscarg2`
|
|
54
|
+
* the `command` `'eslint'` returns `eslintarg1 --eslintarg2`
|
|
55
|
+
* and `'tsc'` returns `--tscarg1` and `--tscarg2`.
|
|
56
|
+
*/
|
|
57
|
+
export declare const to_forwarded_args: (command: string, raw_rest_args?: string[], cache?: Record<string, Args>) => Args;
|
|
58
|
+
export declare const to_forwarded_args_by_command: (raw_rest_args?: string[]) => Record<string, Args>;
|
|
59
|
+
export declare const print_command_args: (serialized_args: string[]) => string;
|
package/dist/args.js
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { magenta } from 'kleur/colors';
|
|
2
|
+
import mri from 'mri';
|
|
3
|
+
/**
|
|
4
|
+
* Parses user input args with a Zod schema.
|
|
5
|
+
* Sets the correct source of truth for `no-` versions of args,
|
|
6
|
+
* to the opposite of the unprefixed versions when not included in `unparsed_args`.
|
|
7
|
+
* This is needed because CLI args don't have a normal way of setting falsy values,
|
|
8
|
+
* so instead the args parser `mri` will pass through the truthy versions of args
|
|
9
|
+
* without the `no-` prefix.
|
|
10
|
+
* When we declare task args schemas,
|
|
11
|
+
* we need include both versions with their defaults to get correct `--help` output.
|
|
12
|
+
* Parsing like this also ensures data consistency for both versions because `mri` only creates one.
|
|
13
|
+
* A simpler implementation could replace `mri`, but it handles some finicky details well.
|
|
14
|
+
*/
|
|
15
|
+
export const parse_args = (unparsed_args, schema) => {
|
|
16
|
+
const parsed = schema.safeParse(unparsed_args);
|
|
17
|
+
if (parsed.success) {
|
|
18
|
+
// mutate `data` with the correct source of truth for `no-` prefixed args
|
|
19
|
+
const { data } = parsed;
|
|
20
|
+
for (const key in parsed.data) {
|
|
21
|
+
if (key.startsWith('no-')) {
|
|
22
|
+
const base_key = key.substring(3);
|
|
23
|
+
if (!(key in unparsed_args)) {
|
|
24
|
+
data[key] = !data[base_key];
|
|
25
|
+
}
|
|
26
|
+
else if (!(base_key in unparsed_args)) {
|
|
27
|
+
data[base_key] = !data[key];
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return parsed;
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* Serializes parsed `Args` for CLI commands.
|
|
36
|
+
*/
|
|
37
|
+
export const serialize_args = (args) => {
|
|
38
|
+
const result = [];
|
|
39
|
+
const add_value = (name, value) => {
|
|
40
|
+
if (value === undefined)
|
|
41
|
+
return;
|
|
42
|
+
result.push(name);
|
|
43
|
+
if (typeof value !== 'boolean') {
|
|
44
|
+
result.push(value + '');
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
let _ = null;
|
|
48
|
+
for (const [key, value] of Object.entries(args)) {
|
|
49
|
+
if (key === '_') {
|
|
50
|
+
_ = value ? value.map((v) => (v === undefined ? '' : v + '')) : [];
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
const name = `${key.length === 1 ? '-' : '--'}${key}`;
|
|
54
|
+
if (Array.isArray(value)) {
|
|
55
|
+
for (const v of value)
|
|
56
|
+
add_value(name, v);
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
add_value(name, value);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return _ ? [..._, ...result] : result;
|
|
64
|
+
};
|
|
65
|
+
/**
|
|
66
|
+
* Parses `task_name` and `args` from `process.argv` using `mri`,
|
|
67
|
+
* ignoring anything after any `--`.
|
|
68
|
+
*/
|
|
69
|
+
export const to_task_args = (argv = process.argv) => {
|
|
70
|
+
const forwarded_index = argv.indexOf('--');
|
|
71
|
+
const args = mri(forwarded_index === -1 ? argv.slice(2) : argv.slice(2, forwarded_index));
|
|
72
|
+
const task_name = args._.shift() || '';
|
|
73
|
+
if (!args._.length)
|
|
74
|
+
delete args._; // enable schema defaults
|
|
75
|
+
return { task_name, args };
|
|
76
|
+
};
|
|
77
|
+
/**
|
|
78
|
+
* Gets the array of raw string args starting with the first `--`, if any.
|
|
79
|
+
*/
|
|
80
|
+
export const to_raw_rest_args = (argv = process.argv) => {
|
|
81
|
+
const forwarded_index = argv.indexOf('--');
|
|
82
|
+
return forwarded_index === -1 ? [] : argv.slice(forwarded_index);
|
|
83
|
+
};
|
|
84
|
+
/**
|
|
85
|
+
* Parses `process.argv` for the specified `command`, so given
|
|
86
|
+
* `gro taskname arg1 --arg2 -- eslint eslintarg1 --eslintarg2 -- tsc --tscarg1 --tscarg2`
|
|
87
|
+
* the `command` `'eslint'` returns `eslintarg1 --eslintarg2`
|
|
88
|
+
* and `'tsc'` returns `--tscarg1` and `--tscarg2`.
|
|
89
|
+
*/
|
|
90
|
+
export const to_forwarded_args = (command, raw_rest_args, cache = to_forwarded_args_by_command(raw_rest_args)) => cache[command] || {};
|
|
91
|
+
export const to_forwarded_args_by_command = (raw_rest_args = to_raw_rest_args()) => {
|
|
92
|
+
// Parse each segment of `argv` separated by `--`.
|
|
93
|
+
const argvs = [];
|
|
94
|
+
let arr;
|
|
95
|
+
for (const arg of raw_rest_args) {
|
|
96
|
+
if (arg === '--') {
|
|
97
|
+
if (arr?.length)
|
|
98
|
+
argvs.push(arr);
|
|
99
|
+
arr = [];
|
|
100
|
+
}
|
|
101
|
+
else if (!arr) {
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
else if (arg) {
|
|
105
|
+
arr.push(arg);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
if (arr?.length)
|
|
109
|
+
argvs.push(arr);
|
|
110
|
+
// Add each segment of parsed `argv` keyed by the first rest arg,
|
|
111
|
+
// which is assumed to be the CLI command that gets forwarded the args.
|
|
112
|
+
const forwarded_args_by_command = {};
|
|
113
|
+
for (const argv of argvs) {
|
|
114
|
+
const args = mri(argv);
|
|
115
|
+
let command = args._.shift();
|
|
116
|
+
if (!command) {
|
|
117
|
+
throw Error(`Malformed args following a \`--\`. Expected a rest arg command: \`${argv.join(' ')}\``);
|
|
118
|
+
}
|
|
119
|
+
// Gro commands get combined with their task name.
|
|
120
|
+
if (command === 'gro') {
|
|
121
|
+
if (!args._.length) {
|
|
122
|
+
throw Error(`Malformed args following a \`--\`. Expected gro taskname: \`${argv.join(' ')}\``);
|
|
123
|
+
}
|
|
124
|
+
command += ' ' + args._.shift();
|
|
125
|
+
}
|
|
126
|
+
if (!args._.length)
|
|
127
|
+
delete args._;
|
|
128
|
+
forwarded_args_by_command[command] = args;
|
|
129
|
+
}
|
|
130
|
+
return forwarded_args_by_command;
|
|
131
|
+
};
|
|
132
|
+
export const print_command_args = (serialized_args) => magenta('running command: ') + serialized_args.join(' ');
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import mri from 'mri';
|
|
2
|
+
import { suite } from 'uvu';
|
|
3
|
+
import * as assert from 'uvu/assert';
|
|
4
|
+
import { serialize_args, to_forwarded_args, to_forwarded_args_by_command, to_raw_rest_args, } from './args.js';
|
|
5
|
+
/* test__serialize_args */
|
|
6
|
+
const test__serialize_args = suite('serialize_args');
|
|
7
|
+
test__serialize_args('basic behavior', () => {
|
|
8
|
+
const raw = ['a', '-i', '1', 'b', 'c', '-i', '-i', 'three'];
|
|
9
|
+
const parsed = mri(raw);
|
|
10
|
+
assert.equal(parsed, { _: ['a', 'b', 'c'], i: [1, true, 'three'] });
|
|
11
|
+
const serialized = serialize_args(parsed);
|
|
12
|
+
assert.equal(serialized, ['a', 'b', 'c', '-i', '1', '-i', '-i', 'three']); // sorted
|
|
13
|
+
});
|
|
14
|
+
test__serialize_args.run();
|
|
15
|
+
/* test__serialize_args */
|
|
16
|
+
/* test__to_forwarded_args_by_command */
|
|
17
|
+
const test__to_forwarded_args_by_command = suite('to_forwarded_args_by_command');
|
|
18
|
+
test__to_forwarded_args_by_command('basic behavior', () => {
|
|
19
|
+
const raw_rest_args = to_raw_rest_args(('gro taskname a b c --d -e 1 -- -- ' +
|
|
20
|
+
'eslint a --b c -- ' +
|
|
21
|
+
'gro a --a -- ' +
|
|
22
|
+
'tsc -b -- ' +
|
|
23
|
+
'gro b -t2 t2a --t2 t2b --t222 2 -- -- -- ' +
|
|
24
|
+
'groc --m --n nn -- ' +
|
|
25
|
+
'gro d -b a --c 4 -- ' +
|
|
26
|
+
'gro d -b a --c 5 -- ').split(' '));
|
|
27
|
+
assert.equal(to_forwarded_args_by_command(raw_rest_args), {
|
|
28
|
+
eslint: { _: ['a'], b: 'c' },
|
|
29
|
+
'gro a': { a: true },
|
|
30
|
+
tsc: { b: true },
|
|
31
|
+
'gro b': { '2': 't2a', t: true, t2: 't2b', t222: 2 },
|
|
32
|
+
groc: { m: true, n: 'nn' },
|
|
33
|
+
'gro d': { b: 'a', c: 5 },
|
|
34
|
+
});
|
|
35
|
+
assert.equal(to_forwarded_args('gro b', raw_rest_args), {
|
|
36
|
+
'2': 't2a',
|
|
37
|
+
t: true,
|
|
38
|
+
t2: 't2b',
|
|
39
|
+
t222: 2,
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
test__to_forwarded_args_by_command.run();
|
|
43
|
+
/* test__to_forwarded_args_by_command */
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { Task } from './task.js';
|
|
3
|
+
export declare const Args: z.ZodObject<{
|
|
4
|
+
install: z.ZodDefault<z.ZodBoolean>;
|
|
5
|
+
}, "strict", z.ZodTypeAny, {
|
|
6
|
+
install: boolean;
|
|
7
|
+
}, {
|
|
8
|
+
install?: boolean | undefined;
|
|
9
|
+
}>;
|
|
10
|
+
export type Args = z.infer<typeof Args>;
|
|
11
|
+
export declare const task: Task<Args>;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { Plugins } from './plugin.js';
|
|
3
|
+
import { clean_fs } from './clean_fs.js';
|
|
4
|
+
export const Args = z
|
|
5
|
+
.object({
|
|
6
|
+
install: z.boolean({ description: 'run npm install before building' }).default(false),
|
|
7
|
+
})
|
|
8
|
+
.strict();
|
|
9
|
+
export const task = {
|
|
10
|
+
summary: 'build the project',
|
|
11
|
+
Args,
|
|
12
|
+
run: async (ctx) => {
|
|
13
|
+
const { args, invoke_task } = ctx;
|
|
14
|
+
const { install } = args;
|
|
15
|
+
await invoke_task('sync', { install });
|
|
16
|
+
// TODO possibly detect if the git workspace is clean, and ask for confirmation if not,
|
|
17
|
+
// because we're not doing things like `gro gen` here because that's a dev/CI concern
|
|
18
|
+
await clean_fs({ build_dist: true });
|
|
19
|
+
const plugins = await Plugins.create({ ...ctx, dev: false, watch: false });
|
|
20
|
+
await plugins.setup();
|
|
21
|
+
await plugins.adapt();
|
|
22
|
+
await plugins.teardown();
|
|
23
|
+
},
|
|
24
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Updates a changelog produced by `@changesets/changelog-git` with better links and formatting.
|
|
3
|
+
* It's similar to `@changesets/changelog-github` but doesn't require a token for light usage.
|
|
4
|
+
* This may be better implemented as a standalone dependency
|
|
5
|
+
* as an alternative to `@changesets/changelog-git`.
|
|
6
|
+
* @returns boolean indicating if the changelog changed
|
|
7
|
+
*/
|
|
8
|
+
export declare const update_changelog: (owner: string, repo: string, path?: string, token?: string, log?: any, cache?: Fetch_Value_Cache) => Promise<boolean>;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { readFile, writeFile } from 'fs/promises';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { github_fetch_commit_prs } from './github.js';
|
|
4
|
+
/**
|
|
5
|
+
* Updates a changelog produced by `@changesets/changelog-git` with better links and formatting.
|
|
6
|
+
* It's similar to `@changesets/changelog-github` but doesn't require a token for light usage.
|
|
7
|
+
* This may be better implemented as a standalone dependency
|
|
8
|
+
* as an alternative to `@changesets/changelog-git`.
|
|
9
|
+
* @returns boolean indicating if the changelog changed
|
|
10
|
+
*/
|
|
11
|
+
export const update_changelog = async (owner, repo, path = 'CHANGELOG.md', token, log, cache = new Map()) => {
|
|
12
|
+
const contents = await readFile(path, 'utf8');
|
|
13
|
+
const parsed = parse_changelog(contents);
|
|
14
|
+
const mapped = await map_changelog(parsed, owner, repo, token, log, cache);
|
|
15
|
+
const updated = serialize_changelog(mapped);
|
|
16
|
+
if (contents === updated) {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
await writeFile(path, updated, 'utf8');
|
|
20
|
+
return true;
|
|
21
|
+
};
|
|
22
|
+
// keeping this really simple for now, no need to parse further for our current usecases
|
|
23
|
+
const Parsed_Changelog = z.array(z.string());
|
|
24
|
+
const parse_changelog = (contents) => contents.split('\n');
|
|
25
|
+
const serialize_changelog = (parsed) => parsed.join('\n');
|
|
26
|
+
const LINE_WITH_SHA_MATCHER = /^- ([a-z0-9]{7,8}): /u;
|
|
27
|
+
const map_changelog = async (parsed, owner, repo, token, log, cache) => {
|
|
28
|
+
const mapped = [];
|
|
29
|
+
for (const line of parsed) {
|
|
30
|
+
const matches = LINE_WITH_SHA_MATCHER.exec(line);
|
|
31
|
+
if (matches) {
|
|
32
|
+
const commit_sha = matches[1];
|
|
33
|
+
const l = '- ' + line.substring(commit_sha.length + 4);
|
|
34
|
+
const prs = await github_fetch_commit_prs(owner, repo, commit_sha, token, log, cache); // eslint-disable-line no-await-in-loop
|
|
35
|
+
if (prs?.length) {
|
|
36
|
+
mapped.push(`${l} (${prs.map((p) => `[#${p.number}](${p.html_url})`).join(', ')})`);
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
mapped.push(`${l} ([${commit_sha}](https://github.com/${owner}/${repo}/commit/${commit_sha}))`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
mapped.push(line);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return mapped;
|
|
47
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|