@doki-land/nifty 0.0.2 → 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/README.md +29 -0
  2. package/cli/nifty.mjs +11 -11
  3. package/locales/en-US/cli.json +68 -64
  4. package/locales/locales.json +4 -4
  5. package/package.json +20 -16
  6. package/src/cli/authArgs.ts +52 -52
  7. package/src/cli/bump-cmd.ts +55 -55
  8. package/src/cli/bump.ts +237 -237
  9. package/src/cli/change-logs-cmd.ts +97 -97
  10. package/src/cli/cli.ts +47 -45
  11. package/src/cli/context.ts +36 -36
  12. package/src/cli/format-cmd.ts +23 -0
  13. package/src/cli/format.ts +28 -0
  14. package/src/cli/index.ts +9 -9
  15. package/src/cli/install-native-cmd.ts +22 -22
  16. package/src/cli/install-native.ts +80 -80
  17. package/src/cli/lint-cmd.ts +35 -33
  18. package/src/cli/lint.ts +102 -113
  19. package/src/cli/main.ts +1 -1
  20. package/src/cli/options.ts +40 -40
  21. package/src/cli/publish-cmd.ts +38 -34
  22. package/src/cli/publish.ts +95 -95
  23. package/src/cli/resolveNpm.ts +16 -16
  24. package/src/cli/retime-cmd.ts +62 -62
  25. package/src/cli/reword-cmd.ts +87 -87
  26. package/src/cli/trust-cmd.ts +38 -38
  27. package/src/cli/trust.ts +83 -83
  28. package/src/cli/update-cmd.ts +20 -20
  29. package/src/cli/update.ts +32 -185
  30. package/src/cli/upload-cmd.ts +44 -44
  31. package/src/cli/upload.ts +313 -313
  32. package/src/cli/workspace.ts +124 -124
  33. package/src/config/defineConfig.ts +12 -12
  34. package/src/config/detectProject.ts +189 -189
  35. package/src/config/index.ts +25 -25
  36. package/src/config/loadConfig.ts +129 -129
  37. package/src/config/types.ts +57 -57
  38. package/src/git.ts +61 -61
  39. package/src/github.ts +184 -184
  40. package/src/gitmoji.ts +77 -77
  41. package/src/index.ts +36 -36
  42. package/src/native.ts +665 -507
  43. package/src/nifty.ts +95 -95
  44. package/src/types.ts +40 -40
package/src/github.ts CHANGED
@@ -1,184 +1,184 @@
1
- import type { GithubAuthor } from "./types.js";
2
-
3
- const USER_AGENT = "nifty-github";
4
- const ACCEPT = "application/vnd.github+json";
5
-
6
- function parseUserJson(body: Record<string, unknown>, fallbackLogin?: string): GithubAuthor {
7
- const id = typeof body.id === "number" ? BigInt(body.id) : undefined;
8
- const login = typeof body.login === "string" ? body.login : fallbackLogin;
9
- if (id === undefined && login === undefined) {
10
- throw new Error("GitHub user response missing id and login");
11
- }
12
- return { id, login };
13
- }
14
-
15
- async function getJson(url: string, token?: string): Promise<Record<string, unknown>> {
16
- const headers: Record<string, string> = {
17
- Accept: ACCEPT,
18
- "User-Agent": USER_AGENT,
19
- };
20
- if (token?.trim()) {
21
- headers.Authorization = `Bearer ${token.trim()}`;
22
- }
23
- const response = await fetch(url, { headers });
24
- if (response.status === 404) {
25
- throw new Error("GitHub resource not found");
26
- }
27
- if (!response.ok) {
28
- throw new Error(`GitHub API error: ${response.status} ${response.statusText}`);
29
- }
30
- return (await response.json()) as Record<string, unknown>;
31
- }
32
-
33
- function parseNoreplyEmail(email: string): GithubAuthor | undefined {
34
- const trimmed = email.trim();
35
- const suffix = "@users.noreply.github.com";
36
- const lower = trimmed.toLowerCase();
37
- if (!lower.endsWith(suffix)) {
38
- return undefined;
39
- }
40
- const local = trimmed.slice(0, -suffix.length);
41
- const plus = local.indexOf("+");
42
- if (plus >= 0) {
43
- const idPart = local.slice(0, plus);
44
- const login = local.slice(plus + 1);
45
- if (!login) {
46
- return undefined;
47
- }
48
- return { id: BigInt(idPart), login };
49
- }
50
- if (!local) {
51
- return undefined;
52
- }
53
- return { login: local };
54
- }
55
-
56
- function loadAuthorMap(json: string): Map<string, GithubAuthor> {
57
- const map = new Map<string, GithubAuthor>();
58
- if (!json.trim()) {
59
- return map;
60
- }
61
- const parsed = JSON.parse(json) as Record<string, { id?: number; login?: string }>;
62
- for (const [email, entry] of Object.entries(parsed)) {
63
- const author: GithubAuthor = {};
64
- if (typeof entry.id === "number") {
65
- author.id = BigInt(entry.id);
66
- }
67
- if (typeof entry.login === "string" && entry.login.trim()) {
68
- author.login = entry.login.trim();
69
- }
70
- if (author.id !== undefined || author.login !== undefined) {
71
- map.set(email.trim().toLowerCase(), author);
72
- }
73
- }
74
- return map;
75
- }
76
-
77
- function mergeAuthors(existing: GithubAuthor, incoming: GithubAuthor): GithubAuthor {
78
- return {
79
- id: existing.id ?? incoming.id,
80
- login: existing.login ?? incoming.login,
81
- };
82
- }
83
-
84
- async function enrichAuthor(author: GithubAuthor, token?: string, fetch = false): Promise<GithubAuthor> {
85
- if (!fetch && author.id !== undefined && author.login !== undefined) {
86
- return author;
87
- }
88
- if (!fetch && author.id !== undefined) {
89
- return author;
90
- }
91
- if (author.login && (author.id === undefined || fetch)) {
92
- const fetched = await userByLoginFetch(author.login, token);
93
- return mergeAuthors(author, fetched);
94
- }
95
- return author;
96
- }
97
-
98
- async function userByLoginFetch(login: string, token?: string): Promise<GithubAuthor> {
99
- const trimmed = login.trim();
100
- if (!trimmed) {
101
- throw new Error("login must not be empty");
102
- }
103
- const body = await getJson(`https://api.github.com/users/${trimmed}`, token);
104
- return parseUserJson(body, trimmed);
105
- }
106
-
107
- async function searchUserByEmailFetch(email: string, token: string): Promise<GithubAuthor | undefined> {
108
- const trimmed = email.trim();
109
- const auth = token.trim();
110
- if (!trimmed) {
111
- throw new Error("email must not be empty");
112
- }
113
- if (!auth) {
114
- throw new Error("GitHub token is required for email search");
115
- }
116
- const query = encodeURIComponent(`${trimmed} in:email`);
117
- const body = await getJson(`https://api.github.com/search/users?q=${query}`, auth);
118
- const items = body.items;
119
- if (!Array.isArray(items) || items.length === 0) {
120
- return undefined;
121
- }
122
- const first = items[0] as Record<string, unknown>;
123
- return parseUserJson(first);
124
- }
125
-
126
- export type LookupUserOptions = {
127
- authorMapJson?: string;
128
- token?: string;
129
- fetch?: boolean;
130
- };
131
-
132
- /**
133
- * GitHub REST helpers via host `fetch` (Node 18+, browsers with CORS).
134
- * Rust equivalent: `nifty-github` crate.
135
- */
136
- export class Github {
137
- constructor(private readonly token?: string) {}
138
-
139
- static open(token?: string): Github {
140
- return new Github(token);
141
- }
142
-
143
- async userByLogin(login: string, token?: string): Promise<GithubAuthor> {
144
- return userByLoginFetch(login, token ?? this.token);
145
- }
146
-
147
- async searchUserByEmail(email: string, token?: string): Promise<GithubAuthor | undefined> {
148
- const auth = token ?? this.token;
149
- if (!auth) {
150
- throw new Error("GitHub token is required for email search");
151
- }
152
- return searchUserByEmailFetch(email, auth);
153
- }
154
-
155
- async lookupUserByEmail(email: string, options: LookupUserOptions = {}): Promise<GithubAuthor | undefined> {
156
- const token = options.token ?? this.token;
157
- const fetch = options.fetch ?? false;
158
- const mapJson = options.authorMapJson ?? "{}";
159
-
160
- const fromNoreply = parseNoreplyEmail(email);
161
- if (fromNoreply) {
162
- return enrichAuthor(fromNoreply, token, fetch);
163
- }
164
-
165
- const map = loadAuthorMap(mapJson);
166
- const mapped = map.get(email.trim().toLowerCase());
167
- if (mapped) {
168
- return enrichAuthor(mapped, token, fetch);
169
- }
170
-
171
- if (token) {
172
- const found = await searchUserByEmailFetch(email, token);
173
- if (found) {
174
- return enrichAuthor(found, token, fetch);
175
- }
176
- }
177
-
178
- return undefined;
179
- }
180
- }
181
-
182
- export function createGithub(token?: string): Github {
183
- return Github.open(token);
184
- }
1
+ import type { GithubAuthor } from "./types.js";
2
+
3
+ const USER_AGENT = "nifty-github";
4
+ const ACCEPT = "application/vnd.github+json";
5
+
6
+ function parseUserJson(body: Record<string, unknown>, fallbackLogin?: string): GithubAuthor {
7
+ const id = typeof body.id === "number" ? BigInt(body.id) : undefined;
8
+ const login = typeof body.login === "string" ? body.login : fallbackLogin;
9
+ if (id === undefined && login === undefined) {
10
+ throw new Error("GitHub user response missing id and login");
11
+ }
12
+ return { id, login };
13
+ }
14
+
15
+ async function getJson(url: string, token?: string): Promise<Record<string, unknown>> {
16
+ const headers: Record<string, string> = {
17
+ Accept: ACCEPT,
18
+ "User-Agent": USER_AGENT,
19
+ };
20
+ if (token?.trim()) {
21
+ headers.Authorization = `Bearer ${token.trim()}`;
22
+ }
23
+ const response = await fetch(url, { headers });
24
+ if (response.status === 404) {
25
+ throw new Error("GitHub resource not found");
26
+ }
27
+ if (!response.ok) {
28
+ throw new Error(`GitHub API error: ${response.status} ${response.statusText}`);
29
+ }
30
+ return (await response.json()) as Record<string, unknown>;
31
+ }
32
+
33
+ function parseNoreplyEmail(email: string): GithubAuthor | undefined {
34
+ const trimmed = email.trim();
35
+ const suffix = "@users.noreply.github.com";
36
+ const lower = trimmed.toLowerCase();
37
+ if (!lower.endsWith(suffix)) {
38
+ return undefined;
39
+ }
40
+ const local = trimmed.slice(0, -suffix.length);
41
+ const plus = local.indexOf("+");
42
+ if (plus >= 0) {
43
+ const idPart = local.slice(0, plus);
44
+ const login = local.slice(plus + 1);
45
+ if (!login) {
46
+ return undefined;
47
+ }
48
+ return { id: BigInt(idPart), login };
49
+ }
50
+ if (!local) {
51
+ return undefined;
52
+ }
53
+ return { login: local };
54
+ }
55
+
56
+ function loadAuthorMap(json: string): Map<string, GithubAuthor> {
57
+ const map = new Map<string, GithubAuthor>();
58
+ if (!json.trim()) {
59
+ return map;
60
+ }
61
+ const parsed = JSON.parse(json) as Record<string, { id?: number; login?: string }>;
62
+ for (const [email, entry] of Object.entries(parsed)) {
63
+ const author: GithubAuthor = {};
64
+ if (typeof entry.id === "number") {
65
+ author.id = BigInt(entry.id);
66
+ }
67
+ if (typeof entry.login === "string" && entry.login.trim()) {
68
+ author.login = entry.login.trim();
69
+ }
70
+ if (author.id !== undefined || author.login !== undefined) {
71
+ map.set(email.trim().toLowerCase(), author);
72
+ }
73
+ }
74
+ return map;
75
+ }
76
+
77
+ function mergeAuthors(existing: GithubAuthor, incoming: GithubAuthor): GithubAuthor {
78
+ return {
79
+ id: existing.id ?? incoming.id,
80
+ login: existing.login ?? incoming.login,
81
+ };
82
+ }
83
+
84
+ async function enrichAuthor(author: GithubAuthor, token?: string, fetch = false): Promise<GithubAuthor> {
85
+ if (!fetch && author.id !== undefined && author.login !== undefined) {
86
+ return author;
87
+ }
88
+ if (!fetch && author.id !== undefined) {
89
+ return author;
90
+ }
91
+ if (author.login && (author.id === undefined || fetch)) {
92
+ const fetched = await userByLoginFetch(author.login, token);
93
+ return mergeAuthors(author, fetched);
94
+ }
95
+ return author;
96
+ }
97
+
98
+ async function userByLoginFetch(login: string, token?: string): Promise<GithubAuthor> {
99
+ const trimmed = login.trim();
100
+ if (!trimmed) {
101
+ throw new Error("login must not be empty");
102
+ }
103
+ const body = await getJson(`https://api.github.com/users/${trimmed}`, token);
104
+ return parseUserJson(body, trimmed);
105
+ }
106
+
107
+ async function searchUserByEmailFetch(email: string, token: string): Promise<GithubAuthor | undefined> {
108
+ const trimmed = email.trim();
109
+ const auth = token.trim();
110
+ if (!trimmed) {
111
+ throw new Error("email must not be empty");
112
+ }
113
+ if (!auth) {
114
+ throw new Error("GitHub token is required for email search");
115
+ }
116
+ const query = encodeURIComponent(`${trimmed} in:email`);
117
+ const body = await getJson(`https://api.github.com/search/users?q=${query}`, auth);
118
+ const items = body.items;
119
+ if (!Array.isArray(items) || items.length === 0) {
120
+ return undefined;
121
+ }
122
+ const first = items[0] as Record<string, unknown>;
123
+ return parseUserJson(first);
124
+ }
125
+
126
+ export type LookupUserOptions = {
127
+ authorMapJson?: string;
128
+ token?: string;
129
+ fetch?: boolean;
130
+ };
131
+
132
+ /**
133
+ * GitHub REST helpers via host `fetch` (Node 18+, browsers with CORS).
134
+ * Rust equivalent: `nifty-github` crate.
135
+ */
136
+ export class Github {
137
+ constructor(private readonly token?: string) {}
138
+
139
+ static open(token?: string): Github {
140
+ return new Github(token);
141
+ }
142
+
143
+ async userByLogin(login: string, token?: string): Promise<GithubAuthor> {
144
+ return userByLoginFetch(login, token ?? this.token);
145
+ }
146
+
147
+ async searchUserByEmail(email: string, token?: string): Promise<GithubAuthor | undefined> {
148
+ const auth = token ?? this.token;
149
+ if (!auth) {
150
+ throw new Error("GitHub token is required for email search");
151
+ }
152
+ return searchUserByEmailFetch(email, auth);
153
+ }
154
+
155
+ async lookupUserByEmail(email: string, options: LookupUserOptions = {}): Promise<GithubAuthor | undefined> {
156
+ const token = options.token ?? this.token;
157
+ const fetch = options.fetch ?? false;
158
+ const mapJson = options.authorMapJson ?? "{}";
159
+
160
+ const fromNoreply = parseNoreplyEmail(email);
161
+ if (fromNoreply) {
162
+ return enrichAuthor(fromNoreply, token, fetch);
163
+ }
164
+
165
+ const map = loadAuthorMap(mapJson);
166
+ const mapped = map.get(email.trim().toLowerCase());
167
+ if (mapped) {
168
+ return enrichAuthor(mapped, token, fetch);
169
+ }
170
+
171
+ if (token) {
172
+ const found = await searchUserByEmailFetch(email, token);
173
+ if (found) {
174
+ return enrichAuthor(found, token, fetch);
175
+ }
176
+ }
177
+
178
+ return undefined;
179
+ }
180
+ }
181
+
182
+ export function createGithub(token?: string): Github {
183
+ return Github.open(token);
184
+ }
package/src/gitmoji.ts CHANGED
@@ -1,77 +1,77 @@
1
- import type { CommitRecord, GithubAuthor, ParsedSubject, RangeInfo, ReleaseSection, TagInfo } from "./types.js";
2
- import { loadNiftyNative, type GitmojiExports } from "./native.js";
3
-
4
- /**
5
- * Nifty default convention: every commit subject starts with a gitmoji followed by a space.
6
- */
7
- export class Gitmoji {
8
- constructor(
9
- private readonly wasm: GitmojiExports,
10
- private readonly defaultAuthorMapJson = "{}",
11
- ) {}
12
-
13
- static open(authorMapJson = "{}"): Gitmoji {
14
- const native = loadNiftyNative();
15
- return new Gitmoji(native.gitmoji, authorMapJson);
16
- }
17
-
18
- knownGitmojis(): string[] {
19
- return this.wasm["known-gitmojis"]();
20
- }
21
-
22
- validateSubject(subject: string): boolean {
23
- return this.wasm["validate-subject"](subject);
24
- }
25
-
26
- parseSubject(subject: string): ParsedSubject {
27
- return this.wasm["parse-subject"](subject);
28
- }
29
-
30
- formatSubject(gitmoji: string, body: string): string {
31
- return this.wasm["format-subject"](gitmoji, body);
32
- }
33
-
34
- stripGitmoji(subject: string): string {
35
- return this.wasm["strip-gitmoji"](subject);
36
- }
37
-
38
- leadingGitmoji(subject: string): string | undefined {
39
- return this.wasm["leading-gitmoji"](subject);
40
- }
41
-
42
- sectionForGitmoji(gitmoji?: string): ReleaseSection {
43
- return this.wasm["section-for-gitmoji"](gitmoji);
44
- }
45
-
46
- parseNoreplyEmail(email: string): GithubAuthor | undefined {
47
- return this.wasm["parse-noreply-email"](email);
48
- }
49
-
50
- resolveGithubAuthor(email: string, authorMapJson = this.defaultAuthorMapJson): GithubAuthor | undefined {
51
- return this.wasm["resolve-github-author"](email, authorMapJson);
52
- }
53
-
54
- displayLogin(author: GithubAuthor): string {
55
- return this.wasm["display-login"](author);
56
- }
57
-
58
- profileUrl(author: GithubAuthor): string {
59
- return this.wasm["profile-url"](author);
60
- }
61
-
62
- avatarUrl(author: GithubAuthor): string {
63
- return this.wasm["avatar-url"](author);
64
- }
65
-
66
- authorMention(email: string, authorName: string, authorMapJson = this.defaultAuthorMapJson): string {
67
- return this.wasm["author-mention"](email, authorName, authorMapJson);
68
- }
69
-
70
- commitBullet(body: string, email: string, authorName: string, authorMapJson = this.defaultAuthorMapJson): string {
71
- return this.wasm["commit-bullet"](body, email, authorName, authorMapJson);
72
- }
73
- }
74
-
75
- export function createGitmoji(): Gitmoji {
76
- return Gitmoji.open();
77
- }
1
+ import type { CommitRecord, GithubAuthor, ParsedSubject, RangeInfo, ReleaseSection, TagInfo } from "./types.js";
2
+ import { loadNiftyNative, type GitmojiExports } from "./native.js";
3
+
4
+ /**
5
+ * Nifty default convention: every commit subject starts with a gitmoji followed by a space.
6
+ */
7
+ export class Gitmoji {
8
+ constructor(
9
+ private readonly wasm: GitmojiExports,
10
+ private readonly defaultAuthorMapJson = "{}",
11
+ ) {}
12
+
13
+ static open(authorMapJson = "{}"): Gitmoji {
14
+ const native = loadNiftyNative();
15
+ return new Gitmoji(native.gitmoji, authorMapJson);
16
+ }
17
+
18
+ knownGitmojis(): string[] {
19
+ return this.wasm["known-gitmojis"]();
20
+ }
21
+
22
+ validateSubject(subject: string): boolean {
23
+ return this.wasm["validate-subject"](subject);
24
+ }
25
+
26
+ parseSubject(subject: string): ParsedSubject {
27
+ return this.wasm["parse-subject"](subject);
28
+ }
29
+
30
+ formatSubject(gitmoji: string, body: string): string {
31
+ return this.wasm["format-subject"](gitmoji, body);
32
+ }
33
+
34
+ stripGitmoji(subject: string): string {
35
+ return this.wasm["strip-gitmoji"](subject);
36
+ }
37
+
38
+ leadingGitmoji(subject: string): string | undefined {
39
+ return this.wasm["leading-gitmoji"](subject);
40
+ }
41
+
42
+ sectionForGitmoji(gitmoji?: string): ReleaseSection {
43
+ return this.wasm["section-for-gitmoji"](gitmoji);
44
+ }
45
+
46
+ parseNoreplyEmail(email: string): GithubAuthor | undefined {
47
+ return this.wasm["parse-noreply-email"](email);
48
+ }
49
+
50
+ resolveGithubAuthor(email: string, authorMapJson = this.defaultAuthorMapJson): GithubAuthor | undefined {
51
+ return this.wasm["resolve-github-author"](email, authorMapJson);
52
+ }
53
+
54
+ displayLogin(author: GithubAuthor): string {
55
+ return this.wasm["display-login"](author);
56
+ }
57
+
58
+ profileUrl(author: GithubAuthor): string {
59
+ return this.wasm["profile-url"](author);
60
+ }
61
+
62
+ avatarUrl(author: GithubAuthor): string {
63
+ return this.wasm["avatar-url"](author);
64
+ }
65
+
66
+ authorMention(email: string, authorName: string, authorMapJson = this.defaultAuthorMapJson): string {
67
+ return this.wasm["author-mention"](email, authorName, authorMapJson);
68
+ }
69
+
70
+ commitBullet(body: string, email: string, authorName: string, authorMapJson = this.defaultAuthorMapJson): string {
71
+ return this.wasm["commit-bullet"](body, email, authorName, authorMapJson);
72
+ }
73
+ }
74
+
75
+ export function createGitmoji(): Gitmoji {
76
+ return Gitmoji.open();
77
+ }
package/src/index.ts CHANGED
@@ -1,36 +1,36 @@
1
- /**
2
- * @doki-land/nifty — gitmoji-first commit conventions and gix-backed git helpers.
3
- *
4
- * Build native bindings first: `npm run build:napi` at the repo root.
5
- * CLI (`nifty update`, `nifty lint`, `nifty upload`) is provided via the `nifty` bin.
6
- */
7
-
8
- export type {
9
- CommitRecord,
10
- GithubAuthor,
11
- ParsedSubject,
12
- RangeInfo,
13
- ReleaseSection,
14
- TagInfo,
15
- } from "./types.js";
16
- export { Git, createGit, type ResolveRangeOptions } from "./git.js";
17
- export { Github, createGithub, type LookupUserOptions } from "./github.js";
18
- export { Gitmoji, createGitmoji } from "./gitmoji.js";
19
- export { Nifty, createNifty, type NiftyOpenOptions } from "./nifty.js";
20
- export {
21
- defineConfig,
22
- loadConfig,
23
- findConfigFile,
24
- authorMapToJson,
25
- detectProjectLayout,
26
- discoverPackageDirs,
27
- findCargoManifest,
28
- findPackageManifest,
29
- type LoadConfigOptions,
30
- type LoadedNiftyConfig,
31
- type NiftyAuthorEntry,
32
- type NiftyConfig,
33
- type NiftyConfigEnv,
34
- type ProjectKind,
35
- type ProjectLayout,
36
- } from "./config/index.js";
1
+ /**
2
+ * @doki-land/nifty — gitmoji-first commit conventions and gix-backed git helpers.
3
+ *
4
+ * Build native bindings first: `npm run build:napi` at the repo root.
5
+ * CLI (`nifty update`, `nifty lint`, `nifty upload`) is provided via the `nifty` bin.
6
+ */
7
+
8
+ export type {
9
+ CommitRecord,
10
+ GithubAuthor,
11
+ ParsedSubject,
12
+ RangeInfo,
13
+ ReleaseSection,
14
+ TagInfo,
15
+ } from "./types.js";
16
+ export { Git, createGit, type ResolveRangeOptions } from "./git.js";
17
+ export { Github, createGithub, type LookupUserOptions } from "./github.js";
18
+ export { Gitmoji, createGitmoji } from "./gitmoji.js";
19
+ export { Nifty, createNifty, type NiftyOpenOptions } from "./nifty.js";
20
+ export {
21
+ defineConfig,
22
+ loadConfig,
23
+ findConfigFile,
24
+ authorMapToJson,
25
+ detectProjectLayout,
26
+ discoverPackageDirs,
27
+ findCargoManifest,
28
+ findPackageManifest,
29
+ type LoadConfigOptions,
30
+ type LoadedNiftyConfig,
31
+ type NiftyAuthorEntry,
32
+ type NiftyConfig,
33
+ type NiftyConfigEnv,
34
+ type ProjectKind,
35
+ type ProjectLayout,
36
+ } from "./config/index.js";