fitout 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -5
- package/dist/claude.d.ts +1 -0
- package/dist/claude.js +30 -5
- package/dist/cli.js +0 -0
- package/dist/globalConfig.d.ts +4 -4
- package/dist/globalConfig.js +13 -16
- package/dist/init.d.ts +2 -0
- package/dist/init.js +26 -7
- package/dist/install.js +5 -5
- package/dist/marketplace.d.ts +18 -11
- package/dist/marketplace.js +47 -28
- package/dist/update.js +2 -0
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -17,7 +17,7 @@ Fitout ensures your actual runtime state matches your declared configuration.
|
|
|
17
17
|
|
|
18
18
|
1. Declare desired plugins in `.claude/fitout.toml`
|
|
19
19
|
2. Run `fitout status` to see the diff
|
|
20
|
-
3. Run `fitout
|
|
20
|
+
3. Run `fitout install` to sync
|
|
21
21
|
|
|
22
22
|
## Installation
|
|
23
23
|
|
|
@@ -71,7 +71,7 @@ Context: /path/to/project
|
|
|
71
71
|
Install missing plugins:
|
|
72
72
|
|
|
73
73
|
```bash
|
|
74
|
-
fitout
|
|
74
|
+
fitout install
|
|
75
75
|
```
|
|
76
76
|
|
|
77
77
|
## Commands
|
|
@@ -86,13 +86,13 @@ Shows the diff between desired and installed plugins.
|
|
|
86
86
|
|
|
87
87
|
Exit code is `1` if any plugins are missing, `0` otherwise.
|
|
88
88
|
|
|
89
|
-
### `fitout
|
|
89
|
+
### `fitout install`
|
|
90
90
|
|
|
91
91
|
Installs missing plugins to sync with config.
|
|
92
92
|
|
|
93
93
|
```bash
|
|
94
|
-
fitout
|
|
95
|
-
fitout
|
|
94
|
+
fitout install # Install missing plugins
|
|
95
|
+
fitout install --dry-run # Preview what would be installed
|
|
96
96
|
```
|
|
97
97
|
|
|
98
98
|
## Profiles
|
package/dist/claude.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ export interface InstalledPlugin {
|
|
|
5
5
|
enabled: boolean;
|
|
6
6
|
projectPath?: string;
|
|
7
7
|
}
|
|
8
|
+
export declare function claudeEnv(): NodeJS.ProcessEnv;
|
|
8
9
|
export declare function parsePluginList(jsonOutput: string): InstalledPlugin[];
|
|
9
10
|
export declare function listPlugins(): InstalledPlugin[];
|
|
10
11
|
export declare function installPlugin(pluginId: string): void;
|
package/dist/claude.js
CHANGED
|
@@ -1,4 +1,16 @@
|
|
|
1
|
-
import { execFileSync } from 'node:child_process';
|
|
1
|
+
import { execFileSync, execSync } from 'node:child_process';
|
|
2
|
+
import { mkdtempSync, readFileSync, rmSync } from 'node:fs';
|
|
3
|
+
import { tmpdir } from 'node:os';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
// Claude CLI sets CLAUDECODE=1 to detect nested sessions and refuses to run
|
|
6
|
+
// if it's present. Fitout calls `claude plugin list/install` from SessionStart
|
|
7
|
+
// hooks, which inherit this env var. These are safe metadata/management commands,
|
|
8
|
+
// not nested interactive sessions, so we strip the variable.
|
|
9
|
+
export function claudeEnv() {
|
|
10
|
+
const env = { ...process.env };
|
|
11
|
+
delete env.CLAUDECODE;
|
|
12
|
+
return env;
|
|
13
|
+
}
|
|
2
14
|
export function parsePluginList(jsonOutput) {
|
|
3
15
|
const parsed = JSON.parse(jsonOutput);
|
|
4
16
|
if (!Array.isArray(parsed)) {
|
|
@@ -7,14 +19,27 @@ export function parsePluginList(jsonOutput) {
|
|
|
7
19
|
return parsed;
|
|
8
20
|
}
|
|
9
21
|
export function listPlugins() {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
22
|
+
// Use file redirection to work around Claude CLI stdout truncation at 64KB
|
|
23
|
+
// when piped to a non-tty. File redirection captures the full output.
|
|
24
|
+
const tmpDir = mkdtempSync(join(tmpdir(), 'fitout-'));
|
|
25
|
+
const tmpFile = join(tmpDir, 'plugins.json');
|
|
26
|
+
try {
|
|
27
|
+
execSync(`claude plugin list --json > "${tmpFile}"`, {
|
|
28
|
+
encoding: 'utf-8',
|
|
29
|
+
env: claudeEnv(),
|
|
30
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
31
|
+
});
|
|
32
|
+
const output = readFileSync(tmpFile, 'utf-8');
|
|
33
|
+
return parsePluginList(output);
|
|
34
|
+
}
|
|
35
|
+
finally {
|
|
36
|
+
rmSync(tmpDir, { recursive: true, force: true });
|
|
37
|
+
}
|
|
14
38
|
}
|
|
15
39
|
export function installPlugin(pluginId) {
|
|
16
40
|
execFileSync('claude', ['plugin', 'install', pluginId, '--scope', 'local'], {
|
|
17
41
|
encoding: 'utf-8',
|
|
42
|
+
env: claudeEnv(),
|
|
18
43
|
stdio: 'inherit',
|
|
19
44
|
});
|
|
20
45
|
}
|
package/dist/cli.js
CHANGED
|
File without changes
|
package/dist/globalConfig.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
export { getGlobalConfigDir, getGlobalConfigPath } from './paths.js';
|
|
2
2
|
export interface GlobalConfig {
|
|
3
|
-
marketplaces?:
|
|
3
|
+
marketplaces?: string[];
|
|
4
4
|
}
|
|
5
5
|
export declare function readGlobalConfig(): GlobalConfig;
|
|
6
6
|
export declare function writeGlobalConfig(config: GlobalConfig): void;
|
|
7
7
|
export declare function hasGlobalConfig(): boolean;
|
|
8
|
-
export declare function getConfiguredMarketplaces():
|
|
9
|
-
export declare function getGlobalConfigContent(marketplaces?:
|
|
10
|
-
export declare function createGlobalConfig(marketplaces?:
|
|
8
|
+
export declare function getConfiguredMarketplaces(): string[];
|
|
9
|
+
export declare function getGlobalConfigContent(marketplaces?: string[]): string;
|
|
10
|
+
export declare function createGlobalConfig(marketplaces?: string[]): boolean;
|
package/dist/globalConfig.js
CHANGED
|
@@ -27,27 +27,24 @@ export function hasGlobalConfig() {
|
|
|
27
27
|
}
|
|
28
28
|
export function getConfiguredMarketplaces() {
|
|
29
29
|
const config = readGlobalConfig();
|
|
30
|
-
return config.marketplaces ||
|
|
30
|
+
return config.marketplaces || [];
|
|
31
31
|
}
|
|
32
32
|
export function getGlobalConfigContent(marketplaces) {
|
|
33
|
-
if (!marketplaces ||
|
|
34
|
-
return `#
|
|
35
|
-
#
|
|
33
|
+
if (!marketplaces || marketplaces.length === 0) {
|
|
34
|
+
return `# Fitout global config
|
|
35
|
+
# Marketplace sources to ensure are installed
|
|
36
36
|
|
|
37
|
-
[
|
|
38
|
-
# pickled-claude-plugins = "https://github.com/technicalpickles/pickled-claude-plugins"
|
|
37
|
+
marketplaces = []
|
|
39
38
|
`;
|
|
40
39
|
}
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
}
|
|
50
|
-
return lines.join('\n') + '\n';
|
|
40
|
+
const quotedSources = marketplaces.map((s) => ` "${s}"`).join(',\n');
|
|
41
|
+
return `# Fitout global config
|
|
42
|
+
# Marketplace sources to ensure are installed
|
|
43
|
+
|
|
44
|
+
marketplaces = [
|
|
45
|
+
${quotedSources},
|
|
46
|
+
]
|
|
47
|
+
`;
|
|
51
48
|
}
|
|
52
49
|
export function createGlobalConfig(marketplaces) {
|
|
53
50
|
const configPath = getGlobalConfigPath();
|
package/dist/init.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
export declare function readClaudeSettings(path: string): Record<string, unknown>;
|
|
2
2
|
export type HookStatus = 'none' | 'current' | 'outdated';
|
|
3
|
+
export declare const HOOK_COMMAND_DEFAULT = "npx fitout@latest install --hook";
|
|
4
|
+
export declare const HOOK_COMMAND_DEV = "fitout install --hook";
|
|
3
5
|
export declare function getFitoutHookStatus(settings: Record<string, unknown>): HookStatus;
|
|
4
6
|
export declare function hasFitoutHook(settings: Record<string, unknown>): boolean;
|
|
5
7
|
interface ClaudeSettings {
|
package/dist/init.js
CHANGED
|
@@ -14,17 +14,31 @@ export function readClaudeSettings(path) {
|
|
|
14
14
|
return {};
|
|
15
15
|
}
|
|
16
16
|
}
|
|
17
|
+
// Hook command patterns
|
|
18
|
+
export const HOOK_COMMAND_DEFAULT = 'npx fitout@latest install --hook';
|
|
19
|
+
export const HOOK_COMMAND_DEV = 'fitout install --hook';
|
|
20
|
+
function isCurrentHook(command) {
|
|
21
|
+
if (!command)
|
|
22
|
+
return false;
|
|
23
|
+
// Match both "npx fitout@latest install --hook" and "fitout install --hook"
|
|
24
|
+
return command.includes('fitout install --hook') || command.includes('fitout@latest install --hook');
|
|
25
|
+
}
|
|
26
|
+
function isLegacyHook(command) {
|
|
27
|
+
if (!command)
|
|
28
|
+
return false;
|
|
29
|
+
return command.includes('fitout apply --hook') || command.includes('fitout@latest apply --hook');
|
|
30
|
+
}
|
|
17
31
|
export function getFitoutHookStatus(settings) {
|
|
18
32
|
const hooks = settings.hooks;
|
|
19
33
|
if (!hooks?.SessionStart)
|
|
20
34
|
return 'none';
|
|
21
35
|
const sessionStartHooks = hooks.SessionStart;
|
|
22
|
-
// Check for current command
|
|
23
|
-
const hasCurrent = sessionStartHooks.some((matcher) => matcher.hooks?.some((hook) => hook.command
|
|
36
|
+
// Check for current command (matches both npx and dev versions)
|
|
37
|
+
const hasCurrent = sessionStartHooks.some((matcher) => matcher.hooks?.some((hook) => isCurrentHook(hook.command)));
|
|
24
38
|
if (hasCurrent)
|
|
25
39
|
return 'current';
|
|
26
40
|
// Check for legacy command
|
|
27
|
-
const hasLegacy = sessionStartHooks.some((matcher) => matcher.hooks?.some((hook) => hook.command
|
|
41
|
+
const hasLegacy = sessionStartHooks.some((matcher) => matcher.hooks?.some((hook) => isLegacyHook(hook.command)));
|
|
28
42
|
if (hasLegacy)
|
|
29
43
|
return 'outdated';
|
|
30
44
|
return 'none';
|
|
@@ -43,7 +57,7 @@ export function addFitoutHook(settings) {
|
|
|
43
57
|
}
|
|
44
58
|
result.hooks.SessionStart.push({
|
|
45
59
|
hooks: [
|
|
46
|
-
{ type: 'command', command:
|
|
60
|
+
{ type: 'command', command: HOOK_COMMAND_DEFAULT }
|
|
47
61
|
]
|
|
48
62
|
});
|
|
49
63
|
return result;
|
|
@@ -56,8 +70,11 @@ export function upgradeFitoutHook(settings) {
|
|
|
56
70
|
for (const matcher of result.hooks.SessionStart) {
|
|
57
71
|
if (matcher.hooks) {
|
|
58
72
|
for (const hook of matcher.hooks) {
|
|
59
|
-
if (hook.command
|
|
60
|
-
|
|
73
|
+
if (isLegacyHook(hook.command)) {
|
|
74
|
+
// Replace both legacy patterns with the new default
|
|
75
|
+
hook.command = hook.command
|
|
76
|
+
.replace('fitout@latest apply --hook', HOOK_COMMAND_DEFAULT)
|
|
77
|
+
.replace('fitout apply --hook', HOOK_COMMAND_DEFAULT);
|
|
61
78
|
}
|
|
62
79
|
}
|
|
63
80
|
}
|
|
@@ -107,7 +124,7 @@ Read \`~/.claude/settings.json\` and verify the Fitout hook exists:
|
|
|
107
124
|
"SessionStart": [
|
|
108
125
|
{
|
|
109
126
|
"hooks": [
|
|
110
|
-
{ "type": "command", "command": "fitout install --hook" }
|
|
127
|
+
{ "type": "command", "command": "npx fitout@latest install --hook" }
|
|
111
128
|
]
|
|
112
129
|
}
|
|
113
130
|
]
|
|
@@ -115,6 +132,8 @@ Read \`~/.claude/settings.json\` and verify the Fitout hook exists:
|
|
|
115
132
|
}
|
|
116
133
|
\`\`\`
|
|
117
134
|
|
|
135
|
+
Note: Developers may use \`fitout install --hook\` (without npx) for local development.
|
|
136
|
+
|
|
118
137
|
If the hook is missing, suggest running \`fitout init\`.
|
|
119
138
|
|
|
120
139
|
### 2. Check Plugin Status
|
package/dist/install.js
CHANGED
|
@@ -133,13 +133,13 @@ export function runInstall(cwd, options = {}) {
|
|
|
133
133
|
}
|
|
134
134
|
// Ensure configured marketplaces are installed (skip in hook mode for cleaner output)
|
|
135
135
|
if (!options.hook && hasGlobalConfig()) {
|
|
136
|
-
const
|
|
137
|
-
if (
|
|
138
|
-
const marketplaceResult = ensureMarketplaces();
|
|
136
|
+
const marketplaceSources = getConfiguredMarketplaces();
|
|
137
|
+
if (marketplaceSources.length > 0) {
|
|
138
|
+
const marketplaceResult = ensureMarketplaces(marketplaceSources);
|
|
139
139
|
if (marketplaceResult.added.length > 0) {
|
|
140
140
|
console.log(colors.header('Marketplaces:'));
|
|
141
|
-
for (const
|
|
142
|
-
console.log(` ${symbols.install} ${
|
|
141
|
+
for (const source of marketplaceResult.added) {
|
|
142
|
+
console.log(` ${symbols.install} ${source}`);
|
|
143
143
|
}
|
|
144
144
|
console.log('');
|
|
145
145
|
}
|
package/dist/marketplace.d.ts
CHANGED
|
@@ -4,16 +4,15 @@ export interface AvailablePlugin {
|
|
|
4
4
|
version: string;
|
|
5
5
|
marketplace: string;
|
|
6
6
|
}
|
|
7
|
+
export interface InstalledMarketplace {
|
|
8
|
+
name: string;
|
|
9
|
+
source: 'github' | 'git' | string;
|
|
10
|
+
repo?: string;
|
|
11
|
+
url?: string;
|
|
12
|
+
installLocation: string;
|
|
13
|
+
}
|
|
7
14
|
export declare function listAvailablePlugins(): AvailablePlugin[];
|
|
8
15
|
export declare function refreshMarketplaces(): void;
|
|
9
|
-
/**
|
|
10
|
-
* Get list of marketplace names that are installed locally
|
|
11
|
-
*/
|
|
12
|
-
export declare function getInstalledMarketplaces(): string[];
|
|
13
|
-
/**
|
|
14
|
-
* Check if a marketplace is installed
|
|
15
|
-
*/
|
|
16
|
-
export declare function isMarketplaceInstalled(name: string): boolean;
|
|
17
16
|
/**
|
|
18
17
|
* Add a marketplace from a source URL
|
|
19
18
|
*/
|
|
@@ -22,11 +21,19 @@ export interface EnsureMarketplacesResult {
|
|
|
22
21
|
added: string[];
|
|
23
22
|
alreadyInstalled: string[];
|
|
24
23
|
failed: {
|
|
25
|
-
|
|
24
|
+
source: string;
|
|
26
25
|
error: string;
|
|
27
26
|
}[];
|
|
28
27
|
}
|
|
29
28
|
/**
|
|
30
|
-
* Ensure all configured
|
|
29
|
+
* Ensure all configured marketplace sources are installed
|
|
30
|
+
*/
|
|
31
|
+
export declare function ensureMarketplaces(sources: string[]): EnsureMarketplacesResult;
|
|
32
|
+
/**
|
|
33
|
+
* List installed marketplaces using Claude CLI JSON output
|
|
34
|
+
*/
|
|
35
|
+
export declare function listInstalledMarketplaces(): InstalledMarketplace[];
|
|
36
|
+
/**
|
|
37
|
+
* Check if a marketplace source URL is already installed
|
|
31
38
|
*/
|
|
32
|
-
export declare function
|
|
39
|
+
export declare function isMarketplaceSourceInstalled(source: string): boolean;
|
package/dist/marketplace.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { execFileSync } from 'node:child_process';
|
|
2
2
|
import { existsSync, readdirSync, readFileSync } from 'node:fs';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
|
-
import { getConfiguredMarketplaces } from './globalConfig.js';
|
|
5
4
|
import { getMarketplacesDir } from './paths.js';
|
|
6
5
|
export { getMarketplacesDir } from './paths.js';
|
|
7
6
|
export function listAvailablePlugins() {
|
|
@@ -41,25 +40,6 @@ export function refreshMarketplaces() {
|
|
|
41
40
|
stdio: 'inherit',
|
|
42
41
|
});
|
|
43
42
|
}
|
|
44
|
-
/**
|
|
45
|
-
* Get list of marketplace names that are installed locally
|
|
46
|
-
*/
|
|
47
|
-
export function getInstalledMarketplaces() {
|
|
48
|
-
const marketplacesDir = getMarketplacesDir();
|
|
49
|
-
if (!existsSync(marketplacesDir)) {
|
|
50
|
-
return [];
|
|
51
|
-
}
|
|
52
|
-
return readdirSync(marketplacesDir, { withFileTypes: true })
|
|
53
|
-
.filter((d) => d.isDirectory())
|
|
54
|
-
.map((d) => d.name);
|
|
55
|
-
}
|
|
56
|
-
/**
|
|
57
|
-
* Check if a marketplace is installed
|
|
58
|
-
*/
|
|
59
|
-
export function isMarketplaceInstalled(name) {
|
|
60
|
-
const marketplacesDir = getMarketplacesDir();
|
|
61
|
-
return existsSync(join(marketplacesDir, name));
|
|
62
|
-
}
|
|
63
43
|
/**
|
|
64
44
|
* Add a marketplace from a source URL
|
|
65
45
|
*/
|
|
@@ -70,27 +50,26 @@ export function addMarketplace(source) {
|
|
|
70
50
|
});
|
|
71
51
|
}
|
|
72
52
|
/**
|
|
73
|
-
* Ensure all configured
|
|
53
|
+
* Ensure all configured marketplace sources are installed
|
|
74
54
|
*/
|
|
75
|
-
export function ensureMarketplaces() {
|
|
76
|
-
const configured = getConfiguredMarketplaces();
|
|
55
|
+
export function ensureMarketplaces(sources) {
|
|
77
56
|
const result = {
|
|
78
57
|
added: [],
|
|
79
58
|
alreadyInstalled: [],
|
|
80
59
|
failed: [],
|
|
81
60
|
};
|
|
82
|
-
for (const
|
|
83
|
-
if (
|
|
84
|
-
result.alreadyInstalled.push(
|
|
61
|
+
for (const source of sources) {
|
|
62
|
+
if (isMarketplaceSourceInstalled(source)) {
|
|
63
|
+
result.alreadyInstalled.push(source);
|
|
85
64
|
}
|
|
86
65
|
else {
|
|
87
66
|
try {
|
|
88
67
|
addMarketplace(source);
|
|
89
|
-
result.added.push(
|
|
68
|
+
result.added.push(source);
|
|
90
69
|
}
|
|
91
70
|
catch (err) {
|
|
92
71
|
result.failed.push({
|
|
93
|
-
|
|
72
|
+
source,
|
|
94
73
|
error: err instanceof Error ? err.message : 'Unknown error',
|
|
95
74
|
});
|
|
96
75
|
}
|
|
@@ -98,3 +77,43 @@ export function ensureMarketplaces() {
|
|
|
98
77
|
}
|
|
99
78
|
return result;
|
|
100
79
|
}
|
|
80
|
+
/**
|
|
81
|
+
* List installed marketplaces using Claude CLI JSON output
|
|
82
|
+
*/
|
|
83
|
+
export function listInstalledMarketplaces() {
|
|
84
|
+
try {
|
|
85
|
+
const output = execFileSync('claude', ['plugin', 'marketplace', 'list', '--json'], {
|
|
86
|
+
encoding: 'utf-8',
|
|
87
|
+
});
|
|
88
|
+
return JSON.parse(output);
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
return [];
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Normalize a marketplace source URL to extract owner/repo for comparison
|
|
96
|
+
*/
|
|
97
|
+
function normalizeGitHubSource(source) {
|
|
98
|
+
// Match github.com URLs with or without .git suffix
|
|
99
|
+
const match = source.match(/github\.com\/([^/]+\/[^/.]+)/);
|
|
100
|
+
return match ? match[1] : null;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Check if a marketplace source URL is already installed
|
|
104
|
+
*/
|
|
105
|
+
export function isMarketplaceSourceInstalled(source) {
|
|
106
|
+
const installed = listInstalledMarketplaces();
|
|
107
|
+
const normalizedSource = normalizeGitHubSource(source);
|
|
108
|
+
return installed.some((m) => {
|
|
109
|
+
// Check github source by repo
|
|
110
|
+
if (m.source === 'github' && m.repo && normalizedSource) {
|
|
111
|
+
return m.repo === normalizedSource;
|
|
112
|
+
}
|
|
113
|
+
// Check git source by URL
|
|
114
|
+
if (m.source === 'git' && m.url) {
|
|
115
|
+
return m.url === source;
|
|
116
|
+
}
|
|
117
|
+
return false;
|
|
118
|
+
});
|
|
119
|
+
}
|
package/dist/update.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { execFileSync } from 'node:child_process';
|
|
2
|
+
import { claudeEnv } from './claude.js';
|
|
2
3
|
/**
|
|
3
4
|
* Compare semver versions. Returns:
|
|
4
5
|
* -1 if a < b
|
|
@@ -49,6 +50,7 @@ export function findOutdatedPlugins(installed, available, projectPath) {
|
|
|
49
50
|
export function updatePlugin(pluginId, scope = 'local') {
|
|
50
51
|
execFileSync('claude', ['plugin', 'update', pluginId, '--scope', scope], {
|
|
51
52
|
encoding: 'utf-8',
|
|
53
|
+
env: claudeEnv(),
|
|
52
54
|
stdio: 'inherit',
|
|
53
55
|
});
|
|
54
56
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fitout",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Context-aware plugin manager for Claude Code",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -39,6 +39,7 @@
|
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
41
|
"@types/node": "^25.1.0",
|
|
42
|
+
"semantic-release": "^25.0.3",
|
|
42
43
|
"tsx": "^4.21.0",
|
|
43
44
|
"typescript": "^5.9.3",
|
|
44
45
|
"vitest": "^4.0.18"
|