├── .envrc
├── .github
└── workflows
│ └── flakehub-publish-tagged.yml
├── .gitignore
├── LICENSE
├── README.md
├── flake.lock
├── flake.nix
├── packages
└── thaw
│ ├── default.nix
│ └── src
│ ├── help.ts
│ ├── index.ts
│ ├── util
│ ├── args.ts
│ ├── edit.ts
│ ├── forge.ts
│ ├── forges
│ │ └── github.ts
│ ├── log.ts
│ ├── merge.ts
│ ├── nix.ts
│ └── todo.ts
│ └── vendor
│ ├── arg.cjs
│ ├── kleur.cjs
│ ├── littlelog.cjs
│ └── sleet.mjs
└── shells
└── default
└── default.nix
/.envrc:
--------------------------------------------------------------------------------
1 | use flake
2 |
--------------------------------------------------------------------------------
/.github/workflows/flakehub-publish-tagged.yml:
--------------------------------------------------------------------------------
1 | name: "Publish tags to FlakeHub"
2 | on:
3 | push:
4 | tags:
5 | - "v?[0-9]+.[0-9]+.[0-9]+*"
6 | workflow_dispatch:
7 | inputs:
8 | tag:
9 | description: "The existing tag to publish to FlakeHub"
10 | type: "string"
11 | required: true
12 | jobs:
13 | flakehub-publish:
14 | runs-on: "ubuntu-latest"
15 | permissions:
16 | id-token: "write"
17 | contents: "read"
18 | steps:
19 | - uses: "actions/checkout@v3"
20 | with:
21 | ref: "${{ (inputs.tag != null) && format('refs/tags/{0}', inputs.tag) || '' }}"
22 | - uses: "DeterminateSystems/nix-installer-action@main"
23 | - uses: "DeterminateSystems/flakehub-push@main"
24 | with:
25 | visibility: "public"
26 | name: "snowfallorg/thaw"
27 | tag: "${{ inputs.tag }}"
28 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Nix
2 | result
3 | .direnv
4 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 |
3 | Version 2.0, January 2004
4 |
5 | http://www.apache.org/licenses/ TERMS
6 | AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction, and
11 | distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by the copyright
14 | owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the
17 | union of the acting entity and all other entities that control, are controlled
18 | by, or are under common control with that entity. For the purposes of this
19 | definition, "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or otherwise, or (ii)
21 | ownership of fifty percent (50%) or more of the outstanding shares, or (iii)
22 | beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean
25 | an individual or Legal Entity exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation source, and
29 | configuration files.
30 |
31 | "Object" form shall mean any form resulting
32 | from mechanical transformation or translation of a Source form, including but not
33 | limited to compiled object code, generated documentation, and conversions to
34 | other media types.
35 |
36 | "Work" shall mean the work of authorship,
37 | whether in Source or Object form, made available under the License, as indicated
38 | by a copyright notice that is included in or attached to the work (an example is
39 | provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any
42 | work, whether in Source or Object form, that is based on (or derived from) the
43 | Work and for which the editorial revisions, annotations, elaborations, or other
44 | modifications represent, as a whole, an original work of authorship. For the
45 | purposes of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of, the Work
47 | and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work
50 | of authorship, including the original version of the Work and any modifications
51 | or additions to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner or by an
53 | individual or Legal Entity authorized to submit on behalf of the copyright owner.
54 | For the purposes of this definition, "submitted" means any form of electronic,
55 | verbal, or written communication sent to the Licensor or its representatives,
56 | including but not limited to communication on electronic mailing lists, source
57 | code control systems, and issue tracking systems that are managed by, or on
58 | behalf of, the Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise designated in
60 | writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of
63 | whom a Contribution has been received by Licensor and subsequently incorporated
64 | within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and
67 | conditions of this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license
69 | to reproduce, prepare Derivative Works of, publicly display, publicly perform,
70 | sublicense, and distribute the Work and such Derivative Works in Source or Object
71 | form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of this
74 | License, each Contributor hereby grants to You a perpetual, worldwide,
75 | non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this
76 | section) patent license to make, have made, use, offer to sell, sell, import, and
77 | otherwise transfer the Work, where such license applies only to those patent
78 | claims licensable by such Contributor that are necessarily infringed by their
79 | Contribution(s) alone or by combination of their Contribution(s) with the Work to
80 | which such Contribution(s) was submitted. If You institute patent litigation
81 | against any entity (including a cross-claim or counterclaim in a lawsuit)
82 | alleging that the Work or a Contribution incorporated within the Work constitutes
83 | direct or contributory patent infringement, then any patent licenses granted to
84 | You under this License for that Work shall terminate as of the date such
85 | litigation is filed.
86 |
87 | 4. Redistribution. You may reproduce and distribute
88 | copies of the Work or Derivative Works thereof in any medium, with or without
89 | modifications, and in Source or Object form, provided that You meet the following
90 | conditions:
91 |
92 | (a) You must give any other recipients of the Work or
93 | Derivative Works a copy of this License; and
94 |
95 | (b) You must cause any
96 | modified files to carry prominent notices stating that You changed the files;
97 | and
98 |
99 | (c) You must retain, in the Source form of any Derivative Works that
100 | You distribute, all copyright, patent, trademark, and attribution notices from
101 | the Source form of the Work, excluding those notices that do not pertain to any
102 | part of the Derivative Works; and
103 |
104 | (d) If the Work includes a "NOTICE" text
105 | file as part of its distribution, then any Derivative Works that You distribute
106 | must include a readable copy of the attribution notices contained within such
107 | NOTICE file, excluding those notices that do not pertain to any part of the
108 | Derivative Works, in at least one of the following places: within a NOTICE text
109 | file distributed as part of the Derivative Works; within the Source form or
110 | documentation, if provided along with the Derivative Works; or, within a display
111 | generated by the Derivative Works, if and wherever such third-party notices
112 | normally appear. The contents of the NOTICE file are for informational purposes
113 | only and do not modify the License. You may add Your own attribution notices
114 | within Derivative Works that You distribute, alongside or as an addendum to the
115 | NOTICE text from the Work, provided that such additional attribution notices
116 | cannot be construed as modifying the License.
117 |
118 | You may add Your own
119 | copyright statement to Your modifications and may provide additional or different
120 | license terms and conditions for use, reproduction, or distribution of Your
121 | modifications, or for any such Derivative Works as a whole, provided Your use,
122 | reproduction, and distribution of the Work otherwise complies with the conditions
123 | stated in this License.
124 |
125 | 5. Submission of Contributions. Unless You explicitly
126 | state otherwise, any Contribution intentionally submitted for inclusion in the
127 | Work by You to the Licensor shall be under the terms and conditions of this
128 | License, without any additional terms or conditions. Notwithstanding the above,
129 | nothing herein shall supersede or modify the terms of any separate license
130 | agreement you may have executed with Licensor regarding such Contributions.
131 |
132 | 6. Trademarks. This License does not grant permission to use the trade names,
133 | trademarks, service marks, or product names of the Licensor, except as required
134 | for reasonable and customary use in describing the origin of the Work and
135 | reproducing the content of the NOTICE file.
136 |
137 | 7. Disclaimer of Warranty. Unless
138 | required by applicable law or agreed to in writing, Licensor provides the Work
139 | (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT
140 | WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including,
141 | without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT,
142 | MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible
143 | for determining the appropriateness of using or redistributing the Work and
144 | assume any risks associated with Your exercise of permissions under this
145 | License.
146 |
147 | 8. Limitation of Liability. In no event and under no legal theory,
148 | whether in tort (including negligence), contract, or otherwise, unless required
149 | by applicable law (such as deliberate and grossly negligent acts) or agreed to in
150 | writing, shall any Contributor be liable to You for damages, including any
151 | direct, indirect, special, incidental, or consequential damages of any character
152 | arising as a result of this License or out of the use or inability to use the
153 | Work (including but not limited to damages for loss of goodwill, work stoppage,
154 | computer failure or malfunction, or any and all other commercial damages or
155 | losses), even if such Contributor has been advised of the possibility of such
156 | damages.
157 |
158 | 9. Accepting Warranty or Additional Liability. While redistributing
159 | the Work or Derivative Works thereof, You may choose to offer, and charge a fee
160 | for, acceptance of support, warranty, indemnity, or other liability obligations
161 | and/or rights consistent with this License. However, in accepting such
162 | obligations, You may act only on Your own behalf and on Your sole responsibility,
163 | not on behalf of any other Contributor, and only if You agree to indemnify,
164 | defend, and hold each Contributor harmless for any liability incurred by, or
165 | claims asserted against, such Contributor by reason of your accepting any such
166 | warranty or additional liability. END OF TERMS AND CONDITIONS
167 |
168 | APPENDIX: How to apply the Apache License to your work.
169 |
170 | To apply the Apache License to your work,
171 | attach the following boilerplate notice, with the fields enclosed by brackets
172 | "[]" replaced with your own identifying information. (Don't include the
173 | brackets!) The text should be enclosed in the appropriate comment syntax for the
174 | file format. We also recommend that a file or class name and description of
175 | purpose be included on the same "printed page" as the copyright notice for easier
176 | identification within third-party archives.
177 |
178 | Copyright 2023 Jake Hamilton
179 |
180 | Licensed under the Apache License, Version 2.0 (the "License");
181 | you may not use this file except in compliance with the License.
182 |
183 | You may obtain a copy of the License at
184 |
185 | http://www.apache.org/licenses/LICENSE-2.0
186 |
187 | Unless required by applicable law or agreed to in writing, software
188 | distributed under the License is distributed on an "AS IS" BASIS,
189 |
190 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
191 |
192 | See the License for the specific language governing permissions and
193 | limitations under the License.
194 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Snowfall Thaw
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | 15 | 16 |
17 | 18 | > Semantic Versioning for Nix Flakes. 19 | 20 | ## Features 21 | 22 | Thaw works by operating directly on the `ref` specified for your flake's inputs. If a ref exists 23 | on the input and is a valid SemVer version, thaw will attempt to upgrade it to the latest version. 24 | Both url querey param and object configuration syntax are supported. 25 | 26 | By default, non-major upgrades are allowed, but this can be changed with the `--major` flag. No 27 | additional tools or components are required. 28 | 29 | Supported flake input types: 30 | 31 | - [x] GitHub 32 | - [ ] GitLab 33 | - [ ] Gitea 34 | - [ ] SourceHut 35 | 36 | Need one of these supported? Open an issue or submit a pull request! 37 | 38 | ## Installation 39 | 40 | ### Nix Profile 41 | 42 | You can install this package imperatively with the following command. 43 | 44 | ```bash 45 | nix profile install github:snowfallorg/thaw 46 | ``` 47 | 48 | ### Nix Configuration 49 | 50 | You can install this package by adding it as an input to your Nix Flake. 51 | 52 | ```nix 53 | { 54 | description = "My system thaw"; 55 | 56 | inputs = { 57 | nixpkgs.url = "github:nixos/nixpkgs/nixos-23.05"; 58 | 59 | # Snowfall Lib is not required, but will make configuration easier for you. 60 | snowfall-lib = { 61 | url = "github:snowfallorg/lib"; 62 | inputs.nixpkgs.follows = "nixpkgs"; 63 | }; 64 | 65 | snowfall-thaw = { 66 | url = "github:snowfallorg/thaw"; 67 | inputs.nixpkgs.follows = "nixpkgs"; 68 | }; 69 | }; 70 | 71 | outputs = inputs: 72 | inputs.snowfall-lib.mkFlake { 73 | inherit inputs; 74 | src = ./.; 75 | 76 | overlays = with inputs; [ 77 | # Use the overlay provided by this thaw. 78 | snowfall-thaw.overlays.default 79 | 80 | # There is also a named overlay, though the output is the same. 81 | snowfall-thaw.overlays."package/thaw" 82 | ]; 83 | }; 84 | } 85 | ``` 86 | 87 | If you've added the overlay from this flake, then in your system configuration you 88 | can add the `snowfallorg.thaw` package. 89 | 90 | ```nix 91 | { pkgs }: 92 | 93 | { 94 | environment.systemPackages = with pkgs; [ 95 | snowfallorg.thaw 96 | ]; 97 | } 98 | ``` 99 | 100 | ## Usage 101 | 102 | ``` 103 | thaw 104 | 105 | DESCRIPTION 106 | 107 | Upgrade Nix Flake inputs using SemVer. 108 | 109 | USAGE 110 | 111 | $ thaw [options] [...inputs] 112 | 113 | OPTIONS 114 | 115 | --flake, -f Choose a flake other than the current directory 116 | --major, -M Allow major version upgrades 117 | --init, -i Initialize inputs to the latest version 118 | --dry-run, -d Show available upgrades without applying them 119 | 120 | --help, -h Show this help message 121 | --verbose Increase logging verbosity, up to 3 times 122 | ``` 123 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "flake-compat": { 4 | "flake": false, 5 | "locked": { 6 | "lastModified": 1650374568, 7 | "narHash": "sha256-Z+s0J8/r907g149rllvwhb4pKi8Wam5ij0st8PwAh+E=", 8 | "owner": "edolstra", 9 | "repo": "flake-compat", 10 | "rev": "b4a34015c698c7793d592d66adbab377907a2be8", 11 | "type": "github" 12 | }, 13 | "original": { 14 | "owner": "edolstra", 15 | "repo": "flake-compat", 16 | "type": "github" 17 | } 18 | }, 19 | "flake-utils": { 20 | "inputs": { 21 | "systems": "systems" 22 | }, 23 | "locked": { 24 | "lastModified": 1694529238, 25 | "narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=", 26 | "owner": "numtide", 27 | "repo": "flake-utils", 28 | "rev": "ff7b65b44d01cf9ba6a71320833626af21126384", 29 | "type": "github" 30 | }, 31 | "original": { 32 | "owner": "numtide", 33 | "repo": "flake-utils", 34 | "type": "github" 35 | } 36 | }, 37 | "flake-utils-plus": { 38 | "inputs": { 39 | "flake-utils": "flake-utils" 40 | }, 41 | "locked": { 42 | "lastModified": 1715533576, 43 | "narHash": "sha256-fT4ppWeCJ0uR300EH3i7kmgRZnAVxrH+XtK09jQWihk=", 44 | "owner": "gytis-ivaskevicius", 45 | "repo": "flake-utils-plus", 46 | "rev": "3542fe9126dc492e53ddd252bb0260fe035f2c0f", 47 | "type": "github" 48 | }, 49 | "original": { 50 | "owner": "gytis-ivaskevicius", 51 | "repo": "flake-utils-plus", 52 | "rev": "3542fe9126dc492e53ddd252bb0260fe035f2c0f", 53 | "type": "github" 54 | } 55 | }, 56 | "nixpkgs": { 57 | "locked": { 58 | "lastModified": 1700794826, 59 | "narHash": "sha256-RyJTnTNKhO0yqRpDISk03I/4A67/dp96YRxc86YOPgU=", 60 | "owner": "nixos", 61 | "repo": "nixpkgs", 62 | "rev": "5a09cb4b393d58f9ed0d9ca1555016a8543c2ac8", 63 | "type": "github" 64 | }, 65 | "original": { 66 | "owner": "nixos", 67 | "ref": "nixos-unstable", 68 | "repo": "nixpkgs", 69 | "type": "github" 70 | } 71 | }, 72 | "root": { 73 | "inputs": { 74 | "nixpkgs": "nixpkgs", 75 | "snowfall-lib": "snowfall-lib" 76 | } 77 | }, 78 | "snowfall-lib": { 79 | "inputs": { 80 | "flake-compat": "flake-compat", 81 | "flake-utils-plus": "flake-utils-plus", 82 | "nixpkgs": [ 83 | "nixpkgs" 84 | ] 85 | }, 86 | "locked": { 87 | "lastModified": 1716675292, 88 | "narHash": "sha256-7TFvVE4HR/b65/0AAhewYHEJzUXxIEJn82ow5bCkrDo=", 89 | "owner": "snowfallorg", 90 | "repo": "lib", 91 | "rev": "5d6e9f235735393c28e1145bec919610b172a20f", 92 | "type": "github" 93 | }, 94 | "original": { 95 | "owner": "snowfallorg", 96 | "ref": "v3.0.2", 97 | "repo": "lib", 98 | "type": "github" 99 | } 100 | }, 101 | "systems": { 102 | "locked": { 103 | "lastModified": 1681028828, 104 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 105 | "owner": "nix-systems", 106 | "repo": "default", 107 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 108 | "type": "github" 109 | }, 110 | "original": { 111 | "owner": "nix-systems", 112 | "repo": "default", 113 | "type": "github" 114 | } 115 | } 116 | }, 117 | "root": "root", 118 | "version": 7 119 | } 120 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "Snowfall Thaw"; 3 | 4 | inputs = { 5 | nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; 6 | 7 | snowfall-lib = { 8 | url = "github:snowfallorg/lib?ref=v3.0.2"; 9 | inputs.nixpkgs.follows = "nixpkgs"; 10 | }; 11 | }; 12 | 13 | outputs = inputs: 14 | inputs.snowfall-lib.mkFlake { 15 | inherit inputs; 16 | 17 | src = ./.; 18 | 19 | snowfall = { 20 | namespace = "snowfallorg"; 21 | 22 | meta = { 23 | name = "thaw"; 24 | title = "Thaw"; 25 | }; 26 | }; 27 | alias.packages.default = "thaw"; 28 | }; 29 | } 30 | -------------------------------------------------------------------------------- /packages/thaw/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | lib, 3 | substituteAll, 4 | nixos-option, 5 | nix, 6 | writeShellApplication, 7 | inputs, 8 | bun, 9 | ... 10 | }: let 11 | substitute = args: builtins.readFile (substituteAll args); 12 | 13 | src = ./src; 14 | in 15 | writeShellApplication { 16 | name = "thaw"; 17 | text = '' 18 | ${lib.getExe bun} ${src}/index.ts $@ 19 | ''; 20 | checkPhase = ""; 21 | runtimeInputs = [ 22 | nix 23 | ]; 24 | } 25 | -------------------------------------------------------------------------------- /packages/thaw/src/help.ts: -------------------------------------------------------------------------------- 1 | import kleur from "./vendor/kleur.cjs"; 2 | 3 | export default function help() { 4 | // prettier-ignore 5 | const message = ` 6 | ${kleur.bold().blue("thaw")} 7 | 8 | ${kleur.bold("DESCRIPTION")} 9 | 10 | Upgrade Nix Flake inputs using SemVer. 11 | 12 | ${kleur.bold("USAGE")} 13 | 14 | ${kleur.dim("$")} ${kleur.bold("thaw")} [options] [...inputs] 15 | 16 | ${kleur.bold("OPTIONS")} 17 | 18 | --flake, -f Choose a flake other than the current directory 19 | --major, -M Allow major version upgrades 20 | --init, -i Initialize inputs to the latest version 21 | --dry-run, -d Show available upgrades without applying them 22 | 23 | --help, -h Show this help message 24 | --verbose Increase logging verbosity, up to 3 times 25 | `; 26 | 27 | console.log(message); 28 | } 29 | -------------------------------------------------------------------------------- /packages/thaw/src/index.ts: -------------------------------------------------------------------------------- 1 | import * as Sleet from "./vendor/sleet.mjs"; 2 | import arg from "./vendor/arg.cjs"; 3 | import kleur from "./vendor/kleur.cjs"; 4 | import rootArgs from "./util/args"; 5 | import log from "./util/log"; 6 | import todo from "./util/todo"; 7 | import { getFlakeInputs, upgradeFlakeInput } from "./util/nix"; 8 | import { getTargetUpgrade } from "./util/forge"; 9 | import help from "./help"; 10 | import * as Bun from "bun"; 11 | import path from "node:path"; 12 | import edit from "./util/edit"; 13 | 14 | const main = async () => { 15 | const args = arg(rootArgs, { 16 | permissive: true, 17 | }); 18 | 19 | log.debug({ args }); 20 | 21 | if (args["--help"]) { 22 | help(); 23 | process.exit(0); 24 | } 25 | 26 | if (args["--dry-run"]) { 27 | log.info("Dry run enabled. No changes will be made."); 28 | } 29 | 30 | let flake: string = 31 | args["--flake"] ?? path.resolve(process.cwd(), "flake.nix"); 32 | 33 | if (!path.isAbsolute(flake)) { 34 | flake = path.resolve(process.cwd(), flake); 35 | } 36 | 37 | if (!flake.endsWith("flake.nix")) { 38 | flake = path.join(flake, "flake.nix"); 39 | } 40 | 41 | const inputs = await getFlakeInputs(flake); 42 | 43 | const names = args._.length > 0 ? args._ : Object.keys(inputs); 44 | 45 | for (const [name, input] of Object.entries(inputs)) { 46 | if (!names.includes(name)) { 47 | log.trace(kleur.dim(`Skipping input ${kleur.bold(name)}...`)); 48 | continue; 49 | } 50 | 51 | log.trace(`Processing input ${kleur.bold().white(name)}...`); 52 | const target = await getTargetUpgrade( 53 | input, 54 | args["--major"], 55 | args["--init"], 56 | ); 57 | 58 | if (target) { 59 | log.info( 60 | `Upgrading ${kleur.bold().white(name)} to ${kleur.bold(target)}`, 61 | ); 62 | 63 | if (!args["--dry-run"]) { 64 | await upgradeFlakeInput(flake, input, target); 65 | } 66 | } else { 67 | log.info( 68 | kleur.dim(`No upgrade available for ${kleur.bold().white(name)}`), 69 | ); 70 | } 71 | } 72 | }; 73 | 74 | main().catch((error) => { 75 | log.error("An unexpected error occurred."); 76 | console.error(error); 77 | }); 78 | -------------------------------------------------------------------------------- /packages/thaw/src/util/args.ts: -------------------------------------------------------------------------------- 1 | import arg from "../vendor/arg.cjs"; 2 | 3 | const args = { 4 | "--help": Boolean, 5 | "-h": "--help", 6 | 7 | "--verbose": arg.COUNT, 8 | "-v": "--verbose", 9 | 10 | "--flake": String, 11 | "-f": "--flake", 12 | 13 | "--major": Boolean, 14 | "-M": "--major", 15 | 16 | "--dry-run": Boolean, 17 | "-d": "--dry-run", 18 | 19 | "--init": Boolean, 20 | "-i": "--init", 21 | }; 22 | 23 | export default args; 24 | -------------------------------------------------------------------------------- /packages/thaw/src/util/edit.ts: -------------------------------------------------------------------------------- 1 | export default async function edit({ file, from, to, insert }) { 2 | const text = await Bun.file(file).text(); 3 | 4 | let cursor = 0; 5 | let line = 1; 6 | let col = 1; 7 | 8 | let result = ''; 9 | 10 | const eat = () => { 11 | let char = text[cursor]; 12 | 13 | if (char === '\r' && text[cursor + 1] === '\n') { 14 | char += '\n'; 15 | cursor += 2; 16 | line++; 17 | col = 1; 18 | } else if (char === '\n') { 19 | cursor++; 20 | line++; 21 | col = 1; 22 | } else { 23 | cursor++; 24 | col++; 25 | } 26 | 27 | return char; 28 | }; 29 | 30 | while (line < from.line && cursor < text.length) { 31 | result += eat(); 32 | } 33 | 34 | while (col < from.col && cursor < text.length) { 35 | result += eat(); 36 | } 37 | 38 | result += insert; 39 | 40 | while (line < to.line && cursor < text.length) { 41 | eat(); 42 | } 43 | 44 | while (col < to.col && cursor < text.length) { 45 | eat(); 46 | } 47 | 48 | while (cursor < text.length) { 49 | result += eat(); 50 | } 51 | 52 | await Bun.write(file, result); 53 | } 54 | -------------------------------------------------------------------------------- /packages/thaw/src/util/forge.ts: -------------------------------------------------------------------------------- 1 | import * as github from "./forges/github"; 2 | import semver from "semver"; 3 | import log from "./log"; 4 | import kleur from "../vendor/kleur.cjs"; 5 | 6 | export async function getTargetUpgrade(input, major, init) { 7 | let current: string | null = null; 8 | let protocol: string | null = null; 9 | 10 | if (input.ref) { 11 | current = input.ref; 12 | protocol = `${input.type}:`; 13 | } else { 14 | const url = new URL(input.url); 15 | 16 | current = url.searchParams.get("ref"); 17 | protocol = url.protocol; 18 | } 19 | 20 | if (!current && init) { 21 | current = "v0.0.0"; 22 | } 23 | 24 | if (!current || !semver.valid(semver.clean(current))) { 25 | return null; 26 | } 27 | 28 | let versions = []; 29 | 30 | switch (protocol) { 31 | default: 32 | log.warn( 33 | `Protocol ${kleur.bold( 34 | protocol.replace(/:$/, ""), 35 | )} is not currently supported...`, 36 | ); 37 | return null; 38 | case "github:": 39 | versions = await github.fetchVersions(input); 40 | break; 41 | } 42 | 43 | const next = versions.reduce((latest, version) => { 44 | const cleanLatest = semver.clean(latest); 45 | const cleanVersion = semver.clean(version); 46 | 47 | if (cleanVersion === null) { 48 | return latest; 49 | } 50 | 51 | if (cleanLatest === null) { 52 | return version; 53 | } 54 | 55 | const kind = semver.diff(cleanLatest, cleanVersion); 56 | 57 | if ( 58 | semver.gt(semver.clean(version), semver.clean(latest)) && 59 | (kind !== "major" || major || init) 60 | ) { 61 | return version; 62 | } else { 63 | return latest; 64 | } 65 | }, current); 66 | 67 | if (next === current) { 68 | return null; 69 | } 70 | 71 | return next; 72 | } 73 | -------------------------------------------------------------------------------- /packages/thaw/src/util/forges/github.ts: -------------------------------------------------------------------------------- 1 | export async function fetchVersions(input) { 2 | const url = new URL(input.url); 3 | 4 | const [owner, repo] = url.pathname.split("/").filter(Boolean); 5 | 6 | const response = await fetch( 7 | `https://api.github.com/repos/${owner}/${repo}/tags`, 8 | ); 9 | 10 | const tags = await response.json(); 11 | 12 | return tags.map((tag) => tag.name); 13 | } 14 | -------------------------------------------------------------------------------- /packages/thaw/src/util/log.ts: -------------------------------------------------------------------------------- 1 | import littlelog, { 2 | configure, 3 | LogLevel, 4 | parseLogLevelNumber, 5 | } from "../vendor/littlelog.cjs"; 6 | import arg from "../vendor/arg.cjs"; 7 | import rootArgs from "./args"; 8 | 9 | const args = arg(rootArgs, { 10 | permissive: true, 11 | }); 12 | 13 | if (args["--verbose"]) { 14 | const level = 15 | args["--verbose"] > 2 16 | ? LogLevel.Trace 17 | : parseLogLevelNumber(args["--verbose"]); 18 | 19 | configure({ 20 | level, 21 | }); 22 | } 23 | 24 | export default littlelog.child("Thaw"); 25 | -------------------------------------------------------------------------------- /packages/thaw/src/util/merge.ts: -------------------------------------------------------------------------------- 1 | export default function merge(a, b) { 2 | if (a === undefined) { 3 | return b; 4 | } 5 | 6 | if (typeof a !== "object" || a === null) { 7 | return b; 8 | } 9 | 10 | if (typeof b !== "object") { 11 | return b; 12 | } 13 | 14 | if (Array.isArray(a) !== Array.isArray(b)) { 15 | return b; 16 | } 17 | 18 | if (Array.isArray(a)) { 19 | return [...a, ...b]; 20 | } 21 | 22 | const result = {}; 23 | for (const key of Object.keys(a)) { 24 | result[key] = merge(a[key], b[key]); 25 | } 26 | 27 | return result; 28 | } 29 | -------------------------------------------------------------------------------- /packages/thaw/src/util/nix.ts: -------------------------------------------------------------------------------- 1 | import { Parser, NodeKind } from '../vendor/sleet.mjs'; 2 | import todo from './todo'; 3 | import merge from './merge'; 4 | import edit from './edit'; 5 | 6 | // Serializes a subset of Nix AST nodes to a static value. 7 | // Location information is preserved on some primitives. 8 | export function toStaticValue(node) { 9 | if (typeof node === 'string') { 10 | return node; 11 | } else if (typeof node === 'boolean') { 12 | return node; 13 | } 14 | 15 | switch (node.kind) { 16 | default: 17 | throw new Error(`Unsupported AST node: ${node.kind}`); 18 | case 'Expr': 19 | return toStaticValue(node.value); 20 | case 'SubExpr': 21 | return toStaticValue(node.value); 22 | case 'String': { 23 | const result = new String(node.value.map(toStaticValue).join('')); 24 | result.loc = node.loc; 25 | return result; 26 | } 27 | case 'Bool': { 28 | const result = new Boolean(node.value); 29 | result.loc = node.loc; 30 | return result; 31 | } 32 | case 'Fn': 33 | return undefined; 34 | case 'Attrs': { 35 | let attrs = {}; 36 | 37 | for (const attr of node.value) { 38 | let property = attrs; 39 | 40 | const parts = attr.name.value.map(toStaticValue); 41 | 42 | for (let i = 0; i < parts.length; i++) { 43 | const part = parts[i]; 44 | 45 | if (i !== parts.length - 1) { 46 | property[part] ??= {}; 47 | } else if (attr.value.kind === 'Attrs') { 48 | property[part] ??= {}; 49 | } else if (attr.value.kind === 'List') { 50 | property[part] ??= []; 51 | } else if (!property.hasOwnProperty(part)) { 52 | property[part] = toStaticValue(attr.value); 53 | } else { 54 | throw new Error(`Duplicate property: ${part}`); 55 | } 56 | 57 | property = property[part]; 58 | } 59 | } 60 | 61 | return attrs; 62 | } 63 | } 64 | } 65 | 66 | export async function parse(path: string) { 67 | const text = await Bun.file(path).text(); 68 | 69 | const parser = new Parser(); 70 | 71 | const ast = parser.parse(text); 72 | 73 | return ast; 74 | } 75 | 76 | export async function getFlakeInputs(path: string) { 77 | const ast = await parse(path); 78 | 79 | const root = ast.value.value.value; 80 | 81 | if (root.kind !== 'Attrs') { 82 | throw new Error('Expected flake to be an attribute set.'); 83 | } 84 | 85 | const flake = toStaticValue(root); 86 | 87 | if (!flake.inputs) { 88 | throw new Error('Expected flake to have inputs.'); 89 | } 90 | 91 | return flake.inputs; 92 | } 93 | 94 | export async function upgradeFlakeInput(flake, input, target) { 95 | if (!input.ref?.loc && !input.url?.loc) { 96 | throw new Error('No location information for input: ' + input.name); 97 | } 98 | 99 | if (input.ref) { 100 | await edit({ 101 | file: flake, 102 | from: input.ref.loc.start, 103 | to: input.ref.loc.end, 104 | insert: `"${target}"`, 105 | }); 106 | } else { 107 | const url = new URL(input.url); 108 | 109 | url.searchParams.set('ref', target); 110 | 111 | await edit({ 112 | file: flake, 113 | from: input.url.loc.start, 114 | to: input.url.loc.end, 115 | insert: `"${url.href}"`, 116 | }); 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /packages/thaw/src/util/todo.ts: -------------------------------------------------------------------------------- 1 | export default function todo(message = "Not implemented") { 2 | throw new Error(message); 3 | } 4 | -------------------------------------------------------------------------------- /packages/thaw/src/vendor/arg.cjs: -------------------------------------------------------------------------------- 1 | const flagSymbol = Symbol("arg flag"); 2 | 3 | class ArgError extends Error { 4 | constructor(msg, code) { 5 | super(msg); 6 | this.name = "ArgError"; 7 | this.code = code; 8 | 9 | Object.setPrototypeOf(this, ArgError.prototype); 10 | } 11 | } 12 | 13 | function arg( 14 | opts, 15 | { 16 | argv = process.argv.slice(2), 17 | permissive = false, 18 | stopAtPositional = false, 19 | } = {}, 20 | ) { 21 | if (!opts) { 22 | throw new ArgError( 23 | "argument specification object is required", 24 | "ARG_CONFIG_NO_SPEC", 25 | ); 26 | } 27 | 28 | const result = { _: [] }; 29 | 30 | const aliases = {}; 31 | const handlers = {}; 32 | 33 | for (const key of Object.keys(opts)) { 34 | if (!key) { 35 | throw new ArgError( 36 | "argument key cannot be an empty string", 37 | "ARG_CONFIG_EMPTY_KEY", 38 | ); 39 | } 40 | 41 | if (key[0] !== "-") { 42 | throw new ArgError( 43 | `argument key must start with '-' but found: '${key}'`, 44 | "ARG_CONFIG_NONOPT_KEY", 45 | ); 46 | } 47 | 48 | if (key.length === 1) { 49 | throw new ArgError( 50 | `argument key must have a name; singular '-' keys are not allowed: ${key}`, 51 | "ARG_CONFIG_NONAME_KEY", 52 | ); 53 | } 54 | 55 | if (typeof opts[key] === "string") { 56 | aliases[key] = opts[key]; 57 | continue; 58 | } 59 | 60 | let type = opts[key]; 61 | let isFlag = false; 62 | 63 | if ( 64 | Array.isArray(type) && 65 | type.length === 1 && 66 | typeof type[0] === "function" 67 | ) { 68 | const [fn] = type; 69 | type = (value, name, prev = []) => { 70 | prev.push(fn(value, name, prev[prev.length - 1])); 71 | return prev; 72 | }; 73 | isFlag = fn === Boolean || fn[flagSymbol] === true; 74 | } else if (typeof type === "function") { 75 | isFlag = type === Boolean || type[flagSymbol] === true; 76 | } else { 77 | throw new ArgError( 78 | `type missing or not a function or valid array type: ${key}`, 79 | "ARG_CONFIG_VAD_TYPE", 80 | ); 81 | } 82 | 83 | if (key[1] !== "-" && key.length > 2) { 84 | throw new ArgError( 85 | `short argument keys (with a single hyphen) must have only one character: ${key}`, 86 | "ARG_CONFIG_SHORTOPT_TOOLONG", 87 | ); 88 | } 89 | 90 | handlers[key] = [type, isFlag]; 91 | } 92 | 93 | for (let i = 0, len = argv.length; i < len; i++) { 94 | const wholeArg = argv[i]; 95 | 96 | if (stopAtPositional && result._.length > 0) { 97 | result._ = result._.concat(argv.slice(i)); 98 | break; 99 | } 100 | 101 | if (wholeArg === "--") { 102 | result._ = result._.concat(argv.slice(i + 1)); 103 | break; 104 | } 105 | 106 | if (wholeArg.length > 1 && wholeArg[0] === "-") { 107 | /* eslint-disable operator-linebreak */ 108 | const separatedArguments = 109 | wholeArg[1] === "-" || wholeArg.length === 2 110 | ? [wholeArg] 111 | : wholeArg 112 | .slice(1) 113 | .split("") 114 | .map((a) => `-${a}`); 115 | /* eslint-enable operator-linebreak */ 116 | 117 | for (let j = 0; j < separatedArguments.length; j++) { 118 | const arg = separatedArguments[j]; 119 | const [originalArgName, argStr] = 120 | arg[1] === "-" ? arg.split(/=(.*)/, 2) : [arg, undefined]; 121 | 122 | let argName = originalArgName; 123 | while (argName in aliases) { 124 | argName = aliases[argName]; 125 | } 126 | 127 | if (!(argName in handlers)) { 128 | if (permissive) { 129 | result._.push(arg); 130 | continue; 131 | } else { 132 | throw new ArgError( 133 | `unknown or unexpected option: ${originalArgName}`, 134 | "ARG_UNKNOWN_OPTION", 135 | ); 136 | } 137 | } 138 | 139 | const [type, isFlag] = handlers[argName]; 140 | 141 | if (!isFlag && j + 1 < separatedArguments.length) { 142 | throw new ArgError( 143 | `option requires argument (but was followed by another short argument): ${originalArgName}`, 144 | "ARG_MISSING_REQUIRED_SHORTARG", 145 | ); 146 | } 147 | 148 | if (isFlag) { 149 | result[argName] = type(true, argName, result[argName]); 150 | } else if (argStr === undefined) { 151 | if ( 152 | argv.length < i + 2 || 153 | (argv[i + 1].length > 1 && 154 | argv[i + 1][0] === "-" && 155 | !( 156 | argv[i + 1].match(/^-?\d*(\.(?=\d))?\d*$/) && 157 | (type === Number || 158 | // eslint-disable-next-line no-undef 159 | (typeof BigInt !== "undefined" && type === BigInt)) 160 | )) 161 | ) { 162 | const extended = 163 | originalArgName === argName ? "" : ` (alias for ${argName})`; 164 | throw new ArgError( 165 | `option requires argument: ${originalArgName}${extended}`, 166 | "ARG_MISSING_REQUIRED_LONGARG", 167 | ); 168 | } 169 | 170 | result[argName] = type(argv[i + 1], argName, result[argName]); 171 | ++i; 172 | } else { 173 | result[argName] = type(argStr, argName, result[argName]); 174 | } 175 | } 176 | } else { 177 | result._.push(wholeArg); 178 | } 179 | } 180 | 181 | return result; 182 | } 183 | 184 | arg.flag = (fn) => { 185 | fn[flagSymbol] = true; 186 | return fn; 187 | }; 188 | 189 | // Utility types 190 | arg.COUNT = arg.flag((v, name, existingCount) => (existingCount || 0) + 1); 191 | 192 | // Expose error class 193 | arg.ArgError = ArgError; 194 | 195 | module.exports = arg; 196 | -------------------------------------------------------------------------------- /packages/thaw/src/vendor/kleur.cjs: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | let FORCE_COLOR, 4 | NODE_DISABLE_COLORS, 5 | NO_COLOR, 6 | TERM, 7 | isTTY = true; 8 | if (typeof process !== "undefined") { 9 | ({ FORCE_COLOR, NODE_DISABLE_COLORS, NO_COLOR, TERM } = process.env || {}); 10 | isTTY = process.stdout && process.stdout.isTTY; 11 | } 12 | 13 | const $ = { 14 | enabled: 15 | !NODE_DISABLE_COLORS && 16 | NO_COLOR == null && 17 | TERM !== "dumb" && 18 | ((FORCE_COLOR != null && FORCE_COLOR !== "0") || isTTY), 19 | 20 | // modifiers 21 | reset: init(0, 0), 22 | bold: init(1, 22), 23 | dim: init(2, 22), 24 | italic: init(3, 23), 25 | underline: init(4, 24), 26 | inverse: init(7, 27), 27 | hidden: init(8, 28), 28 | strikethrough: init(9, 29), 29 | 30 | // colors 31 | black: init(30, 39), 32 | red: init(31, 39), 33 | green: init(32, 39), 34 | yellow: init(33, 39), 35 | blue: init(34, 39), 36 | magenta: init(35, 39), 37 | cyan: init(36, 39), 38 | white: init(37, 39), 39 | gray: init(90, 39), 40 | grey: init(90, 39), 41 | 42 | // background colors 43 | bgBlack: init(40, 49), 44 | bgRed: init(41, 49), 45 | bgGreen: init(42, 49), 46 | bgYellow: init(43, 49), 47 | bgBlue: init(44, 49), 48 | bgMagenta: init(45, 49), 49 | bgCyan: init(46, 49), 50 | bgWhite: init(47, 49), 51 | }; 52 | 53 | function run(arr, str) { 54 | let i = 0, 55 | tmp, 56 | beg = "", 57 | end = ""; 58 | for (; i < arr.length; i++) { 59 | tmp = arr[i]; 60 | beg += tmp.open; 61 | end += tmp.close; 62 | if (!!~str.indexOf(tmp.close)) { 63 | str = str.replace(tmp.rgx, tmp.close + tmp.open); 64 | } 65 | } 66 | return beg + str + end; 67 | } 68 | 69 | function chain(has, keys) { 70 | let ctx = { has, keys }; 71 | 72 | ctx.reset = $.reset.bind(ctx); 73 | ctx.bold = $.bold.bind(ctx); 74 | ctx.dim = $.dim.bind(ctx); 75 | ctx.italic = $.italic.bind(ctx); 76 | ctx.underline = $.underline.bind(ctx); 77 | ctx.inverse = $.inverse.bind(ctx); 78 | ctx.hidden = $.hidden.bind(ctx); 79 | ctx.strikethrough = $.strikethrough.bind(ctx); 80 | 81 | ctx.black = $.black.bind(ctx); 82 | ctx.red = $.red.bind(ctx); 83 | ctx.green = $.green.bind(ctx); 84 | ctx.yellow = $.yellow.bind(ctx); 85 | ctx.blue = $.blue.bind(ctx); 86 | ctx.magenta = $.magenta.bind(ctx); 87 | ctx.cyan = $.cyan.bind(ctx); 88 | ctx.white = $.white.bind(ctx); 89 | ctx.gray = $.gray.bind(ctx); 90 | ctx.grey = $.grey.bind(ctx); 91 | 92 | ctx.bgBlack = $.bgBlack.bind(ctx); 93 | ctx.bgRed = $.bgRed.bind(ctx); 94 | ctx.bgGreen = $.bgGreen.bind(ctx); 95 | ctx.bgYellow = $.bgYellow.bind(ctx); 96 | ctx.bgBlue = $.bgBlue.bind(ctx); 97 | ctx.bgMagenta = $.bgMagenta.bind(ctx); 98 | ctx.bgCyan = $.bgCyan.bind(ctx); 99 | ctx.bgWhite = $.bgWhite.bind(ctx); 100 | 101 | return ctx; 102 | } 103 | 104 | function init(open, close) { 105 | let blk = { 106 | open: `\x1b[${open}m`, 107 | close: `\x1b[${close}m`, 108 | rgx: new RegExp(`\\x1b\\[${close}m`, "g"), 109 | }; 110 | return function (txt) { 111 | if (this !== void 0 && this.has !== void 0) { 112 | !!~this.has.indexOf(open) || (this.has.push(open), this.keys.push(blk)); 113 | return txt === void 0 114 | ? this 115 | : $.enabled 116 | ? run(this.keys, txt + "") 117 | : txt + ""; 118 | } 119 | return txt === void 0 120 | ? chain([open], [blk]) 121 | : $.enabled 122 | ? run([blk], txt + "") 123 | : txt + ""; 124 | }; 125 | } 126 | 127 | module.exports = $; 128 | -------------------------------------------------------------------------------- /packages/thaw/src/vendor/littlelog.cjs: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var w = Object.defineProperty; 3 | var C = Object.getOwnPropertySymbols; 4 | var D = Object.prototype.hasOwnProperty, 5 | S = Object.prototype.propertyIsEnumerable; 6 | var T = (e, r, o) => 7 | r in e 8 | ? w(e, r, { enumerable: !0, configurable: !0, writable: !0, value: o }) 9 | : (e[r] = o), 10 | A = (e, r) => { 11 | for (var o in r || (r = {})) D.call(r, o) && T(e, o, r[o]); 12 | if (C) for (var o of C(r)) S.call(r, o) && T(e, o, r[o]); 13 | return e; 14 | }; 15 | Object.defineProperties(exports, { 16 | __esModule: { value: !0 }, 17 | [Symbol.toStringTag]: { value: "Module" }, 18 | }); 19 | var m = require("./kleur.cjs"); 20 | function U(e) { 21 | return e && typeof e == "object" && "default" in e ? e : { default: e }; 22 | } 23 | var t = U(m), 24 | I = ((e) => ( 25 | (e.Silent = "SILENT"), 26 | (e.Info = "INFO"), 27 | (e.Debug = "DEBUG"), 28 | (e.Trace = "TRACE"), 29 | e 30 | ))(I || {}); 31 | const f = process.stdout.write.bind(process.stdout), 32 | v = process.stderr.write.bind(process.stderr), 33 | p = { 34 | colors: { 35 | INFO: t.default.bold().blue, 36 | WARN: t.default.bold().yellow, 37 | DEBUG: t.default.bold().magenta, 38 | TRACE: t.default.bold().dim, 39 | ERROR: t.default.bold().red, 40 | FATAL: t.default.bold().bgWhite().red, 41 | }, 42 | icons: { 43 | INFO: "\uF7FC", 44 | WARN: "\uF529", 45 | DEBUG: "\uF188", 46 | TRACE: "\uFBCE", 47 | ERROR: "\uF06A", 48 | FATAL: "\uFB8A", 49 | }, 50 | loggers: { INFO: f, WARN: f, DEBUG: f, TRACE: f, ERROR: v, FATAL: v }, 51 | levels: { 52 | INFO: ["INFO", "DEBUG", "TRACE"], 53 | WARN: ["INFO", "DEBUG", "TRACE"], 54 | DEBUG: ["DEBUG", "TRACE"], 55 | TRACE: ["TRACE"], 56 | ERROR: ["INFO", "DEBUG", "TRACE"], 57 | FATAL: ["INFO", "DEBUG", "TRACE"], 58 | }, 59 | }, 60 | N = { 61 | colors: [ 62 | t.default.bold().blue, 63 | t.default.bold().green, 64 | t.default.bold().cyan, 65 | t.default.bold().magenta, 66 | t.default.bold().yellow, 67 | t.default.bold().white, 68 | ], 69 | }, 70 | s = { 71 | colors: [ 72 | t.default.bold().blue, 73 | t.default.bold().magenta, 74 | t.default.bold().yellow, 75 | ], 76 | }, 77 | n = { 78 | colors: { 79 | undefined: t.default.dim, 80 | null: t.default.blue, 81 | number: t.default.yellow, 82 | string: t.default.green, 83 | boolean: t.default.blue, 84 | comma: t.default.white, 85 | property: t.default.white, 86 | equals: t.default.white, 87 | circular: t.default.dim, 88 | stack: t.default.red, 89 | }, 90 | }, 91 | _ = { colors: { timestamp: t.default.dim } }; 92 | let b = -1; 93 | const g = new Map(); 94 | var G; 95 | let c = { 96 | level: process.env.LOG_LEVEL || "INFO", 97 | filter: process.env.LOG_FILTER || process.env.DEBUG || void 0, 98 | prefix: process.env.LOG_PREFIX !== "false", 99 | timestamp: process.env.LOG_TIMESTAMP === "true", 100 | color: 101 | process.env.LOG_COLOR === "true" || 102 | process.env.FORCE_COLOR === "1" || 103 | ((process.env.LOG_COLOR !== "false" || process.env.FORCE_COLOR !== "0") && 104 | ((G = process.stdout.isTTY) != null ? G : !1)), 105 | icons: process.env.LOG_ICONS === "true", 106 | }; 107 | function j(e) { 108 | c = A(A({}, c), e); 109 | } 110 | function M(e) { 111 | if (typeof e == "number") 112 | switch (e) { 113 | default: 114 | case 0: 115 | return "INFO"; 116 | case 1: 117 | return "DEBUG"; 118 | case 2: 119 | return "TRACE"; 120 | } 121 | } 122 | function l(e, r) { 123 | return c.color ? e(r) : r; 124 | } 125 | function x() { 126 | return c.timestamp 127 | ? `${l(_.colors.timestamp, new Date().toISOString())} ` 128 | : ""; 129 | } 130 | function W(e) { 131 | return c.icons ? `${p.icons[e]} ` : ""; 132 | } 133 | function k(e, r) { 134 | return l(p.colors[e], ` ${r}${e.padEnd(5, " ")} `); 135 | } 136 | function P(e) { 137 | return ( 138 | e.length > 0 && 139 | !g.has(e) && 140 | (b++, b >= N.colors.length && (b = 0), g.set(e, N.colors[b])), 141 | e.length > 0 ? `${l(g.get(e), e.join(":"))} ` : "" 142 | ); 143 | } 144 | function $(e) { 145 | return e.replaceAll('"', '\\"'); 146 | } 147 | function F(e, r = 0, o = new Set()) { 148 | const u = typeof e; 149 | if (o.has(e)) return n.colors.circular("[Circular Reference]"); 150 | if ((o.add(e), r > 200)) return n.colors.stack("[Stack Size Exceeded]"); 151 | if (u === "undefined") return n.colors.undefined("undefined"); 152 | if (e === null) return n.colors.null("null"); 153 | if (u === "number") return n.colors.number(e.toString()); 154 | if (u === "boolean") return n.colors.boolean(e.toString()); 155 | if (u === "string") return n.colors.string(`"${$(e)}"`); 156 | if (Array.isArray(e)) { 157 | const i = e 158 | .map((h) => F(h, r + 1, new Set([...o]))) 159 | .join(n.colors.comma(", ")), 160 | d = r % s.colors.length, 161 | a = l(s.colors[d], "["), 162 | R = l(s.colors[d], "]"); 163 | return `${a} ${i} ${R}`; 164 | } else if (u === "object") { 165 | const i = []; 166 | for (const [O, y] of Object.entries(e)) { 167 | const B = O.match(/[^[a-zA-Z_\$]/g) 168 | ? n.colors.string(`"${$(O)}"`) 169 | : n.colors.property(O), 170 | L = F(y, r + 1, new Set([...o])); 171 | i.push(`${B}${n.colors.equals("=")}${L}`); 172 | } 173 | const d = i.join(" "), 174 | a = r % s.colors.length, 175 | R = l(s.colors[a], "{"), 176 | h = l(s.colors[a], "}"); 177 | return `${R} ${d} ${h}`; 178 | } else return e; 179 | } 180 | function q(e) { 181 | return e.length === 1 && typeof e[0] == "string" 182 | ? e[0] 183 | : e.map((r) => F(r, 0)).join(" "); 184 | } 185 | function z(e, r, ...o) { 186 | return `${x()}${k(e, W(e))} ${P(r)}${q(o)} 187 | `; 188 | } 189 | class E { 190 | constructor(r) { 191 | (this.info = this.log.bind(this, "INFO")), 192 | (this.warn = this.log.bind(this, "WARN")), 193 | (this.debug = this.log.bind(this, "DEBUG")), 194 | (this.trace = this.log.bind(this, "TRACE")), 195 | (this.error = this.log.bind(this, "ERROR")), 196 | (this.fatal = this.log.bind(this, "FATAL")), 197 | (this.prefix = r); 198 | } 199 | static create(r) { 200 | return new E(r ? [r] : []); 201 | } 202 | child(r) { 203 | return new E([...this.prefix, r]); 204 | } 205 | log(r, ...o) { 206 | p.levels[r].includes(c.level) && p.loggers[r](z(r, this.prefix, ...o)); 207 | } 208 | } 209 | var K = E.create(); 210 | exports.LogLevel = I; 211 | exports.configure = j; 212 | exports.default = K; 213 | exports.parseLogLevelNumber = M; 214 | -------------------------------------------------------------------------------- /packages/thaw/src/vendor/sleet.mjs: -------------------------------------------------------------------------------- 1 | var E = Object.defineProperty; 2 | var v = (n, e, t) => e in n ? E(n, e, { enumerable: !0, configurable: !0, writable: !0, value: t }) : n[e] = t; 3 | var k = (n, e, t) => (v(n, typeof e != "symbol" ? e + "" : e, t), t); 4 | var s = /* @__PURE__ */ ((n) => (n.Eof = "Eof", n.Comment = "Comment", n.Null = "Null", n.Bool = "Bool", n.String = "String", n.Int = "Int", n.Float = "Float", n.Path = "Path", n.OpenParen = "OpenParen", n.CloseParen = "CloseParen", n.OpenCurly = "OpenCurly", n.CloseCurly = "CloseCurly", n.DollarCurly = "DollarCurly", n.OpenBracket = "OpenBracket", n.CloseBracket = "CloseBracket", n.Keyword = "Keyword", n.Operator = "Operator", n.Semi = "Semi", n.Identifier = "Identifier", n.NewLine = "NewLine", n.Has = "Has", n.At = "At", n.Colon = "Colon", n.Eq = "Eq", n.EqEq = "EqEq", n.NotEq = "NotEq", n.Not = "Not", n.Lt = "Lt", n.Lte = "Lte", n.Gt = "Gt", n.Gte = "Gte", n.Add = "Add", n.Sub = "Sub", n.Mul = "Mul", n.Div = "Div", n.Imp = "Imp", n.Update = "Update", n.Concat = "Concat", n.Or = "Or", n.And = "And", n.Period = "Period", n.Comma = "Comma", n.Ellipsis = "Ellipsis", n.Interp = "Interp", n))(s || {}); 5 | class g { 6 | constructor() { 7 | k(this, "code", ""); 8 | k(this, "cursor", 0); 9 | k(this, "line", 1); 10 | k(this, "col", 1); 11 | k(this, "tokens", []); 12 | } 13 | lex(e) { 14 | for (this.code = e, this.cursor = 0, this.line = 1, this.col = 1, this.tokens = []; this.cursor < this.code.length; ) { 15 | const t = this.lexToken(); 16 | if (this.tokens.push(t), t.kind === "Eof") 17 | break; 18 | } 19 | return (this.tokens.length === 0 || this.tokens[this.tokens.length - 1].kind !== "Eof") && this.tokens.push(this.lexEofToken()), this.tokens; 20 | } 21 | peek(e = 0) { 22 | const t = this.cursor, i = this.line, r = this.col; 23 | this.cursor += e; 24 | const l = this.consume(); 25 | return this.cursor = t, this.line = i, this.col = r, l; 26 | } 27 | consume() { 28 | let e = this.code[this.cursor]; 29 | return this.cursor++, this.col++, e === "\r" && this.peek() === ` 30 | ` ? (this.cursor++, e = `\r 31 | `, this.line++, this.col = 1) : e === ` 32 | ` && (this.line++, this.col = 1), e; 33 | } 34 | lexToken() { 35 | let e; 36 | for (; this.cursor < this.code.length && (e = this.peek(), !(e === ` 37 | ` || e === `\r 38 | ` || !/\s/.test(e))); ) 39 | this.consume(); 40 | return e = this.peek(), e === "." && (this.peek(1) === void 0 || this.peek(1) !== void 0 && this.peek(1) !== "/" && this.peek(1) !== ".") ? this.lexPeriod() : e === "#" ? this.lexComment() : e === "/" && this.peek(1) == "*" ? this.lexMultilineComment() : e === void 0 ? this.lexEofToken() : e === ` 41 | ` || e === `\r 42 | ` ? this.lexNewLine() : e === ";" ? this.lexSemi() : e === "(" ? this.lexOpenParen() : e === ")" ? this.lexCloseParen() : e === "{" ? this.lexOpenCurly() : e === "}" ? this.lexCloseCurly() : e === "[" ? this.lexOpenBracket() : e === "]" ? this.lexCloseBracket() : e === "$" && this.peek(1) === "{" ? this.lexInterp() : this.isIdentStart(e) ? this.lexIdent() : e === "." && this.cursor + 2 < this.code.length && this.peek(1) === "." && this.peek(2) === "." ? this.lexOperator() : this.isPathStart(e) && this.cursor + 1 < this.code.length && this.isPath(this.peek(1)) && `${e}${this.peek(1)}` != "//" ? this.lexPath() : this.isNumberStart(e) ? this.lexNumber() : this.isOperatorStart(e) ? this.lexOperator() : e === '"' || e === "'" && this.peek(1) === "'" ? this.lexString() : this.lexEofToken(); 43 | } 44 | lexPeriod() { 45 | const { line: e, col: t } = this; 46 | this.consume(); 47 | const { line: i, col: r } = this; 48 | return { 49 | kind: "Period", 50 | loc: { 51 | start: { line: e, col: t }, 52 | end: { line: i, col: r } 53 | } 54 | }; 55 | } 56 | lexNewLine() { 57 | const { line: e, col: t } = this; 58 | this.consume(); 59 | const { line: i, col: r } = this; 60 | return { 61 | kind: "NewLine", 62 | loc: { 63 | start: { line: e, col: t }, 64 | end: { line: i, col: r } 65 | } 66 | }; 67 | } 68 | lexEofToken() { 69 | return this.consume(), { 70 | kind: "Eof", 71 | loc: { 72 | start: { line: this.line, col: this.col }, 73 | end: { line: this.line, col: this.col } 74 | } 75 | }; 76 | } 77 | lexSemi() { 78 | const { line: e, col: t } = this; 79 | this.consume(); 80 | const { line: i, col: r } = this; 81 | return { 82 | kind: "Semi", 83 | loc: { 84 | start: { line: e, col: t }, 85 | end: { line: i, col: r } 86 | } 87 | }; 88 | } 89 | lexOpenParen() { 90 | const { line: e, col: t } = this; 91 | this.consume(); 92 | const { line: i, col: r } = this; 93 | return { 94 | kind: "OpenParen", 95 | loc: { 96 | start: { line: e, col: t }, 97 | end: { line: i, col: r } 98 | } 99 | }; 100 | } 101 | lexCloseParen() { 102 | const { line: e, col: t } = this; 103 | this.consume(); 104 | const { line: i, col: r } = this; 105 | return { 106 | kind: "CloseParen", 107 | loc: { 108 | start: { line: e, col: t }, 109 | end: { line: i, col: r } 110 | } 111 | }; 112 | } 113 | lexOpenCurly() { 114 | const { line: e, col: t } = this; 115 | this.consume(); 116 | const { line: i, col: r } = this; 117 | return { 118 | kind: "OpenCurly", 119 | loc: { 120 | start: { line: e, col: t }, 121 | end: { line: i, col: r } 122 | } 123 | }; 124 | } 125 | lexCloseCurly() { 126 | const { line: e, col: t } = this; 127 | this.consume(); 128 | const { line: i, col: r } = this; 129 | return { 130 | kind: "CloseCurly", 131 | loc: { 132 | start: { line: e, col: t }, 133 | end: { line: i, col: r } 134 | } 135 | }; 136 | } 137 | lexOpenBracket() { 138 | const { line: e, col: t } = this; 139 | this.consume(); 140 | const { line: i, col: r } = this; 141 | return { 142 | kind: "OpenBracket", 143 | loc: { 144 | start: { line: e, col: t }, 145 | end: { line: i, col: r } 146 | } 147 | }; 148 | } 149 | lexCloseBracket() { 150 | const { line: e, col: t } = this; 151 | this.consume(); 152 | const { line: i, col: r } = this; 153 | return { 154 | kind: "CloseBracket", 155 | loc: { 156 | start: { line: e, col: t }, 157 | end: { line: i, col: r } 158 | } 159 | }; 160 | } 161 | isIdentStart(e) { 162 | return /[a-zA-Z_]/.test(e); 163 | } 164 | isIdent(e) { 165 | return /[a-zA-Z0-9_'\-]/.test(e); 166 | } 167 | lexIdent() { 168 | const { line: e, col: t } = this; 169 | let i = this.consume(); 170 | for (; this.cursor < this.code.length && this.isIdent(this.peek()); ) 171 | i += this.consume(); 172 | const { line: r, col: l } = this; 173 | switch (i) { 174 | case "null": 175 | return { 176 | kind: "Null", 177 | loc: { 178 | start: { line: e, col: t }, 179 | end: { line: r, col: l } 180 | } 181 | }; 182 | case "true": 183 | return { 184 | kind: "Bool", 185 | value: !0, 186 | loc: { 187 | start: { line: e, col: t }, 188 | end: { line: r, col: l } 189 | } 190 | }; 191 | case "false": 192 | return { 193 | kind: "Bool", 194 | value: !1, 195 | loc: { 196 | start: { line: e, col: t }, 197 | end: { line: r, col: l } 198 | } 199 | }; 200 | case "let": 201 | case "in": 202 | case "rec": 203 | case "with": 204 | case "inherit": 205 | case "assert": 206 | case "or": 207 | case "import": 208 | case "if": 209 | case "then": 210 | case "else": 211 | return { 212 | kind: "Keyword", 213 | value: i, 214 | loc: { 215 | start: { line: e, col: t }, 216 | end: { line: r, col: l } 217 | } 218 | }; 219 | default: 220 | return { 221 | kind: "Identifier", 222 | value: i, 223 | loc: { 224 | start: { line: e, col: t }, 225 | end: { line: r, col: l } 226 | } 227 | }; 228 | } 229 | } 230 | isPathStart(e) { 231 | return e === "." || e === "/" || e === "~"; 232 | } 233 | isPath(e) { 234 | return /[A-Za-z0-9_@\.\-\$\/\~]/.test(e); 235 | } 236 | lexPath() { 237 | const { line: e, col: t } = this; 238 | let i = this.consume(); 239 | const { line: r, col: l } = this; 240 | for (; this.cursor < this.code.length; ) { 241 | let a = this.peek(); 242 | if (a === "\\") { 243 | this.consume(), i += this.consume(); 244 | continue; 245 | } 246 | if (this.isPath(a)) 247 | i += this.consume(); 248 | else 249 | break; 250 | } 251 | return { 252 | kind: "Path", 253 | value: i, 254 | loc: { 255 | start: { line: e, col: t }, 256 | end: { line: r, col: l } 257 | } 258 | }; 259 | } 260 | isNumberStart(e) { 261 | return /[0-9]/.test(e); 262 | } 263 | isNumber(e) { 264 | return /[0-9]/.test(e); 265 | } 266 | lexNumber() { 267 | const { line: e, col: t } = this; 268 | let i = this.consume(), r = !1; 269 | const { line: l, col: a } = this; 270 | for (; this.cursor < this.code.length; ) { 271 | let c = this.peek(); 272 | if (c === "." && this.isNumber(this.peek(1))) { 273 | i += this.consume(), r = !0; 274 | continue; 275 | } 276 | if (this.isNumber(c)) 277 | i += this.consume(); 278 | else 279 | break; 280 | } 281 | return { 282 | kind: r ? "Float" : "Int", 283 | value: Number(i), 284 | loc: { 285 | start: { line: e, col: t }, 286 | end: { line: l, col: a } 287 | } 288 | }; 289 | } 290 | isOperatorStart(e) { 291 | return ["=", "!", "+", "-", "*", "/", "<", ">", ":", "@", ".", ",", "?", "|", "&"].includes(e); 292 | } 293 | isOperator(e) { 294 | return [ 295 | "=", 296 | "==", 297 | "!=", 298 | "!", 299 | "+", 300 | "-", 301 | "*", 302 | "/", 303 | "->", 304 | "//", 305 | "++", 306 | "<", 307 | ">", 308 | ":", 309 | "@", 310 | "..", 311 | // This is terrible, but... 312 | "...", 313 | ",", 314 | "?", 315 | "<=", 316 | ">=", 317 | "||", 318 | "&&" 319 | ].includes(e); 320 | } 321 | lexOperator() { 322 | const e = this.cursor, { line: t, col: i } = this; 323 | let r = this.consume(); 324 | for (; this.cursor < this.code.length && this.isOperator(r + this.peek()); ) 325 | r += this.consume(); 326 | const l = this.cursor, { line: a, col: c } = this, o = { 327 | start: { line: t, col: i }, 328 | end: { line: a, col: c } 329 | }; 330 | switch (r) { 331 | case "?": 332 | return { 333 | kind: "Has", 334 | loc: o 335 | }; 336 | case "@": 337 | return { 338 | kind: "At", 339 | loc: o 340 | }; 341 | case ":": 342 | return { 343 | kind: "Colon", 344 | loc: o 345 | }; 346 | case "=": 347 | return { 348 | kind: "Eq", 349 | loc: o 350 | }; 351 | case "==": 352 | return { 353 | kind: "EqEq", 354 | loc: o 355 | }; 356 | case "!=": 357 | return { 358 | kind: "NotEq", 359 | loc: o 360 | }; 361 | case "!": 362 | return { 363 | kind: "Not", 364 | loc: o 365 | }; 366 | case "<": 367 | return { 368 | kind: "Lt", 369 | loc: o 370 | }; 371 | case ">": 372 | return { 373 | kind: "Gt", 374 | loc: o 375 | }; 376 | case "+": 377 | return { 378 | kind: "Add", 379 | loc: o 380 | }; 381 | case "-": 382 | return { 383 | kind: "Sub", 384 | loc: o 385 | }; 386 | case "*": 387 | return { 388 | kind: "Mul", 389 | loc: o 390 | }; 391 | case "/": 392 | return { 393 | kind: "Div", 394 | loc: o 395 | }; 396 | case "->": 397 | return { 398 | kind: "Imp", 399 | loc: o 400 | }; 401 | case "++": 402 | return { 403 | kind: "Concat", 404 | loc: o 405 | }; 406 | case ",": 407 | return { 408 | kind: "Comma", 409 | loc: o 410 | }; 411 | case "...": 412 | return { 413 | kind: "Ellipsis", 414 | loc: o 415 | }; 416 | case "//": 417 | return { 418 | kind: "Update", 419 | loc: o 420 | }; 421 | case "||": 422 | return { 423 | kind: "Or", 424 | loc: o 425 | }; 426 | case "&&": 427 | return { 428 | kind: "And", 429 | loc: o 430 | }; 431 | case "<=": 432 | return { 433 | kind: "Lte", 434 | loc: o 435 | }; 436 | case ">=": 437 | return { 438 | kind: "Gte", 439 | loc: o 440 | }; 441 | } 442 | throw console.log(this.code.substring(e - 10, l + 10)), new Error(`Unknown operator: ${r}`); 443 | } 444 | lexInterp() { 445 | const { line: e, col: t } = this; 446 | this.consume(), this.consume(); 447 | let i = 0; 448 | const r = []; 449 | for (; this.cursor < this.code.length; ) { 450 | const c = this.lexToken(); 451 | if (c.kind === "OpenCurly" && i++, c.kind === "CloseCurly" && i--, i === -1) 452 | break; 453 | r.push(c); 454 | } 455 | const { line: l, col: a } = this; 456 | return { 457 | kind: "Interp", 458 | value: r, 459 | loc: { 460 | start: { line: e, col: t }, 461 | end: { line: l, col: a } 462 | } 463 | }; 464 | } 465 | lexString() { 466 | const { line: e, col: t } = this, i = this.peek() === "'", r = [""]; 467 | for (this.consume(), i && this.consume(); this.cursor < this.code.length; ) { 468 | if (i && this.peek() === "'" && this.peek(1) === "'") { 469 | this.consume(), this.consume(); 470 | break; 471 | } 472 | if (!i && this.peek() === '"') { 473 | this.consume(); 474 | break; 475 | } 476 | const c = this.peek(); 477 | if (c === "\\") { 478 | this.consume(), r[r.length - 1] = r[r.length - 1] + this.consume(); 479 | continue; 480 | } 481 | if (c === "$" && this.peek(1) === "{") { 482 | const o = this.lexInterp(); 483 | r.push(o), r.push(""); 484 | continue; 485 | } 486 | r[r.length - 1] = r[r.length - 1] + this.consume(); 487 | } 488 | const { line: l, col: a } = this; 489 | return { 490 | kind: "String", 491 | multiline: i, 492 | value: r, 493 | loc: { 494 | start: { line: e, col: t }, 495 | end: { line: l, col: a } 496 | } 497 | }; 498 | } 499 | lexComment() { 500 | const { line: e, col: t } = this; 501 | let i = ""; 502 | for (this.consume(); this.cursor < this.code.length && this.peek() !== ` 503 | ` && !(this.peek() === "\r" && this.peek(1) === ` 504 | `); ) 505 | i += this.consume(); 506 | const { line: r, col: l } = this; 507 | return { 508 | kind: "Comment", 509 | value: i, 510 | multiline: !1, 511 | loc: { 512 | start: { line: e, col: t }, 513 | end: { line: r, col: l } 514 | } 515 | }; 516 | } 517 | lexMultilineComment() { 518 | const { line: e, col: t } = this; 519 | let i = ""; 520 | for (this.consume(), this.consume(); this.cursor < this.code.length && !(this.peek() === "*" && this.peek(1) === "/"); ) 521 | i += this.consume(); 522 | this.consume(), this.consume(); 523 | const { line: r, col: l } = this; 524 | return { 525 | kind: "Comment", 526 | value: i, 527 | multiline: !0, 528 | loc: { 529 | start: { line: e, col: t }, 530 | end: { line: r, col: l } 531 | } 532 | }; 533 | } 534 | } 535 | var L = /* @__PURE__ */ ((n) => (n.Root = "Root", n.Comment = "Comment", n.Expr = "Expr", n.UnaryExpr = "UnaryExpr", n.BinaryExpr = "BinaryExpr", n.SubExpr = "SubExpr", n.Conditional = "Conditional", n.Modifier = "Modifier", n.LetIn = "LetIn", n.Import = "Import", n.Fallback = "Fallback", n.Identifier = "Identifier", n.Null = "Null", n.Int = "Int", n.Float = "Float", n.Bool = "Bool", n.String = "String", n.Path = "Path", n.Attrs = "Attrs", n.Attr = "Attr", n.List = "List", n.Fn = "Fn", n.FnParams = "FnParams", n.FnParam = "FnParam", n.FnCall = "FnCall", n.Has = "Has", n.Eq = "Eq", n.EqEq = "EqEq", n.NotEq = "NotEq", n.Not = "Not", n.Lt = "Lt", n.Lte = "Lte", n.Gt = "Gt", n.Gte = "Gte", n.Add = "Add", n.Sub = "Sub", n.Mul = "Mul", n.Div = "Div", n.Imp = "Imp", n.Update = "Update", n.Concat = "Concat", n.Or = "Or", n.And = "And", n.Period = "Period", n.Interp = "Interp", n))(L || {}); 536 | const I = (n) => n.hasOwnProperty("name"), S = (n) => !I(n), O = (n) => n.hasOwnProperty("name"), A = (n) => n.hasOwnProperty("value"), P = { 537 | [s.Period]: 1, 538 | [s.Has]: 4, 539 | [s.Concat]: 5, 540 | [s.Mul]: 6, 541 | [s.Div]: 6, 542 | [s.Sub]: 7, 543 | [s.Add]: 7, 544 | [s.Update]: 9, 545 | [s.Lt]: 10, 546 | [s.Lte]: 10, 547 | [s.Gt]: 10, 548 | [s.Gte]: 10, 549 | [s.EqEq]: 11, 550 | [s.NotEq]: 11, 551 | [s.And]: 12, 552 | [s.Or]: 13, 553 | [s.Imp]: 13 554 | }; 555 | class w { 556 | constructor() { 557 | k(this, "lexer", new g()); 558 | k(this, "cursor", 0); 559 | k(this, "state", []); 560 | k(this, "tokens", []); 561 | } 562 | isOperator(e) { 563 | return e.kind === s.Add || e.kind === s.Sub || e.kind === s.Mul || e.kind === s.Div || e.kind === s.Eq || e.kind === s.EqEq || e.kind === s.NotEq || e.kind === s.Lt || e.kind === s.Lte || e.kind === s.Gt || e.kind === s.Gte || e.kind === s.Imp || e.kind === s.Update || e.kind === s.Concat || e.kind === s.Or || e.kind === s.And || e.kind === s.Period || e.kind === s.Has; 564 | } 565 | getOperatorPrecedence(e) { 566 | return P[e.kind] ?? 0; 567 | } 568 | getOpNode(e) { 569 | let t; 570 | switch (e.kind) { 571 | case s.Has: 572 | t = "Has"; 573 | break; 574 | case s.Eq: 575 | t = "Eq"; 576 | break; 577 | case s.EqEq: 578 | t = "EqEq"; 579 | break; 580 | case s.NotEq: 581 | t = "NotEq"; 582 | break; 583 | case s.Lt: 584 | t = "Lt"; 585 | break; 586 | case s.Lte: 587 | t = "Lte"; 588 | break; 589 | case s.Gt: 590 | t = "Gt"; 591 | break; 592 | case s.Gte: 593 | t = "Gte"; 594 | break; 595 | case s.Add: 596 | t = "Add"; 597 | break; 598 | case s.Sub: 599 | t = "Sub"; 600 | break; 601 | case s.Mul: 602 | t = "Mul"; 603 | break; 604 | case s.Div: 605 | t = "Div"; 606 | break; 607 | case s.Imp: 608 | t = "Imp"; 609 | break; 610 | case s.Update: 611 | t = "Update"; 612 | break; 613 | case s.Concat: 614 | t = "Concat"; 615 | break; 616 | case s.Or: 617 | t = "Or"; 618 | break; 619 | case s.And: 620 | t = "And"; 621 | break; 622 | case s.Period: 623 | t = "Period"; 624 | break; 625 | default: 626 | throw new Error(`Unknown op node for token "${e.kind}"`); 627 | } 628 | return { 629 | kind: t, 630 | loc: e.loc 631 | }; 632 | } 633 | lookahead(e) { 634 | const t = this.cursor, i = [...this.state], r = e(); 635 | return this.cursor = t, this.state = i, r; 636 | } 637 | peek(e = 0) { 638 | return this.tokens[this.cursor + e]; 639 | } 640 | consume() { 641 | const e = this.tokens[this.cursor]; 642 | return this.cursor++, e; 643 | } 644 | parse(e) { 645 | const t = this.lexer.lex(e); 646 | return this.parseTokens(t); 647 | } 648 | skipNewLines() { 649 | for (; this.cursor < this.tokens.length && this.peek().kind === s.NewLine; ) 650 | this.consume(); 651 | } 652 | parseTokens(e) { 653 | this.cursor = 0, this.state = [], this.tokens = e; 654 | const t = this.parseExpr(); 655 | return { 656 | kind: "Root", 657 | value: t, 658 | loc: t.loc 659 | }; 660 | } 661 | parseExpr(e = !1, t = !1, i = !1) { 662 | let r = 0, l = this.parseSubExpr(!0, e, t), a; 663 | for (; this.cursor < this.tokens.length && !(e || t); ) { 664 | let c = this.lookahead(() => { 665 | for (; this.cursor < this.tokens.length; ) { 666 | const o = this.peek(); 667 | if (o.kind === s.NewLine || o.kind === s.Comment || o.kind === s.Eof) { 668 | this.consume(); 669 | continue; 670 | } 671 | return o; 672 | } 673 | }); 674 | if (c !== void 0 && c.kind === s.Period) { 675 | this.skipNewLines(), this.consume(); 676 | const o = this.parseSubExpr(e, t, i); 677 | o.kind === "SubExpr" && o.value.kind === "FnCall" && o.value.name.kind === "Identifier" && (o.value.name.value.unshift({ 678 | kind: "Expr", 679 | value: l, 680 | loc: l.loc 681 | }), o.loc.start = l.loc.start), o.kind === "SubExpr" && o.value.kind === "Identifier" && (o.value.value.unshift({ 682 | kind: "Expr", 683 | value: l, 684 | loc: l.loc 685 | }), o.loc.start = l.loc.start), l = o, r = 0; 686 | continue; 687 | } 688 | if (c !== void 0 && i) 689 | break; 690 | if (c !== void 0 && c.kind === s.Keyword && c.value === "or") { 691 | this.skipNewLines(), this.consume(); 692 | const o = this.parseSubExpr(!0, e, t); 693 | if (l.kind !== "BinaryExpr") { 694 | const h = { 695 | kind: "BinaryExpr", 696 | op: { 697 | kind: "Fallback", 698 | loc: c.loc 699 | }, 700 | left: l, 701 | right: o, 702 | loc: { 703 | start: l.loc.start, 704 | end: o.loc.end 705 | } 706 | }; 707 | a = h, l = h; 708 | } else { 709 | const h = { 710 | kind: "BinaryExpr", 711 | op: { 712 | kind: "Fallback", 713 | loc: c.loc 714 | }, 715 | left: a.right, 716 | right: o, 717 | loc: { 718 | start: a.right.loc.start, 719 | end: o.loc.end 720 | } 721 | }; 722 | a.right = h, a = h; 723 | } 724 | r = 1; 725 | continue; 726 | } 727 | if (c !== void 0 && this.isOperator(c)) { 728 | this.skipNewLines(); 729 | const o = this.getOperatorPrecedence(c); 730 | this.consume(); 731 | const h = this.parseSubExpr(!0, e, t); 732 | if (l.kind === "SubExpr" || o > r) { 733 | const p = { 734 | kind: "BinaryExpr", 735 | op: this.getOpNode(c), 736 | left: l, 737 | right: h, 738 | loc: { 739 | start: l.loc.start, 740 | end: h.loc.end 741 | } 742 | }; 743 | a = p, l = p; 744 | } else { 745 | const p = { 746 | kind: "BinaryExpr", 747 | op: this.getOpNode(c), 748 | left: a.right, 749 | right: h, 750 | loc: { 751 | start: a.right.loc.start, 752 | end: h.loc.end 753 | } 754 | }; 755 | a.right = p, a = p; 756 | } 757 | r = o; 758 | continue; 759 | } 760 | break; 761 | } 762 | return { 763 | kind: "Expr", 764 | value: l, 765 | loc: { 766 | start: l.loc.start, 767 | end: l.loc.end 768 | } 769 | }; 770 | } 771 | parseSubExpr(e = !0, t = !1, i = !1) { 772 | const r = [], l = { 773 | before: [], 774 | after: [] 775 | }; 776 | r.push(...this.parseModifiers()), l.before = this.parseComments(), r.push(...this.parseModifiers()); 777 | let a = this.peek(); 778 | const c = a.loc; 779 | let o = !1; 780 | a.kind === s.Sub && (o = !0, this.consume(), a = this.peek()); 781 | let h, p = a.loc; 782 | switch (a.kind) { 783 | case s.Int: { 784 | const u = this.parseInt(o); 785 | p = u.loc, h = { 786 | ...u, 787 | loc: { 788 | start: c.start, 789 | end: u.loc.end 790 | } 791 | }; 792 | break; 793 | } 794 | case s.Float: { 795 | const u = this.parseFloat(o); 796 | p = u.loc, h = { 797 | ...u, 798 | loc: { 799 | start: c.start, 800 | end: u.loc.end 801 | } 802 | }; 803 | break; 804 | } 805 | case s.Bool: { 806 | h = this.parseBool(), p = h.loc; 807 | break; 808 | } 809 | case s.String: { 810 | h = this.parseString(), p = h.loc; 811 | break; 812 | } 813 | case s.Interp: { 814 | h = this.parseInterp(), p = h.loc; 815 | break; 816 | } 817 | case s.OpenBracket: { 818 | h = this.parseList(), p = h.loc; 819 | break; 820 | } 821 | case s.OpenCurly: { 822 | const u = this.tryParseFunction(); 823 | u ? h = u : h = this.parseAttrs(), p = h.loc; 824 | break; 825 | } 826 | case s.Identifier: { 827 | const u = this.tryParseFunction(); 828 | u ? h = u : h = this.parseIdentifier(), p = h.loc; 829 | break; 830 | } 831 | case s.Path: { 832 | h = this.parsePath(), p = h.loc; 833 | break; 834 | } 835 | case s.OpenParen: { 836 | const u = this.consume(), m = this.parseExpr(), C = this.consume(); 837 | h = m, p = { 838 | start: u.loc.start, 839 | end: C.loc.end 840 | }; 841 | break; 842 | } 843 | case s.Keyword: { 844 | h = this.parseKeyword(), p = h.loc; 845 | break; 846 | } 847 | case s.Null: { 848 | h = this.parseNull(), p = h.loc; 849 | break; 850 | } 851 | case s.Not: 852 | return this.parseNot(t); 853 | default: 854 | throw console.log(this.tokens.slice(this.cursor - 10, this.cursor)), console.log("-----"), console.log(a), new Error(`Unexpected token: ${a.kind}`); 855 | } 856 | const d = []; 857 | if ( 858 | // Function args must be their own expression to be parsed as a function call. 859 | !t && // You can't call a function in a list because the list delimiter is whitespace. 860 | !i && // Only certain nodes can be called as functions. 861 | (h.kind === "Expr" || h.kind === "Identifier" || h.kind === "Import") 862 | ) 863 | for (; this.cursor < this.tokens.length; ) { 864 | const u = this.lookahead(() => { 865 | for (; this.cursor < this.tokens.length; ) { 866 | const m = this.peek(); 867 | if (m.kind !== s.NewLine && m.kind !== s.Comment) 868 | return m; 869 | this.consume(); 870 | } 871 | }); 872 | if (u === void 0 || u.kind === s.Eof || u.kind === s.Comma || u.kind === s.Semi || u.kind === s.CloseCurly || u.kind === s.CloseBracket || u.kind === s.CloseParen || u.kind === s.Keyword && u.value !== "rec" || this.isOperator(u)) 873 | break; 874 | d.push(this.parseExpr(!0)); 875 | } 876 | return e && (l.after = this.parseComments()), d.length > 0 ? { 877 | kind: "SubExpr", 878 | value: { 879 | kind: "FnCall", 880 | name: h, 881 | value: d, 882 | loc: { 883 | start: c.start, 884 | end: d[d.length - 1].loc.end 885 | } 886 | }, 887 | modifiers: r, 888 | comments: l, 889 | loc: { 890 | start: c.start, 891 | end: p.end 892 | } 893 | } : { 894 | kind: "SubExpr", 895 | value: h, 896 | modifiers: r, 897 | comments: l, 898 | loc: { 899 | start: c.start, 900 | end: p.end 901 | } 902 | }; 903 | } 904 | parseNot(e = !1) { 905 | const t = this.consume(), i = this.parseExpr(e, !1, !0); 906 | return { 907 | kind: "UnaryExpr", 908 | op: { 909 | kind: "Not", 910 | loc: t.loc 911 | }, 912 | value: i, 913 | loc: { 914 | start: t.loc.start, 915 | end: i.loc.end 916 | } 917 | }; 918 | } 919 | parseInt(e = !1) { 920 | const t = this.consume(); 921 | return { 922 | kind: "Int", 923 | value: e ? -1 * t.value : t.value, 924 | loc: t.loc 925 | }; 926 | } 927 | parseFloat(e = !1) { 928 | const t = this.consume(); 929 | return { 930 | kind: "Float", 931 | value: e ? -1 * t.value : t.value, 932 | loc: t.loc 933 | }; 934 | } 935 | parseBool() { 936 | const e = this.consume(); 937 | return { 938 | kind: "Bool", 939 | value: e.value, 940 | loc: e.loc 941 | }; 942 | } 943 | parseString() { 944 | const e = this.consume(), t = new w(), i = e.value.map((r) => typeof r == "string" ? r : t.parseTokens([r]).value.value.value); 945 | return { 946 | kind: "String", 947 | multiline: e.multiline, 948 | value: i, 949 | loc: e.loc 950 | }; 951 | } 952 | parseInterp() { 953 | const e = this.consume(); 954 | return { 955 | kind: "Interp", 956 | value: new w().parseTokens(e.value).value, 957 | loc: e.loc 958 | }; 959 | } 960 | parseList() { 961 | const e = this.consume(), t = []; 962 | for (; this.cursor < this.tokens.length; ) { 963 | this.skipNewLines(); 964 | const r = this.peek(); 965 | if (r !== void 0 && r.kind === s.CloseBracket) 966 | break; 967 | const l = this.parseExpr(!1, !0); 968 | if (t.length > 0) { 969 | const a = t[t.length - 1]; 970 | l.value.kind === "SubExpr" && a.value.kind === "SubExpr" && a.value.comments.after.length > 0 && (l.value.comments.before = a.value.comments.after, a.value.comments.after = []); 971 | } 972 | t.push(l); 973 | } 974 | const i = this.consume(); 975 | return { 976 | kind: "List", 977 | value: t, 978 | loc: { 979 | start: e.loc.start, 980 | end: i.loc.end 981 | } 982 | }; 983 | } 984 | parseAttrs() { 985 | const e = this.consume(), t = []; 986 | for (; this.cursor < this.tokens.length; ) { 987 | this.skipNewLines(); 988 | const r = this.lookahead(() => { 989 | for (; this.cursor < this.tokens.length; ) { 990 | const a = this.peek(); 991 | if (a.kind !== s.NewLine && a.kind !== s.Comment) 992 | return a; 993 | this.consume(); 994 | } 995 | }); 996 | if ((r == null ? void 0 : r.kind) === s.CloseCurly) { 997 | const a = this.parseComments(); 998 | t.length > 0 && t[t.length - 1].comments.push(...a); 999 | break; 1000 | } 1001 | const l = this.parseAttr(); 1002 | t.push(l); 1003 | } 1004 | const i = this.consume(); 1005 | return { 1006 | kind: "Attrs", 1007 | value: t, 1008 | recursive: !1, 1009 | loc: { 1010 | start: e.loc.start, 1011 | end: i.loc.end 1012 | } 1013 | }; 1014 | } 1015 | parseAttr() { 1016 | const e = this.parseComments(), t = this.peek(); 1017 | if (t.kind === s.Keyword && t.value === "inherit") { 1018 | this.consume(); 1019 | let a; 1020 | const c = []; 1021 | for (this.peek().kind === s.OpenParen && (this.consume(), a = this.parseExpr(), this.consume()); this.cursor < this.tokens.length; ) { 1022 | const o = this.parseComments(); 1023 | if (this.peek().kind === s.Semi) 1024 | break; 1025 | const h = this.parseIdentifier(); 1026 | c.push({ 1027 | ...h, 1028 | comments: o 1029 | }); 1030 | } 1031 | return this.consume(), { 1032 | kind: "Attr", 1033 | from: a, 1034 | comments: e, 1035 | value: c, 1036 | loc: { 1037 | start: t.loc.start, 1038 | end: c[c.length - 1].loc.end 1039 | } 1040 | }; 1041 | } 1042 | const i = this.parseIdentifier(); 1043 | e.push(...this.parseComments()), this.skipNewLines(), this.consume(), this.skipNewLines(); 1044 | const r = this.parseExpr(), l = this.consume(); 1045 | if (l.kind === s.CloseCurly) 1046 | throw new Error("WTF"); 1047 | return { 1048 | kind: "Attr", 1049 | name: i, 1050 | value: r, 1051 | comments: e, 1052 | loc: { 1053 | start: i.loc.start, 1054 | end: l.loc.end 1055 | } 1056 | }; 1057 | } 1058 | parseIdentifier() { 1059 | var r; 1060 | const e = [], t = this.peek().loc; 1061 | let i = t; 1062 | for (; this.cursor < this.tokens.length; ) { 1063 | const l = this.peek(); 1064 | switch (i = l.loc, l.kind) { 1065 | case s.Identifier: 1066 | this.consume(), e.push(l.value); 1067 | break; 1068 | case s.String: 1069 | e.push(this.parseString()); 1070 | break; 1071 | case s.Interp: 1072 | e.push(this.parseInterp()); 1073 | break; 1074 | default: 1075 | throw console.log(this.tokens.slice(this.cursor - 10, this.cursor)), console.log("-----"), console.log(l), new Error(`Unknown token in identifier path: "${l.kind}"`); 1076 | } 1077 | if (((r = this.peek()) == null ? void 0 : r.kind) !== s.Period) 1078 | break; 1079 | this.consume(); 1080 | } 1081 | return { 1082 | kind: "Identifier", 1083 | value: e, 1084 | loc: { 1085 | start: t.start, 1086 | end: i.end 1087 | } 1088 | }; 1089 | } 1090 | parseComments() { 1091 | const e = []; 1092 | for (; this.cursor < this.tokens.length; ) { 1093 | this.skipNewLines(); 1094 | const t = this.peek(); 1095 | if (t.kind !== s.Comment) 1096 | break; 1097 | e.push({ 1098 | kind: "Comment", 1099 | value: t.value, 1100 | loc: t.loc 1101 | }), this.consume(); 1102 | } 1103 | return e; 1104 | } 1105 | tryParseFunction() { 1106 | var l, a; 1107 | if (this.lookahead(() => { 1108 | const c = this.peek(), o = this.peek(1); 1109 | return (c == null ? void 0 : c.kind) === s.Identifier && (o == null ? void 0 : o.kind) === s.Colon; 1110 | })) { 1111 | const c = this.parseIdentifier(); 1112 | this.consume(); 1113 | const o = this.parseExpr(); 1114 | return { 1115 | kind: "Fn", 1116 | args: { 1117 | kind: "FnParams", 1118 | name: c, 1119 | loc: c.loc 1120 | }, 1121 | body: o, 1122 | loc: { 1123 | start: c.loc.start, 1124 | end: o.loc.end 1125 | } 1126 | }; 1127 | } 1128 | const t = this.lookahead(() => { 1129 | const c = this.peek(), o = this.peek(1); 1130 | return (c == null ? void 0 : c.kind) === s.Identifier && (o == null ? void 0 : o.kind) === s.At; 1131 | }); 1132 | let i; 1133 | if (t && (i = this.parseIdentifier(), this.consume()), this.lookahead(() => { 1134 | var d, u; 1135 | let c = this.peek(); 1136 | if (c.kind !== s.OpenCurly) 1137 | return !1; 1138 | this.consume(); 1139 | let o = 0; 1140 | for (; this.cursor < this.tokens.length && (c = this.peek(), c.kind === s.OpenCurly && o++, c.kind === s.CloseCurly && o--, this.consume(), o !== -1); ) 1141 | ; 1142 | const h = ((d = this.peek()) == null ? void 0 : d.kind) === s.Colon, p = ((u = this.peek()) == null ? void 0 : u.kind) === s.At; 1143 | return t || h ? h : p; 1144 | })) { 1145 | const c = { 1146 | kind: "FnParams", 1147 | as: i, 1148 | value: [], 1149 | extra: !1 1150 | }, o = this.consume(); 1151 | for (; this.cursor < this.tokens.length; ) { 1152 | const u = this.peek(); 1153 | if (this.skipNewLines(), u.kind === s.CloseCurly) 1154 | break; 1155 | if (u.kind === s.Ellipsis) { 1156 | this.consume(), c.extra = !0, this.skipNewLines(); 1157 | break; 1158 | } 1159 | const m = this.parseComments(), C = this.parseIdentifier(); 1160 | let b; 1161 | ((l = this.peek()) == null ? void 0 : l.kind) === s.Has && (this.consume(), b = this.parseExpr()), this.parseComments(), c.value.push({ 1162 | kind: "FnParam", 1163 | name: C, 1164 | default: b, 1165 | comments: m, 1166 | loc: { 1167 | start: C.loc.start, 1168 | end: b !== void 0 ? b.loc.end : C.loc.end 1169 | } 1170 | }); 1171 | const x = this.lookahead(() => { 1172 | for (; this.cursor < this.tokens.length; ) { 1173 | const f = this.peek(); 1174 | if ((f == null ? void 0 : f.kind) !== s.NewLine && (f == null ? void 0 : f.kind) !== s.Comment) 1175 | return f; 1176 | this.consume(); 1177 | } 1178 | }); 1179 | if (x === void 0) 1180 | throw new Error("Unexpected end of input."); 1181 | if (x.kind !== s.Comma && x.kind !== s.CloseCurly) 1182 | throw new Error(`Expected comma or closed curly brace, but got: "${x.kind}"`); 1183 | this.skipNewLines(), x.kind === s.Comma && this.consume(), this.skipNewLines(); 1184 | } 1185 | const h = this.consume(), p = ((a = this.peek()) == null ? void 0 : a.kind) === s.At; 1186 | p && (this.consume(), c.as = this.parseIdentifier()), p ? c.loc = { 1187 | start: o.loc.start, 1188 | end: c.as.loc.end 1189 | } : c.as ? c.loc = { 1190 | start: c.as.loc.start, 1191 | end: h.loc.end 1192 | } : c.loc = { 1193 | start: o.loc.start, 1194 | end: h.loc.end 1195 | }, this.consume(); 1196 | const d = this.parseExpr(); 1197 | return { 1198 | kind: "Fn", 1199 | args: c, 1200 | body: d, 1201 | loc: { 1202 | start: c.loc.start, 1203 | end: d.loc.end 1204 | } 1205 | }; 1206 | } 1207 | } 1208 | parseModifiers() { 1209 | const e = []; 1210 | for (; this.cursor < this.tokens.length; ) { 1211 | this.skipNewLines(); 1212 | const t = this.peek(); 1213 | if ((t == null ? void 0 : t.kind) !== s.Keyword || (t == null ? void 0 : t.value) !== "with" && (t == null ? void 0 : t.value) !== "assert") 1214 | break; 1215 | switch (t.value) { 1216 | case "with": { 1217 | this.consume(); 1218 | const i = this.parseExpr(), r = this.consume(); 1219 | e.push({ 1220 | kind: "Modifier", 1221 | action: "with", 1222 | value: i, 1223 | loc: { 1224 | start: t.loc.start, 1225 | end: r.loc.end 1226 | } 1227 | }); 1228 | break; 1229 | } 1230 | case "assert": { 1231 | this.consume(); 1232 | const i = this.parseExpr(), r = this.consume(); 1233 | e.push({ 1234 | kind: "Modifier", 1235 | action: "assert", 1236 | value: i, 1237 | loc: { 1238 | start: t.loc.start, 1239 | end: r.loc.end 1240 | } 1241 | }); 1242 | break; 1243 | } 1244 | } 1245 | } 1246 | return e; 1247 | } 1248 | parsePath() { 1249 | const e = this.consume(); 1250 | return { 1251 | kind: "Path", 1252 | value: e.value, 1253 | loc: e.loc 1254 | }; 1255 | } 1256 | parseKeyword() { 1257 | const e = this.peek(); 1258 | switch (e.value) { 1259 | case "let": { 1260 | this.consume(); 1261 | const t = []; 1262 | for (; this.cursor < this.tokens.length; ) { 1263 | this.skipNewLines(); 1264 | const r = this.lookahead(() => { 1265 | for (; this.cursor < this.tokens.length; ) { 1266 | const a = this.peek(); 1267 | if (a.kind !== s.NewLine && a.kind !== s.Comment) 1268 | return a; 1269 | this.consume(); 1270 | } 1271 | }); 1272 | if (r !== void 0 && (r == null ? void 0 : r.kind) === s.Keyword && r.value === "in") { 1273 | const a = this.parseComments(); 1274 | t.length > 0 && t[t.length - 1].comments.push(...a); 1275 | break; 1276 | } 1277 | const l = this.parseAttr(); 1278 | t.push(l); 1279 | } 1280 | this.consume(); 1281 | const i = this.parseExpr(); 1282 | return { 1283 | kind: "LetIn", 1284 | bindings: t, 1285 | body: i, 1286 | loc: { 1287 | start: e.loc.start, 1288 | end: i.loc.end 1289 | } 1290 | }; 1291 | } 1292 | case "import": { 1293 | this.consume(); 1294 | const t = this.parseExpr(!0); 1295 | return { 1296 | kind: "Import", 1297 | value: t, 1298 | loc: { 1299 | start: e.loc.start, 1300 | end: t.loc.end 1301 | } 1302 | }; 1303 | } 1304 | case "if": { 1305 | this.consume(), this.skipNewLines(); 1306 | const t = this.parseExpr(); 1307 | this.skipNewLines(), this.consume(), this.skipNewLines(); 1308 | const i = this.parseExpr(); 1309 | this.skipNewLines(), this.consume(), this.skipNewLines(); 1310 | const r = this.parseExpr(); 1311 | return { 1312 | kind: "Conditional", 1313 | condition: t, 1314 | then: i, 1315 | else: r, 1316 | loc: { 1317 | start: e.loc.start, 1318 | end: r.loc.end 1319 | } 1320 | }; 1321 | } 1322 | case "rec": { 1323 | this.consume(); 1324 | const t = this.parseAttrs(); 1325 | return t.recursive = !0, t.loc.start = e.loc.start, t; 1326 | } 1327 | default: 1328 | throw new Error(`Unexpected keyword: ${e.value}`); 1329 | } 1330 | } 1331 | parseNull() { 1332 | return { 1333 | kind: "Null", 1334 | loc: this.consume().loc 1335 | }; 1336 | } 1337 | } 1338 | export { 1339 | g as Lexer, 1340 | L as NodeKind, 1341 | w as Parser, 1342 | s as TokenKind, 1343 | I as isAttrBinding, 1344 | A as isDestructuredFnParams, 1345 | O as isIdentifierFnParams, 1346 | S as isInheritBinding, 1347 | P as precedence 1348 | }; 1349 | -------------------------------------------------------------------------------- /shells/default/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | mkShell, 3 | bun, 4 | }: 5 | mkShell { 6 | packages = [bun]; 7 | } 8 | --------------------------------------------------------------------------------