@devwizard/vite-plugin-enumify 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +163 -0
- package/dist/index.cjs +183 -0
- package/dist/index.d.cts +58 -0
- package/dist/index.d.mts +56 -0
- package/dist/index.d.ts +58 -0
- package/dist/index.mjs +173 -0
- package/package.json +72 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 DevWizard HQ
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
# @devwizard/vite-plugin-enumify
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@devwizard/vite-plugin-enumify)
|
|
4
|
+
[](https://github.com/devwizardhq/laravel-enumify)
|
|
5
|
+
|
|
6
|
+
Vite plugin for [Laravel Enumify](https://github.com/devwizardhq/laravel-enumify) — automatically sync PHP enums to TypeScript during development and builds.
|
|
7
|
+
|
|
8
|
+
## Features
|
|
9
|
+
|
|
10
|
+
- Runs `php artisan enumify:sync --force --quiet` before Vite compiles
|
|
11
|
+
- Watches enum directories in dev mode (debounced)
|
|
12
|
+
- Reads `config/enumify.php` to discover paths and watch config
|
|
13
|
+
- Ignores changes inside the output directory to avoid infinite loops
|
|
14
|
+
- Cross-platform: Windows/macOS/Linux
|
|
15
|
+
|
|
16
|
+
## Package Links
|
|
17
|
+
|
|
18
|
+
- NPM: https://www.npmjs.com/package/@devwizard/vite-plugin-enumify
|
|
19
|
+
- Repository: https://github.com/devwizardhq/laravel-enumify
|
|
20
|
+
- Composer (Laravel package): https://packagist.org/packages/devwizardhq/laravel-enumify
|
|
21
|
+
|
|
22
|
+
## Installation
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npm install @devwizard/vite-plugin-enumify --save-dev
|
|
26
|
+
# or
|
|
27
|
+
pnpm add -D @devwizard/vite-plugin-enumify
|
|
28
|
+
# or
|
|
29
|
+
yarn add -D @devwizard/vite-plugin-enumify
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Package Manager Support
|
|
33
|
+
|
|
34
|
+
This plugin is package-manager agnostic and works with npm, pnpm, or yarn. The runtime behavior is identical regardless of how you install it.
|
|
35
|
+
|
|
36
|
+
## Usage
|
|
37
|
+
|
|
38
|
+
Add the plugin to your `vite.config.ts`:
|
|
39
|
+
|
|
40
|
+
```ts
|
|
41
|
+
import { defineConfig } from 'vite';
|
|
42
|
+
import laravel from 'laravel-vite-plugin';
|
|
43
|
+
import enumify from '@devwizard/vite-plugin-enumify';
|
|
44
|
+
|
|
45
|
+
export default defineConfig({
|
|
46
|
+
plugins: [
|
|
47
|
+
enumify(),
|
|
48
|
+
laravel({
|
|
49
|
+
input: ['resources/js/app.ts'],
|
|
50
|
+
refresh: true,
|
|
51
|
+
}),
|
|
52
|
+
],
|
|
53
|
+
});
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## How It Works
|
|
57
|
+
|
|
58
|
+
1. **On Build Start**: runs `php artisan enumify:sync --force --quiet` before Vite compiles TypeScript.
|
|
59
|
+
2. **Watch Mode**: watches the enum paths from `config/enumify.php` and re-syncs on changes.
|
|
60
|
+
3. **Safe Output**: ignores changes under the output directory to avoid re-trigger loops.
|
|
61
|
+
|
|
62
|
+
## Options
|
|
63
|
+
|
|
64
|
+
```ts
|
|
65
|
+
enumify({
|
|
66
|
+
// PHP binary path (default: "php")
|
|
67
|
+
artisanBin: 'php',
|
|
68
|
+
|
|
69
|
+
// Path to the artisan file (default: "artisan")
|
|
70
|
+
artisanFile: 'artisan',
|
|
71
|
+
|
|
72
|
+
// Command to run (default: "enumify:sync")
|
|
73
|
+
syncCommand: 'enumify:sync',
|
|
74
|
+
|
|
75
|
+
// Working directory (default: process.cwd())
|
|
76
|
+
cwd: process.cwd(),
|
|
77
|
+
|
|
78
|
+
// Enable watch mode in development (default: runtime.watch from config/enumify.php)
|
|
79
|
+
watch: true,
|
|
80
|
+
|
|
81
|
+
// Additional environment variables
|
|
82
|
+
env: {},
|
|
83
|
+
});
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
The plugin reads `config/enumify.php` to discover enum paths and output paths so it can watch the right files and avoid feedback loops.
|
|
87
|
+
|
|
88
|
+
## Example
|
|
89
|
+
|
|
90
|
+
Given this PHP enum:
|
|
91
|
+
|
|
92
|
+
```php
|
|
93
|
+
// app/Enums/OrderStatus.php
|
|
94
|
+
enum OrderStatus: string
|
|
95
|
+
{
|
|
96
|
+
case PENDING = 'pending';
|
|
97
|
+
case PROCESSING = 'processing';
|
|
98
|
+
case SHIPPED = 'shipped';
|
|
99
|
+
|
|
100
|
+
public function label(): string
|
|
101
|
+
{
|
|
102
|
+
return match ($this) {
|
|
103
|
+
self::PENDING => 'Pending',
|
|
104
|
+
self::PROCESSING => 'Processing',
|
|
105
|
+
self::SHIPPED => 'Shipped',
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
public function color(): string
|
|
110
|
+
{
|
|
111
|
+
return match ($this) {
|
|
112
|
+
self::PENDING => 'yellow',
|
|
113
|
+
self::PROCESSING => 'blue',
|
|
114
|
+
self::SHIPPED => 'green',
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
The plugin generates:
|
|
121
|
+
|
|
122
|
+
```ts
|
|
123
|
+
// resources/js/enums/order-status.ts
|
|
124
|
+
export enum OrderStatus {
|
|
125
|
+
Pending = 'pending',
|
|
126
|
+
Processing = 'processing',
|
|
127
|
+
Shipped = 'shipped',
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export type OrderStatusValue = `${OrderStatus}`;
|
|
131
|
+
|
|
132
|
+
export const OrderStatusLabels: Record<OrderStatus, string> = {
|
|
133
|
+
[OrderStatus.Pending]: 'Pending',
|
|
134
|
+
[OrderStatus.Processing]: 'Processing',
|
|
135
|
+
[OrderStatus.Shipped]: 'Shipped',
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
export const OrderStatusColors: Record<OrderStatus, string> = {
|
|
139
|
+
[OrderStatus.Pending]: 'yellow',
|
|
140
|
+
[OrderStatus.Processing]: 'blue',
|
|
141
|
+
[OrderStatus.Shipped]: 'green',
|
|
142
|
+
};
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## Git Workflow
|
|
146
|
+
|
|
147
|
+
1. Create a feature branch from `main`
|
|
148
|
+
2. Make changes with focused commits
|
|
149
|
+
3. Run `npm run build` and `npm run typecheck`
|
|
150
|
+
4. Open a PR and ensure CI passes
|
|
151
|
+
|
|
152
|
+
Release tip: tag releases after merging to `main`, then publish to NPM.
|
|
153
|
+
|
|
154
|
+
## Requirements
|
|
155
|
+
|
|
156
|
+
- Node.js >= 18.0.0
|
|
157
|
+
- Vite >= 4.0.0 (including Vite 7)
|
|
158
|
+
- PHP >= 8.2
|
|
159
|
+
- Laravel Enumify package installed
|
|
160
|
+
|
|
161
|
+
## License
|
|
162
|
+
|
|
163
|
+
MIT
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
const child_process = require('child_process');
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
|
|
9
|
+
function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e.default : e; }
|
|
10
|
+
|
|
11
|
+
const fs__default = /*#__PURE__*/_interopDefaultCompat(fs);
|
|
12
|
+
const path__default = /*#__PURE__*/_interopDefaultCompat(path);
|
|
13
|
+
|
|
14
|
+
const DEFAULT_ENUM_PATHS = ["app/Enums"];
|
|
15
|
+
const DEFAULT_OUTPUT_PATH = "resources/js/enums";
|
|
16
|
+
const DEFAULT_WATCH = true;
|
|
17
|
+
const WATCH_DEBOUNCE_MS = 250;
|
|
18
|
+
function resolveOptions(options = {}) {
|
|
19
|
+
const cwd = options.cwd ?? process.cwd();
|
|
20
|
+
const config = readEnumifyConfig(cwd);
|
|
21
|
+
return {
|
|
22
|
+
options: {
|
|
23
|
+
artisanBin: options.artisanBin ?? "php",
|
|
24
|
+
artisanFile: options.artisanFile ?? "artisan",
|
|
25
|
+
syncCommand: options.syncCommand ?? "enumify:sync",
|
|
26
|
+
cwd,
|
|
27
|
+
watch: options.watch ?? config.watch,
|
|
28
|
+
env: options.env ?? {}
|
|
29
|
+
},
|
|
30
|
+
config
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
function readEnumifyConfig(cwd) {
|
|
34
|
+
const defaults = {
|
|
35
|
+
enumPaths: DEFAULT_ENUM_PATHS,
|
|
36
|
+
outputPath: DEFAULT_OUTPUT_PATH,
|
|
37
|
+
watch: DEFAULT_WATCH
|
|
38
|
+
};
|
|
39
|
+
const configPath = path__default.join(cwd, "config", "enumify.php");
|
|
40
|
+
if (!fs__default.existsSync(configPath)) {
|
|
41
|
+
return defaults;
|
|
42
|
+
}
|
|
43
|
+
try {
|
|
44
|
+
const contents = fs__default.readFileSync(configPath, "utf8");
|
|
45
|
+
return {
|
|
46
|
+
enumPaths: extractStringArray(contents, "enums") ?? defaults.enumPaths,
|
|
47
|
+
outputPath: extractString(contents, "output") ?? defaults.outputPath,
|
|
48
|
+
watch: extractBoolean(contents, "watch") ?? defaults.watch
|
|
49
|
+
};
|
|
50
|
+
} catch {
|
|
51
|
+
return defaults;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function extractStringArray(contents, key) {
|
|
55
|
+
const match = new RegExp(`['"]${key}['"]\\s*=>\\s*\\[([\\s\\S]*?)\\]`, "m").exec(contents);
|
|
56
|
+
if (!match) {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
const items = [];
|
|
60
|
+
const itemRegex = /['"]([^'"]+)['"]/g;
|
|
61
|
+
let itemMatch;
|
|
62
|
+
while ((itemMatch = itemRegex.exec(match[1])) !== null) {
|
|
63
|
+
items.push(itemMatch[1]);
|
|
64
|
+
}
|
|
65
|
+
return items.length > 0 ? items : null;
|
|
66
|
+
}
|
|
67
|
+
function extractString(contents, key) {
|
|
68
|
+
const match = new RegExp(`['"]${key}['"]\\s*=>\\s*['"]([^'"]+)['"]`, "m").exec(contents);
|
|
69
|
+
return match ? match[1] : null;
|
|
70
|
+
}
|
|
71
|
+
function extractBoolean(contents, key) {
|
|
72
|
+
const match = new RegExp(`['"]${key}['"]\\s*=>\\s*(true|false)`, "mi").exec(contents);
|
|
73
|
+
if (!match) {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
return match[1].toLowerCase() === "true";
|
|
77
|
+
}
|
|
78
|
+
function toAbsolutePath(cwd, value) {
|
|
79
|
+
return path__default.isAbsolute(value) ? value : path__default.join(cwd, value);
|
|
80
|
+
}
|
|
81
|
+
function isPathInside(filePath, dirPath) {
|
|
82
|
+
const relative = path__default.relative(dirPath, filePath);
|
|
83
|
+
return relative !== "" && !relative.startsWith("..") && !path__default.isAbsolute(relative);
|
|
84
|
+
}
|
|
85
|
+
function debounce(fn, delay) {
|
|
86
|
+
let timeoutId = null;
|
|
87
|
+
return (...args) => {
|
|
88
|
+
if (timeoutId) {
|
|
89
|
+
clearTimeout(timeoutId);
|
|
90
|
+
}
|
|
91
|
+
timeoutId = setTimeout(() => {
|
|
92
|
+
fn(...args);
|
|
93
|
+
timeoutId = null;
|
|
94
|
+
}, delay);
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
function enumify(options = {}) {
|
|
98
|
+
const { options: resolved, config } = resolveOptions(options);
|
|
99
|
+
const enumDirs = config.enumPaths.map((enumPath) => toAbsolutePath(resolved.cwd, enumPath));
|
|
100
|
+
const outputDir = toAbsolutePath(resolved.cwd, config.outputPath);
|
|
101
|
+
let viteConfig = null;
|
|
102
|
+
let logger = console;
|
|
103
|
+
let running = false;
|
|
104
|
+
let rerunRequested = false;
|
|
105
|
+
let initialSyncDone = false;
|
|
106
|
+
const spawnSync = () => new Promise((resolve, reject) => {
|
|
107
|
+
const args = [resolved.artisanFile, resolved.syncCommand, "--force", "--quiet"];
|
|
108
|
+
const child = child_process.spawn(resolved.artisanBin, args, {
|
|
109
|
+
cwd: resolved.cwd,
|
|
110
|
+
env: { ...process.env, ...resolved.env },
|
|
111
|
+
stdio: "inherit"
|
|
112
|
+
});
|
|
113
|
+
child.on("error", (error) => reject(error));
|
|
114
|
+
child.on("close", (code) => {
|
|
115
|
+
if (code === 0) {
|
|
116
|
+
resolve();
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
reject(new Error(`Enumify sync failed with exit code ${code}`));
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
const runSync = async () => {
|
|
123
|
+
if (running) {
|
|
124
|
+
rerunRequested = true;
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
running = true;
|
|
128
|
+
do {
|
|
129
|
+
rerunRequested = false;
|
|
130
|
+
await spawnSync();
|
|
131
|
+
logger.info("[plugin @devwizard/vite-plugin-enumify] Enum types generated successfully");
|
|
132
|
+
} while (rerunRequested);
|
|
133
|
+
running = false;
|
|
134
|
+
};
|
|
135
|
+
const debouncedSync = debounce(() => {
|
|
136
|
+
runSync().catch((error) => logger.error(`[enumify] ${error.message}`));
|
|
137
|
+
}, WATCH_DEBOUNCE_MS);
|
|
138
|
+
return {
|
|
139
|
+
name: "@devwizard/vite-plugin-enumify",
|
|
140
|
+
enforce: "pre",
|
|
141
|
+
configResolved(resolvedConfig) {
|
|
142
|
+
viteConfig = resolvedConfig;
|
|
143
|
+
logger = resolvedConfig.logger;
|
|
144
|
+
},
|
|
145
|
+
buildStart() {
|
|
146
|
+
if (viteConfig?.command === "serve") {
|
|
147
|
+
if (initialSyncDone) {
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
initialSyncDone = true;
|
|
151
|
+
}
|
|
152
|
+
return runSync().catch((error) => {
|
|
153
|
+
this.error(`[enumify] ${error.message}`);
|
|
154
|
+
});
|
|
155
|
+
},
|
|
156
|
+
configureServer(server) {
|
|
157
|
+
if (!resolved.watch) {
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
for (const enumDir of enumDirs) {
|
|
161
|
+
server.watcher.add(enumDir);
|
|
162
|
+
}
|
|
163
|
+
},
|
|
164
|
+
handleHotUpdate({ file }) {
|
|
165
|
+
if (!resolved.watch) {
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
const filePath = path__default.resolve(file);
|
|
169
|
+
if (isPathInside(filePath, outputDir)) {
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
for (const enumDir of enumDirs) {
|
|
173
|
+
if (isPathInside(filePath, enumDir)) {
|
|
174
|
+
debouncedSync();
|
|
175
|
+
break;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
exports.default = enumify;
|
|
183
|
+
exports.enumify = enumify;
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { Plugin } from 'vite';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Configuration options for the Enumify Vite plugin.
|
|
5
|
+
*/
|
|
6
|
+
interface EnumifyOptions {
|
|
7
|
+
/**
|
|
8
|
+
* PHP binary path.
|
|
9
|
+
* @default "php"
|
|
10
|
+
*/
|
|
11
|
+
artisanBin?: string;
|
|
12
|
+
/**
|
|
13
|
+
* Path to the artisan file.
|
|
14
|
+
* @default "artisan"
|
|
15
|
+
*/
|
|
16
|
+
artisanFile?: string;
|
|
17
|
+
/**
|
|
18
|
+
* Artisan command to run for syncing enums.
|
|
19
|
+
* @default "enumify:sync"
|
|
20
|
+
*/
|
|
21
|
+
syncCommand?: string;
|
|
22
|
+
/**
|
|
23
|
+
* Working directory for the command.
|
|
24
|
+
* @default process.cwd()
|
|
25
|
+
*/
|
|
26
|
+
cwd?: string;
|
|
27
|
+
/**
|
|
28
|
+
* Whether to watch for changes in development mode.
|
|
29
|
+
* Defaults to the Laravel enumify config runtime.watch setting.
|
|
30
|
+
*/
|
|
31
|
+
watch?: boolean;
|
|
32
|
+
/**
|
|
33
|
+
* Additional environment variables for the command.
|
|
34
|
+
* @default {}
|
|
35
|
+
*/
|
|
36
|
+
env?: Record<string, string>;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Resolved configuration with defaults applied.
|
|
40
|
+
*/
|
|
41
|
+
interface ResolvedEnumifyOptions {
|
|
42
|
+
artisanBin: string;
|
|
43
|
+
artisanFile: string;
|
|
44
|
+
syncCommand: string;
|
|
45
|
+
cwd: string;
|
|
46
|
+
watch: boolean;
|
|
47
|
+
env: Record<string, string>;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Laravel Enumify Vite plugin.
|
|
52
|
+
*/
|
|
53
|
+
declare function enumify(options?: EnumifyOptions): Plugin;
|
|
54
|
+
|
|
55
|
+
// @ts-ignore
|
|
56
|
+
export = enumify;
|
|
57
|
+
export { enumify };
|
|
58
|
+
export type { EnumifyOptions, ResolvedEnumifyOptions };
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { Plugin } from 'vite';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Configuration options for the Enumify Vite plugin.
|
|
5
|
+
*/
|
|
6
|
+
interface EnumifyOptions {
|
|
7
|
+
/**
|
|
8
|
+
* PHP binary path.
|
|
9
|
+
* @default "php"
|
|
10
|
+
*/
|
|
11
|
+
artisanBin?: string;
|
|
12
|
+
/**
|
|
13
|
+
* Path to the artisan file.
|
|
14
|
+
* @default "artisan"
|
|
15
|
+
*/
|
|
16
|
+
artisanFile?: string;
|
|
17
|
+
/**
|
|
18
|
+
* Artisan command to run for syncing enums.
|
|
19
|
+
* @default "enumify:sync"
|
|
20
|
+
*/
|
|
21
|
+
syncCommand?: string;
|
|
22
|
+
/**
|
|
23
|
+
* Working directory for the command.
|
|
24
|
+
* @default process.cwd()
|
|
25
|
+
*/
|
|
26
|
+
cwd?: string;
|
|
27
|
+
/**
|
|
28
|
+
* Whether to watch for changes in development mode.
|
|
29
|
+
* Defaults to the Laravel enumify config runtime.watch setting.
|
|
30
|
+
*/
|
|
31
|
+
watch?: boolean;
|
|
32
|
+
/**
|
|
33
|
+
* Additional environment variables for the command.
|
|
34
|
+
* @default {}
|
|
35
|
+
*/
|
|
36
|
+
env?: Record<string, string>;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Resolved configuration with defaults applied.
|
|
40
|
+
*/
|
|
41
|
+
interface ResolvedEnumifyOptions {
|
|
42
|
+
artisanBin: string;
|
|
43
|
+
artisanFile: string;
|
|
44
|
+
syncCommand: string;
|
|
45
|
+
cwd: string;
|
|
46
|
+
watch: boolean;
|
|
47
|
+
env: Record<string, string>;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Laravel Enumify Vite plugin.
|
|
52
|
+
*/
|
|
53
|
+
declare function enumify(options?: EnumifyOptions): Plugin;
|
|
54
|
+
|
|
55
|
+
export { enumify as default, enumify };
|
|
56
|
+
export type { EnumifyOptions, ResolvedEnumifyOptions };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { Plugin } from 'vite';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Configuration options for the Enumify Vite plugin.
|
|
5
|
+
*/
|
|
6
|
+
interface EnumifyOptions {
|
|
7
|
+
/**
|
|
8
|
+
* PHP binary path.
|
|
9
|
+
* @default "php"
|
|
10
|
+
*/
|
|
11
|
+
artisanBin?: string;
|
|
12
|
+
/**
|
|
13
|
+
* Path to the artisan file.
|
|
14
|
+
* @default "artisan"
|
|
15
|
+
*/
|
|
16
|
+
artisanFile?: string;
|
|
17
|
+
/**
|
|
18
|
+
* Artisan command to run for syncing enums.
|
|
19
|
+
* @default "enumify:sync"
|
|
20
|
+
*/
|
|
21
|
+
syncCommand?: string;
|
|
22
|
+
/**
|
|
23
|
+
* Working directory for the command.
|
|
24
|
+
* @default process.cwd()
|
|
25
|
+
*/
|
|
26
|
+
cwd?: string;
|
|
27
|
+
/**
|
|
28
|
+
* Whether to watch for changes in development mode.
|
|
29
|
+
* Defaults to the Laravel enumify config runtime.watch setting.
|
|
30
|
+
*/
|
|
31
|
+
watch?: boolean;
|
|
32
|
+
/**
|
|
33
|
+
* Additional environment variables for the command.
|
|
34
|
+
* @default {}
|
|
35
|
+
*/
|
|
36
|
+
env?: Record<string, string>;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Resolved configuration with defaults applied.
|
|
40
|
+
*/
|
|
41
|
+
interface ResolvedEnumifyOptions {
|
|
42
|
+
artisanBin: string;
|
|
43
|
+
artisanFile: string;
|
|
44
|
+
syncCommand: string;
|
|
45
|
+
cwd: string;
|
|
46
|
+
watch: boolean;
|
|
47
|
+
env: Record<string, string>;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Laravel Enumify Vite plugin.
|
|
52
|
+
*/
|
|
53
|
+
declare function enumify(options?: EnumifyOptions): Plugin;
|
|
54
|
+
|
|
55
|
+
// @ts-ignore
|
|
56
|
+
export = enumify;
|
|
57
|
+
export { enumify };
|
|
58
|
+
export type { EnumifyOptions, ResolvedEnumifyOptions };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { spawn } from 'child_process';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
|
|
5
|
+
const DEFAULT_ENUM_PATHS = ["app/Enums"];
|
|
6
|
+
const DEFAULT_OUTPUT_PATH = "resources/js/enums";
|
|
7
|
+
const DEFAULT_WATCH = true;
|
|
8
|
+
const WATCH_DEBOUNCE_MS = 250;
|
|
9
|
+
function resolveOptions(options = {}) {
|
|
10
|
+
const cwd = options.cwd ?? process.cwd();
|
|
11
|
+
const config = readEnumifyConfig(cwd);
|
|
12
|
+
return {
|
|
13
|
+
options: {
|
|
14
|
+
artisanBin: options.artisanBin ?? "php",
|
|
15
|
+
artisanFile: options.artisanFile ?? "artisan",
|
|
16
|
+
syncCommand: options.syncCommand ?? "enumify:sync",
|
|
17
|
+
cwd,
|
|
18
|
+
watch: options.watch ?? config.watch,
|
|
19
|
+
env: options.env ?? {}
|
|
20
|
+
},
|
|
21
|
+
config
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
function readEnumifyConfig(cwd) {
|
|
25
|
+
const defaults = {
|
|
26
|
+
enumPaths: DEFAULT_ENUM_PATHS,
|
|
27
|
+
outputPath: DEFAULT_OUTPUT_PATH,
|
|
28
|
+
watch: DEFAULT_WATCH
|
|
29
|
+
};
|
|
30
|
+
const configPath = path.join(cwd, "config", "enumify.php");
|
|
31
|
+
if (!fs.existsSync(configPath)) {
|
|
32
|
+
return defaults;
|
|
33
|
+
}
|
|
34
|
+
try {
|
|
35
|
+
const contents = fs.readFileSync(configPath, "utf8");
|
|
36
|
+
return {
|
|
37
|
+
enumPaths: extractStringArray(contents, "enums") ?? defaults.enumPaths,
|
|
38
|
+
outputPath: extractString(contents, "output") ?? defaults.outputPath,
|
|
39
|
+
watch: extractBoolean(contents, "watch") ?? defaults.watch
|
|
40
|
+
};
|
|
41
|
+
} catch {
|
|
42
|
+
return defaults;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function extractStringArray(contents, key) {
|
|
46
|
+
const match = new RegExp(`['"]${key}['"]\\s*=>\\s*\\[([\\s\\S]*?)\\]`, "m").exec(contents);
|
|
47
|
+
if (!match) {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
const items = [];
|
|
51
|
+
const itemRegex = /['"]([^'"]+)['"]/g;
|
|
52
|
+
let itemMatch;
|
|
53
|
+
while ((itemMatch = itemRegex.exec(match[1])) !== null) {
|
|
54
|
+
items.push(itemMatch[1]);
|
|
55
|
+
}
|
|
56
|
+
return items.length > 0 ? items : null;
|
|
57
|
+
}
|
|
58
|
+
function extractString(contents, key) {
|
|
59
|
+
const match = new RegExp(`['"]${key}['"]\\s*=>\\s*['"]([^'"]+)['"]`, "m").exec(contents);
|
|
60
|
+
return match ? match[1] : null;
|
|
61
|
+
}
|
|
62
|
+
function extractBoolean(contents, key) {
|
|
63
|
+
const match = new RegExp(`['"]${key}['"]\\s*=>\\s*(true|false)`, "mi").exec(contents);
|
|
64
|
+
if (!match) {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
return match[1].toLowerCase() === "true";
|
|
68
|
+
}
|
|
69
|
+
function toAbsolutePath(cwd, value) {
|
|
70
|
+
return path.isAbsolute(value) ? value : path.join(cwd, value);
|
|
71
|
+
}
|
|
72
|
+
function isPathInside(filePath, dirPath) {
|
|
73
|
+
const relative = path.relative(dirPath, filePath);
|
|
74
|
+
return relative !== "" && !relative.startsWith("..") && !path.isAbsolute(relative);
|
|
75
|
+
}
|
|
76
|
+
function debounce(fn, delay) {
|
|
77
|
+
let timeoutId = null;
|
|
78
|
+
return (...args) => {
|
|
79
|
+
if (timeoutId) {
|
|
80
|
+
clearTimeout(timeoutId);
|
|
81
|
+
}
|
|
82
|
+
timeoutId = setTimeout(() => {
|
|
83
|
+
fn(...args);
|
|
84
|
+
timeoutId = null;
|
|
85
|
+
}, delay);
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
function enumify(options = {}) {
|
|
89
|
+
const { options: resolved, config } = resolveOptions(options);
|
|
90
|
+
const enumDirs = config.enumPaths.map((enumPath) => toAbsolutePath(resolved.cwd, enumPath));
|
|
91
|
+
const outputDir = toAbsolutePath(resolved.cwd, config.outputPath);
|
|
92
|
+
let viteConfig = null;
|
|
93
|
+
let logger = console;
|
|
94
|
+
let running = false;
|
|
95
|
+
let rerunRequested = false;
|
|
96
|
+
let initialSyncDone = false;
|
|
97
|
+
const spawnSync = () => new Promise((resolve, reject) => {
|
|
98
|
+
const args = [resolved.artisanFile, resolved.syncCommand, "--force", "--quiet"];
|
|
99
|
+
const child = spawn(resolved.artisanBin, args, {
|
|
100
|
+
cwd: resolved.cwd,
|
|
101
|
+
env: { ...process.env, ...resolved.env },
|
|
102
|
+
stdio: "inherit"
|
|
103
|
+
});
|
|
104
|
+
child.on("error", (error) => reject(error));
|
|
105
|
+
child.on("close", (code) => {
|
|
106
|
+
if (code === 0) {
|
|
107
|
+
resolve();
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
reject(new Error(`Enumify sync failed with exit code ${code}`));
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
const runSync = async () => {
|
|
114
|
+
if (running) {
|
|
115
|
+
rerunRequested = true;
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
running = true;
|
|
119
|
+
do {
|
|
120
|
+
rerunRequested = false;
|
|
121
|
+
await spawnSync();
|
|
122
|
+
logger.info("[plugin @devwizard/vite-plugin-enumify] Enum types generated successfully");
|
|
123
|
+
} while (rerunRequested);
|
|
124
|
+
running = false;
|
|
125
|
+
};
|
|
126
|
+
const debouncedSync = debounce(() => {
|
|
127
|
+
runSync().catch((error) => logger.error(`[enumify] ${error.message}`));
|
|
128
|
+
}, WATCH_DEBOUNCE_MS);
|
|
129
|
+
return {
|
|
130
|
+
name: "@devwizard/vite-plugin-enumify",
|
|
131
|
+
enforce: "pre",
|
|
132
|
+
configResolved(resolvedConfig) {
|
|
133
|
+
viteConfig = resolvedConfig;
|
|
134
|
+
logger = resolvedConfig.logger;
|
|
135
|
+
},
|
|
136
|
+
buildStart() {
|
|
137
|
+
if (viteConfig?.command === "serve") {
|
|
138
|
+
if (initialSyncDone) {
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
initialSyncDone = true;
|
|
142
|
+
}
|
|
143
|
+
return runSync().catch((error) => {
|
|
144
|
+
this.error(`[enumify] ${error.message}`);
|
|
145
|
+
});
|
|
146
|
+
},
|
|
147
|
+
configureServer(server) {
|
|
148
|
+
if (!resolved.watch) {
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
for (const enumDir of enumDirs) {
|
|
152
|
+
server.watcher.add(enumDir);
|
|
153
|
+
}
|
|
154
|
+
},
|
|
155
|
+
handleHotUpdate({ file }) {
|
|
156
|
+
if (!resolved.watch) {
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
const filePath = path.resolve(file);
|
|
160
|
+
if (isPathInside(filePath, outputDir)) {
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
for (const enumDir of enumDirs) {
|
|
164
|
+
if (isPathInside(filePath, enumDir)) {
|
|
165
|
+
debouncedSync();
|
|
166
|
+
break;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export { enumify as default, enumify };
|
package/package.json
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@devwizard/vite-plugin-enumify",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Vite plugin for Laravel Enumify - auto-sync PHP enums to TypeScript",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"vite",
|
|
7
|
+
"vite-plugin",
|
|
8
|
+
"laravel",
|
|
9
|
+
"enum",
|
|
10
|
+
"typescript",
|
|
11
|
+
"codegen"
|
|
12
|
+
],
|
|
13
|
+
"author": {
|
|
14
|
+
"name": "IQBAL HASAN",
|
|
15
|
+
"email": "devwizard24@gmail.com",
|
|
16
|
+
"url": "https://github.com/DevWizardHQ"
|
|
17
|
+
},
|
|
18
|
+
"license": "MIT",
|
|
19
|
+
"funding": {
|
|
20
|
+
"type": "github",
|
|
21
|
+
"url": "https://github.com/sponsors/DevWizardHQ"
|
|
22
|
+
},
|
|
23
|
+
"homepage": "https://github.com/devwizardhq/vite-plugin-enumify#readme",
|
|
24
|
+
"repository": {
|
|
25
|
+
"type": "git",
|
|
26
|
+
"url": "git+https://github.com/devwizardhq/vite-plugin-enumify.git"
|
|
27
|
+
},
|
|
28
|
+
"bugs": {
|
|
29
|
+
"url": "https://github.com/devwizardhq/vite-plugin-enumify/issues"
|
|
30
|
+
},
|
|
31
|
+
"scripts": {
|
|
32
|
+
"prepublishOnly": "npm run build",
|
|
33
|
+
"dev": "unbuild --stub",
|
|
34
|
+
"build": "unbuild",
|
|
35
|
+
"test": "vitest run",
|
|
36
|
+
"lint": "tsc --noEmit",
|
|
37
|
+
"typecheck": "tsc --noEmit",
|
|
38
|
+
"format": "prettier --write ."
|
|
39
|
+
},
|
|
40
|
+
"main": "dist/index.cjs",
|
|
41
|
+
"module": "dist/index.mjs",
|
|
42
|
+
"types": "dist/index.d.ts",
|
|
43
|
+
"exports": {
|
|
44
|
+
".": {
|
|
45
|
+
"import": "./dist/index.mjs",
|
|
46
|
+
"require": "./dist/index.cjs",
|
|
47
|
+
"types": "./dist/index.d.ts"
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
"files": [
|
|
51
|
+
"dist",
|
|
52
|
+
"README.md"
|
|
53
|
+
],
|
|
54
|
+
"peerDependencies": {
|
|
55
|
+
"vite": "^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
|
|
56
|
+
},
|
|
57
|
+
"devDependencies": {
|
|
58
|
+
"@types/node": "^25.0.8",
|
|
59
|
+
"prettier": "^3.5.0",
|
|
60
|
+
"rollup": "^4.40.0",
|
|
61
|
+
"typescript": "^5.7.0",
|
|
62
|
+
"unbuild": "^3.5.0",
|
|
63
|
+
"vite": "^7.3.1",
|
|
64
|
+
"vitest": "^4.0.17"
|
|
65
|
+
},
|
|
66
|
+
"engines": {
|
|
67
|
+
"node": ">=18.0.0"
|
|
68
|
+
},
|
|
69
|
+
"publishConfig": {
|
|
70
|
+
"access": "public"
|
|
71
|
+
}
|
|
72
|
+
}
|