@lumiflora/fsdk 0.1.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.
- package/README.md +150 -0
- package/dist/commands/add-component.d.ts +7 -0
- package/dist/commands/add-component.d.ts.map +1 -0
- package/dist/commands/add-component.js +93 -0
- package/dist/commands/add-page.d.ts +8 -0
- package/dist/commands/add-page.d.ts.map +1 -0
- package/dist/commands/add-page.js +98 -0
- package/dist/commands/add-store.d.ts +6 -0
- package/dist/commands/add-store.d.ts.map +1 -0
- package/dist/commands/add-store.js +204 -0
- package/dist/commands/completion.d.ts +8 -0
- package/dist/commands/completion.d.ts.map +1 -0
- package/dist/commands/completion.js +271 -0
- package/dist/commands/create-app.d.ts +10 -0
- package/dist/commands/create-app.d.ts.map +1 -0
- package/dist/commands/create-app.js +174 -0
- package/dist/commands/index.d.ts +9 -0
- package/dist/commands/index.d.ts.map +1 -0
- package/dist/commands/index.js +8 -0
- package/dist/commands/preview.d.ts +8 -0
- package/dist/commands/preview.d.ts.map +1 -0
- package/dist/commands/preview.js +162 -0
- package/dist/commands/sync-template.d.ts +6 -0
- package/dist/commands/sync-template.d.ts.map +1 -0
- package/dist/commands/sync-template.js +94 -0
- package/dist/commands/validate.d.ts +7 -0
- package/dist/commands/validate.d.ts.map +1 -0
- package/dist/commands/validate.js +136 -0
- package/dist/completion/completion-scripts.d.ts +4 -0
- package/dist/completion/completion-scripts.d.ts.map +1 -0
- package/dist/completion/completion-scripts.js +193 -0
- package/dist/completion/index.d.ts +2 -0
- package/dist/completion/index.d.ts.map +1 -0
- package/dist/completion/index.js +1 -0
- package/dist/core/config-loader.d.ts +91 -0
- package/dist/core/config-loader.d.ts.map +1 -0
- package/dist/core/config-loader.js +118 -0
- package/dist/core/hot-reload.d.ts +24 -0
- package/dist/core/hot-reload.d.ts.map +1 -0
- package/dist/core/hot-reload.js +97 -0
- package/dist/core/index.d.ts +5 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +4 -0
- package/dist/core/plugin-system.d.ts +32 -0
- package/dist/core/plugin-system.d.ts.map +1 -0
- package/dist/core/plugin-system.js +69 -0
- package/dist/core/template-engine.d.ts +22 -0
- package/dist/core/template-engine.d.ts.map +1 -0
- package/dist/core/template-engine.js +89 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +214 -0
- package/dist/plugins/add-component-plugin.d.ts +4 -0
- package/dist/plugins/add-component-plugin.d.ts.map +1 -0
- package/dist/plugins/add-component-plugin.js +23 -0
- package/dist/plugins/add-page-plugin.d.ts +4 -0
- package/dist/plugins/add-page-plugin.d.ts.map +1 -0
- package/dist/plugins/add-page-plugin.js +23 -0
- package/dist/plugins/add-store-plugin.d.ts +4 -0
- package/dist/plugins/add-store-plugin.d.ts.map +1 -0
- package/dist/plugins/add-store-plugin.js +23 -0
- package/dist/plugins/core-plugin.d.ts +4 -0
- package/dist/plugins/core-plugin.d.ts.map +1 -0
- package/dist/plugins/core-plugin.js +22 -0
- package/dist/plugins/index.d.ts +5 -0
- package/dist/plugins/index.d.ts.map +1 -0
- package/dist/plugins/index.js +4 -0
- package/dist/utils/command-helpers.d.ts +15 -0
- package/dist/utils/command-helpers.d.ts.map +1 -0
- package/dist/utils/command-helpers.js +40 -0
- package/dist/utils/index.d.ts +5 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +4 -0
- package/dist/utils/logger.d.ts +19 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +32 -0
- package/dist/utils/path.d.ts +9 -0
- package/dist/utils/path.d.ts.map +1 -0
- package/dist/utils/path.js +66 -0
- package/dist/utils/spinner.d.ts +23 -0
- package/dist/utils/spinner.d.ts.map +1 -0
- package/dist/utils/spinner.js +61 -0
- package/package.json +37 -0
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
import fs from 'fs-extra';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { logger } from '../utils/index.js';
|
|
4
|
+
const BASH_COMPLETION = `#!/bin/bash
|
|
5
|
+
# fsdk bash completion
|
|
6
|
+
|
|
7
|
+
_fsdk_completion() {
|
|
8
|
+
local cur prev opts
|
|
9
|
+
COMPREPLY=()
|
|
10
|
+
cur="\${COMP_WORDS[COMP_CWORD]}"
|
|
11
|
+
prev="\${COMP_WORDS[COMP_CWORD-1]}"
|
|
12
|
+
|
|
13
|
+
opts="create add-page add-component add-store sync-template validate preview completion"
|
|
14
|
+
|
|
15
|
+
case "\${prev}" in
|
|
16
|
+
create)
|
|
17
|
+
opts="--template --package-manager --eslint --no-git --no-install"
|
|
18
|
+
;;
|
|
19
|
+
add-page)
|
|
20
|
+
opts="--router-path --page-name"
|
|
21
|
+
;;
|
|
22
|
+
add-component)
|
|
23
|
+
opts="--type --name --dir"
|
|
24
|
+
;;
|
|
25
|
+
add-store)
|
|
26
|
+
opts="--type --name"
|
|
27
|
+
;;
|
|
28
|
+
sync-template)
|
|
29
|
+
opts="--template --force"
|
|
30
|
+
;;
|
|
31
|
+
validate)
|
|
32
|
+
opts="--config --templates --strict"
|
|
33
|
+
;;
|
|
34
|
+
preview)
|
|
35
|
+
opts="--port --host --open --template"
|
|
36
|
+
;;
|
|
37
|
+
completion)
|
|
38
|
+
opts="bash zsh fish"
|
|
39
|
+
;;
|
|
40
|
+
esac
|
|
41
|
+
|
|
42
|
+
COMPREPLY=($(compgen -W "\${opts}" -- "\${cur}"))
|
|
43
|
+
return 0
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
complete -F _fsdk_completion fsdk
|
|
47
|
+
`;
|
|
48
|
+
const ZSH_COMPLETION = `#fsdk completion
|
|
49
|
+
autoload -Uz compinit
|
|
50
|
+
compinit 2>/dev/null
|
|
51
|
+
|
|
52
|
+
# Define the completion function
|
|
53
|
+
_fsdk() {
|
|
54
|
+
local -a _fsdk_cmds
|
|
55
|
+
_fsdk_cmds=(
|
|
56
|
+
'create:Create a new application'
|
|
57
|
+
'add-page:Add a new page'
|
|
58
|
+
'add-component:Add a new component'
|
|
59
|
+
'add-store:Add a new store'
|
|
60
|
+
'sync-template:Sync template files'
|
|
61
|
+
'validate:Validate configuration and templates'
|
|
62
|
+
'preview:Preview template'
|
|
63
|
+
'completion:Generate shell completion script'
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
_arguments -C \\
|
|
67
|
+
'1: :->command' \\
|
|
68
|
+
'2: :->args' \\
|
|
69
|
+
'*: :->rest'
|
|
70
|
+
|
|
71
|
+
case "$state" in
|
|
72
|
+
command)
|
|
73
|
+
_describe 'command' _fsdk_cmds
|
|
74
|
+
;;
|
|
75
|
+
args)
|
|
76
|
+
case $words[1] in
|
|
77
|
+
create)
|
|
78
|
+
_arguments -s \\
|
|
79
|
+
'--template[Template name]' \\
|
|
80
|
+
'--package-manager[Package manager: npm|pnpm|yarn|bun]' \\
|
|
81
|
+
'--eslint[Enable ESLint]' \\
|
|
82
|
+
'--no-git[Skip git initialization]' \\
|
|
83
|
+
'--no-install[Skip dependency installation]'
|
|
84
|
+
;;
|
|
85
|
+
add-page)
|
|
86
|
+
_arguments -s \\
|
|
87
|
+
'--router-path[Router path]' \\
|
|
88
|
+
'--page-name[Page name]'
|
|
89
|
+
;;
|
|
90
|
+
add-component)
|
|
91
|
+
_arguments -s \\
|
|
92
|
+
'--type[Component type: page|common|business]' \\
|
|
93
|
+
'--name[Component name]' \\
|
|
94
|
+
'--dir[Subdirectory]'
|
|
95
|
+
;;
|
|
96
|
+
add-store)
|
|
97
|
+
_arguments -s \\
|
|
98
|
+
'--type[Store type: pinia|redux]' \\
|
|
99
|
+
'--name[Store name]'
|
|
100
|
+
;;
|
|
101
|
+
sync-template)
|
|
102
|
+
_arguments -s \\
|
|
103
|
+
'--template[Template name]' \\
|
|
104
|
+
'--force[Force sync]'
|
|
105
|
+
;;
|
|
106
|
+
validate)
|
|
107
|
+
_arguments -s \\
|
|
108
|
+
'--config[Validate config only]' \\
|
|
109
|
+
'--templates[Validate templates only]' \\
|
|
110
|
+
'--strict[Strict validation]'
|
|
111
|
+
;;
|
|
112
|
+
preview)
|
|
113
|
+
_arguments -s \\
|
|
114
|
+
'--port[Port number]' \\
|
|
115
|
+
'--host[Host address]' \\
|
|
116
|
+
'--open[Open browser]' \\
|
|
117
|
+
'--template[Template name]'
|
|
118
|
+
;;
|
|
119
|
+
completion)
|
|
120
|
+
_arguments -s \\
|
|
121
|
+
'bash:Generate bash completion' \\
|
|
122
|
+
'zsh:Generate zsh completion' \\
|
|
123
|
+
'fish:Generate fish completion'
|
|
124
|
+
;;
|
|
125
|
+
esac
|
|
126
|
+
;;
|
|
127
|
+
esac
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
# Register the completion function for fsdk command
|
|
131
|
+
compdef _fsdk fsdk
|
|
132
|
+
`;
|
|
133
|
+
const FISH_COMPLETION = `# fish completion for fsdk
|
|
134
|
+
|
|
135
|
+
complete -c fsdk -n '__fish_use_subcommand' -a 'create' -d 'Create a new application'
|
|
136
|
+
complete -c fsdk -n '__fish_use_subcommand' -a 'add-page' -d 'Add a new page'
|
|
137
|
+
complete -c fsdk -n '__fish_use_subcommand' -a 'add-component' -d 'Add a new component'
|
|
138
|
+
complete -c fsdk -n '__fish_use_subcommand' -a 'add-store' -d 'Add a new store'
|
|
139
|
+
complete -c fsdk -n '__fish_use_subcommand' -a 'sync-template' -d 'Sync template files'
|
|
140
|
+
complete -c fsdk -n '__fish_use_subcommand' -a 'validate' -d 'Validate configuration and templates'
|
|
141
|
+
complete -c fsdk -n '__fish_use_subcommand' -a 'preview' -d 'Preview template'
|
|
142
|
+
complete -c fsdk -n '__fish_use_subcommand' -a 'completion' -d 'Generate shell completion script'
|
|
143
|
+
|
|
144
|
+
# create options
|
|
145
|
+
complete -c fsdk -n '__fish_seen_subcommand_from create' -l template -d 'Template name'
|
|
146
|
+
complete -c fsdk -n '__fish_seen_subcommand_from create' -l package-manager -d 'Package manager' -a 'npm pnpm yarn bun'
|
|
147
|
+
complete -c fsdk -n '__fish_seen_subcommand_from create' -l eslint -d 'Enable ESLint'
|
|
148
|
+
complete -c fsdk -n '__fish_seen_subcommand_from create' -l no-git -d 'Skip git initialization'
|
|
149
|
+
complete -c fsdk -n '__fish_seen_subcommand_from create' -l no-install -d 'Skip dependency installation'
|
|
150
|
+
|
|
151
|
+
# add-page options
|
|
152
|
+
complete -c fsdk -n '__fish_seen_subcommand_from add-page' -l router-path -d 'Router path'
|
|
153
|
+
complete -c fsdk -n '__fish_seen_subcommand_from add-page' -l page-name -d 'Page name'
|
|
154
|
+
|
|
155
|
+
# add-component options
|
|
156
|
+
complete -c fsdk -n '__fish_seen_subcommand_from add-component' -l type -d 'Component type' -a 'page common business'
|
|
157
|
+
complete -c fsdk -n '__fish_seen_subcommand_from add-component' -l name -d 'Component name'
|
|
158
|
+
complete -c fsdk -n '__fish_seen_subcommand_from add-component' -l dir -d 'Subdirectory'
|
|
159
|
+
|
|
160
|
+
# add-store options
|
|
161
|
+
complete -c fsdk -n '__fish_seen_subcommand_from add-store' -l type -d 'Store type' -a 'pinia redux'
|
|
162
|
+
complete -c fsdk -n '__fish_seen_subcommand_from add-store' -l name -d 'Store name'
|
|
163
|
+
|
|
164
|
+
# sync-template options
|
|
165
|
+
complete -c fsdk -n '__fish_seen_subcommand_from sync-template' -l template -d 'Template name'
|
|
166
|
+
complete -c fsdk -n '__fish_seen_subcommand_from sync-template' -l force -d 'Force sync'
|
|
167
|
+
|
|
168
|
+
# validate options
|
|
169
|
+
complete -c fsdk -n '__fish_seen_subcommand_from validate' -l config -d 'Validate config only'
|
|
170
|
+
complete -c fsdk -n '__fish_seen_subcommand_from validate' -l templates -d 'Validate templates only'
|
|
171
|
+
complete -c fsdk -n '__fish_seen_subcommand_from validate' -l strict -d 'Strict validation'
|
|
172
|
+
|
|
173
|
+
# preview options
|
|
174
|
+
complete -c fsdk -n '__fish_seen_subcommand_from preview' -l port -d 'Port number'
|
|
175
|
+
complete -c fsdk -n '__fish_seen_subcommand_from preview' -l host -d 'Host address'
|
|
176
|
+
complete -c fsdk -n '__fish_seen_subcommand_from preview' -l open -d 'Open browser'
|
|
177
|
+
complete -c fsdk -n '__fish_seen_subcommand_from preview' -l template -d 'Template name'
|
|
178
|
+
`;
|
|
179
|
+
export async function generateCompletion(options = {}) {
|
|
180
|
+
const shell = options.shell || detectShell();
|
|
181
|
+
const outputPath = options.output || getDefaultOutputPath(shell);
|
|
182
|
+
const install = options.install || false;
|
|
183
|
+
try {
|
|
184
|
+
const content = getCompletionScript(shell);
|
|
185
|
+
await fs.ensureDir(path.dirname(outputPath));
|
|
186
|
+
await fs.writeFile(outputPath, content, 'utf-8');
|
|
187
|
+
if (shell === 'bash') {
|
|
188
|
+
fs.chmodSync(outputPath, 0o755);
|
|
189
|
+
}
|
|
190
|
+
logger.success(`Completion script for ${shell} generated at ${outputPath}`);
|
|
191
|
+
if (install && shell !== 'fish') {
|
|
192
|
+
await installToShellRc(shell, outputPath);
|
|
193
|
+
}
|
|
194
|
+
else if (!install) {
|
|
195
|
+
if (shell !== 'fish') {
|
|
196
|
+
logger.info(`Add the following line to your shell configuration:`);
|
|
197
|
+
logger.info(` source ${outputPath}`);
|
|
198
|
+
}
|
|
199
|
+
else {
|
|
200
|
+
logger.info(`Add the following line to your fish config:`);
|
|
201
|
+
logger.info(` source ${outputPath}`);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
catch (error) {
|
|
206
|
+
logger.error('Failed to generate completion script:', error);
|
|
207
|
+
throw error;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
async function installToShellRc(shell, completionPath) {
|
|
211
|
+
const home = process.env.HOME || '';
|
|
212
|
+
let rcFile = '';
|
|
213
|
+
let sourceLine = `source ${completionPath}`;
|
|
214
|
+
if (shell === 'bash') {
|
|
215
|
+
rcFile = path.resolve(home, '.bashrc');
|
|
216
|
+
}
|
|
217
|
+
else if (shell === 'zsh') {
|
|
218
|
+
rcFile = path.resolve(home, '.zshrc');
|
|
219
|
+
}
|
|
220
|
+
else {
|
|
221
|
+
logger.warning(`--install is not supported for fish shell yet`);
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
try {
|
|
225
|
+
let rcContent = '';
|
|
226
|
+
if (await fs.pathExists(rcFile)) {
|
|
227
|
+
rcContent = await fs.readFile(rcFile, 'utf-8');
|
|
228
|
+
}
|
|
229
|
+
const pattern = shell === 'bash' ? /\.fsdk-completion/ : 'fsdk-completion';
|
|
230
|
+
if (rcContent.includes(pattern)) {
|
|
231
|
+
logger.info(`Completion already installed in ${rcFile}`);
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
await fs.appendFile(rcFile, `\n# fsdk completion\n${sourceLine}\n`);
|
|
235
|
+
logger.success(`Completion installed to ${rcFile}`);
|
|
236
|
+
logger.info(`Run "source ${rcFile}" or restart your shell to apply`);
|
|
237
|
+
}
|
|
238
|
+
catch (error) {
|
|
239
|
+
logger.error(`Failed to install completion to ${rcFile}:`, error);
|
|
240
|
+
throw error;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
function detectShell() {
|
|
244
|
+
const shell = process.env.SHELL || '';
|
|
245
|
+
if (shell.includes('zsh'))
|
|
246
|
+
return 'zsh';
|
|
247
|
+
if (shell.includes('fish'))
|
|
248
|
+
return 'fish';
|
|
249
|
+
return 'bash';
|
|
250
|
+
}
|
|
251
|
+
function getDefaultOutputPath(shell) {
|
|
252
|
+
const home = process.env.HOME || process.env.USERPROFILE || '';
|
|
253
|
+
switch (shell) {
|
|
254
|
+
case 'bash':
|
|
255
|
+
return path.resolve(home, '.fsdk-completion.bash');
|
|
256
|
+
case 'zsh':
|
|
257
|
+
return path.resolve(home, '.fsdk-completion.zsh');
|
|
258
|
+
case 'fish':
|
|
259
|
+
return path.resolve(home, '.config', 'fish', 'completions', 'fsdk.fish');
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
function getCompletionScript(shell) {
|
|
263
|
+
switch (shell) {
|
|
264
|
+
case 'bash':
|
|
265
|
+
return BASH_COMPLETION;
|
|
266
|
+
case 'zsh':
|
|
267
|
+
return ZSH_COMPLETION;
|
|
268
|
+
case 'fish':
|
|
269
|
+
return FISH_COMPLETION;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export interface CreateAppOptions {
|
|
2
|
+
template?: string;
|
|
3
|
+
packageManager?: 'npm' | 'pnpm' | 'yarn' | 'bun';
|
|
4
|
+
eslint?: boolean;
|
|
5
|
+
git?: boolean;
|
|
6
|
+
install?: boolean;
|
|
7
|
+
projectName?: string;
|
|
8
|
+
}
|
|
9
|
+
export declare function createApp(cwd: string, options?: CreateAppOptions): Promise<void>;
|
|
10
|
+
//# sourceMappingURL=create-app.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"create-app.d.ts","sourceRoot":"","sources":["../../src/commands/create-app.ts"],"names":[],"mappings":"AAOA,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,KAAK,GAAG,MAAM,GAAG,MAAM,GAAG,KAAK,CAAC;IACjD,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,wBAAsB,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,GAAE,gBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CAyF1F"}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import prompts from 'prompts';
|
|
2
|
+
import fs from 'fs-extra';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { logger, Spinner, getTemplatesRoot } from '../utils/index.js';
|
|
5
|
+
import { templateEngine } from '../core/index.js';
|
|
6
|
+
import { spawn } from 'child_process';
|
|
7
|
+
export async function createApp(cwd, options = {}) {
|
|
8
|
+
// Early check: if projectName is provided, verify directory doesn't exist
|
|
9
|
+
// This avoids wasting user time with prompts if directory already exists
|
|
10
|
+
if (options.projectName) {
|
|
11
|
+
const projectPath = path.resolve(cwd, options.projectName);
|
|
12
|
+
if (fs.existsSync(projectPath)) {
|
|
13
|
+
throw new Error(`Directory ${options.projectName} already exists`);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
// First resolve options to determine if interactive mode
|
|
17
|
+
const resolvedOptions = await resolveOptions(options, cwd);
|
|
18
|
+
const { projectName, template, packageManager, eslint, git, install } = resolvedOptions;
|
|
19
|
+
const projectPath = path.resolve(cwd, projectName);
|
|
20
|
+
const isInteractive = !options.template;
|
|
21
|
+
// Only show info log in non-interactive mode
|
|
22
|
+
if (!isInteractive) {
|
|
23
|
+
logger.info('Creating new application...');
|
|
24
|
+
}
|
|
25
|
+
// Only use spinner in non-interactive mode (when template is provided)
|
|
26
|
+
const spinner = new Spinner(!isInteractive, 'Creating project...');
|
|
27
|
+
try {
|
|
28
|
+
if (fs.existsSync(projectPath)) {
|
|
29
|
+
spinner.fail(`Directory ${projectName} already exists`);
|
|
30
|
+
throw new Error(`Directory ${projectName} already exists`);
|
|
31
|
+
}
|
|
32
|
+
spinner.text = 'Creating project structure...';
|
|
33
|
+
await fs.ensureDir(projectPath);
|
|
34
|
+
// Copy root template files (package.json, tsconfig, vite.config, etc.)
|
|
35
|
+
const rootTemplatePath = path.resolve(getTemplatesRoot(), template);
|
|
36
|
+
const rootFiles = ['package.json', 'tsconfig.json', 'tsconfig.app.json', 'vite.config.ts', 'index.html', '.gitignore', 'eslint.config.mjs'];
|
|
37
|
+
await Promise.all(rootFiles.map(async (file) => {
|
|
38
|
+
const source = path.resolve(rootTemplatePath, file);
|
|
39
|
+
const dest = path.resolve(projectPath, file);
|
|
40
|
+
if (await fs.pathExists(source)) {
|
|
41
|
+
await fs.copy(source, dest);
|
|
42
|
+
// Update package.json name to project name
|
|
43
|
+
if (file === 'package.json') {
|
|
44
|
+
const pkg = await fs.readJson(dest);
|
|
45
|
+
pkg.name = projectName;
|
|
46
|
+
await fs.writeJson(dest, pkg, { spaces: 2 });
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}));
|
|
50
|
+
// Render src template files
|
|
51
|
+
spinner.text = 'Rendering template files...';
|
|
52
|
+
await templateEngine.renderDirectory({
|
|
53
|
+
templateName: template,
|
|
54
|
+
templateDir: 'src',
|
|
55
|
+
outputDir: path.resolve(projectPath, 'src'),
|
|
56
|
+
data: {
|
|
57
|
+
projectName,
|
|
58
|
+
packageManager,
|
|
59
|
+
eslint,
|
|
60
|
+
git,
|
|
61
|
+
},
|
|
62
|
+
excludes: ['**/node_modules/**'],
|
|
63
|
+
});
|
|
64
|
+
await templateEngine.copyPublicFiles(template, projectPath);
|
|
65
|
+
if (git) {
|
|
66
|
+
spinner.text = 'Initializing git repository...';
|
|
67
|
+
try {
|
|
68
|
+
await spawnCommand('git', ['init'], projectPath);
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
logger.warning('Failed to initialize git repository');
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
if (install) {
|
|
75
|
+
spinner.text = `Installing dependencies with ${packageManager}...`;
|
|
76
|
+
await spawnCommand(packageManager, ['install'], projectPath);
|
|
77
|
+
}
|
|
78
|
+
spinner.succeed(`Project ${projectName} created successfully!`);
|
|
79
|
+
logger.info(`cd ${projectName} to start developing`);
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
spinner.fail('Failed to create project');
|
|
83
|
+
throw error;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
async function resolveOptions(options, cwd) {
|
|
87
|
+
// If all required options are provided, use them directly (non-interactive mode)
|
|
88
|
+
if (options.projectName && options.template) {
|
|
89
|
+
return {
|
|
90
|
+
projectName: options.projectName,
|
|
91
|
+
template: options.template,
|
|
92
|
+
packageManager: options.packageManager || 'pnpm',
|
|
93
|
+
eslint: options.eslint ?? true,
|
|
94
|
+
git: options.git ?? true,
|
|
95
|
+
install: options.install ?? true,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
// Otherwise, enter interactive mode
|
|
99
|
+
const responses = await prompts([
|
|
100
|
+
{
|
|
101
|
+
type: 'text',
|
|
102
|
+
name: 'projectName',
|
|
103
|
+
message: 'What is the project name?',
|
|
104
|
+
initial: 'my-app',
|
|
105
|
+
validate: (value) => /^[a-z0-9-]+$/.test(value) || 'Only lowercase letters, numbers, and hyphens are allowed',
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
type: 'select',
|
|
109
|
+
name: 'template',
|
|
110
|
+
message: 'Select a template',
|
|
111
|
+
choices: [
|
|
112
|
+
{ title: 'Base', value: 'base', description: 'Basic template with Vite' },
|
|
113
|
+
{ title: 'Full', value: 'full', description: 'Full-featured template with Vite + Element Plus + Pinia' },
|
|
114
|
+
],
|
|
115
|
+
initial: 0,
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
type: 'select',
|
|
119
|
+
name: 'packageManager',
|
|
120
|
+
message: 'Select a package manager',
|
|
121
|
+
choices: [
|
|
122
|
+
{ title: 'pnpm', value: 'pnpm' },
|
|
123
|
+
{ title: 'npm', value: 'npm' },
|
|
124
|
+
{ title: 'yarn', value: 'yarn' },
|
|
125
|
+
{ title: 'bun', value: 'bun' },
|
|
126
|
+
],
|
|
127
|
+
initial: 0,
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
type: 'confirm',
|
|
131
|
+
name: 'eslint',
|
|
132
|
+
message: 'Enable ESLint?',
|
|
133
|
+
initial: true,
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
type: 'confirm',
|
|
137
|
+
name: 'git',
|
|
138
|
+
message: 'Initialize git repository?',
|
|
139
|
+
initial: true,
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
type: 'confirm',
|
|
143
|
+
name: 'install',
|
|
144
|
+
message: 'Install dependencies?',
|
|
145
|
+
initial: true,
|
|
146
|
+
},
|
|
147
|
+
]);
|
|
148
|
+
return {
|
|
149
|
+
projectName: options.projectName || responses.projectName,
|
|
150
|
+
template: options.template || responses.template,
|
|
151
|
+
packageManager: options.packageManager || responses.packageManager,
|
|
152
|
+
eslint: options.eslint ?? responses.eslint,
|
|
153
|
+
git: options.git ?? responses.git,
|
|
154
|
+
install: options.install ?? responses.install,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
async function spawnCommand(cmd, args, cwd) {
|
|
158
|
+
return new Promise((resolve, reject) => {
|
|
159
|
+
const child = spawn(cmd, args, {
|
|
160
|
+
cwd,
|
|
161
|
+
stdio: 'inherit',
|
|
162
|
+
shell: process.platform === 'win32',
|
|
163
|
+
});
|
|
164
|
+
child.on('close', (code) => {
|
|
165
|
+
if (code === 0) {
|
|
166
|
+
resolve();
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
169
|
+
reject(new Error(`Command ${cmd} ${args.join(' ')} exited with code ${code}`));
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
child.on('error', reject);
|
|
173
|
+
});
|
|
174
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { createApp, type CreateAppOptions } from './create-app.js';
|
|
2
|
+
export { addPage, type AddPageOptions, syncPageRoutes } from './add-page.js';
|
|
3
|
+
export { addComponent, type AddComponentOptions } from './add-component.js';
|
|
4
|
+
export { addStore, type AddStoreOptions } from './add-store.js';
|
|
5
|
+
export { syncTemplate, type SyncTemplateOptions } from './sync-template.js';
|
|
6
|
+
export { validate, type ValidateOptions } from './validate.js';
|
|
7
|
+
export { preview, type PreviewOptions } from './preview.js';
|
|
8
|
+
export { generateCompletion, type CompletionOptions, type ShellType } from './completion.js';
|
|
9
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/commands/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,KAAK,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACnE,OAAO,EAAE,OAAO,EAAE,KAAK,cAAc,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC7E,OAAO,EAAE,YAAY,EAAE,KAAK,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAC5E,OAAO,EAAE,QAAQ,EAAE,KAAK,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAChE,OAAO,EAAE,YAAY,EAAE,KAAK,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAC5E,OAAO,EAAE,QAAQ,EAAE,KAAK,eAAe,EAAE,MAAM,eAAe,CAAC;AAC/D,OAAO,EAAE,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,cAAc,CAAC;AAC5D,OAAO,EAAE,kBAAkB,EAAE,KAAK,iBAAiB,EAAE,KAAK,SAAS,EAAE,MAAM,iBAAiB,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { createApp } from './create-app.js';
|
|
2
|
+
export { addPage, syncPageRoutes } from './add-page.js';
|
|
3
|
+
export { addComponent } from './add-component.js';
|
|
4
|
+
export { addStore } from './add-store.js';
|
|
5
|
+
export { syncTemplate } from './sync-template.js';
|
|
6
|
+
export { validate } from './validate.js';
|
|
7
|
+
export { preview } from './preview.js';
|
|
8
|
+
export { generateCompletion } from './completion.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"preview.d.ts","sourceRoot":"","sources":["../../src/commands/preview.ts"],"names":[],"mappings":"AASA,MAAM,WAAW,cAAc;IAC7B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,wBAAsB,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,GAAE,cAAmB,GAAG,OAAO,CAAC,IAAI,CAAC,CAkDtF"}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { spawn } from 'child_process';
|
|
2
|
+
import fs from 'fs-extra';
|
|
3
|
+
import http from 'http';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
import { logger } from '../utils/index.js';
|
|
6
|
+
import { templateEngine } from '../core/index.js';
|
|
7
|
+
import { hotReload } from '../core/index.js';
|
|
8
|
+
import { resolveTemplatePath } from '../utils/path.js';
|
|
9
|
+
export async function preview(cwd, options = {}) {
|
|
10
|
+
const port = options.port || 3000;
|
|
11
|
+
const host = options.host || 'localhost';
|
|
12
|
+
const templateName = options.template || 'base';
|
|
13
|
+
try {
|
|
14
|
+
const previewServerDir = path.resolve(cwd, '.fsdk', 'preview-temp');
|
|
15
|
+
await fs.ensureDir(previewServerDir);
|
|
16
|
+
logger.info(`Preparing preview server at http://${host}:${port}`);
|
|
17
|
+
// 监听模板源文件目录
|
|
18
|
+
const templateSrcDir = resolveTemplatePath(templateName, 'src');
|
|
19
|
+
const templatePublicDir = resolveTemplatePath(templateName, 'public');
|
|
20
|
+
const renderTemplate = async () => {
|
|
21
|
+
logger.info(`Re-rendering template: ${templateName}`);
|
|
22
|
+
await templateEngine.renderDirectory({
|
|
23
|
+
templateName,
|
|
24
|
+
outputDir: previewServerDir,
|
|
25
|
+
data: { port, host },
|
|
26
|
+
});
|
|
27
|
+
await templateEngine.copyPublicFiles(templateName, previewServerDir);
|
|
28
|
+
logger.success('Template re-rendered');
|
|
29
|
+
};
|
|
30
|
+
// 初始渲染
|
|
31
|
+
await renderTemplate();
|
|
32
|
+
await startPreviewServer(previewServerDir, port, host);
|
|
33
|
+
if (options.open) {
|
|
34
|
+
const openCmd = process.platform === 'win32' ? 'start' : (process.platform === 'darwin' ? 'open' : 'xdg-open');
|
|
35
|
+
spawn(openCmd, [`http://${host}:${port}`], { stdio: 'ignore', detached: true, shell: true });
|
|
36
|
+
}
|
|
37
|
+
await hotReload.start({
|
|
38
|
+
watchPaths: [templateSrcDir, templatePublicDir],
|
|
39
|
+
ignored: ['**/node_modules/**', '**/.git/**'],
|
|
40
|
+
onChange: async (event, file) => {
|
|
41
|
+
logger.info(`Template ${event}: ${path.relative(templateSrcDir, file) || path.relative(templatePublicDir, file)}`);
|
|
42
|
+
await renderTemplate();
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
logger.success('Preview server is running. Press Ctrl+C to stop.');
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
logger.error('Failed to start preview server:', error);
|
|
49
|
+
throw error;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
async function startPreviewServer(serverDir, port, host) {
|
|
53
|
+
const indexPath = path.resolve(serverDir, 'index.html');
|
|
54
|
+
if (!fs.existsSync(indexPath)) {
|
|
55
|
+
const html = generateDefaultHtml(port, host);
|
|
56
|
+
await fs.writeFile(indexPath, html);
|
|
57
|
+
}
|
|
58
|
+
return new Promise((resolve, reject) => {
|
|
59
|
+
const server = http.createServer(async (req, res) => {
|
|
60
|
+
try {
|
|
61
|
+
// Serve index.html for root path
|
|
62
|
+
if (req.url === '/' || req.url === '/index.html') {
|
|
63
|
+
const content = await fs.readFile(indexPath, 'utf-8');
|
|
64
|
+
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
65
|
+
res.end(content);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
// Try to serve static files
|
|
69
|
+
const filePath = path.join(serverDir, req.url || '');
|
|
70
|
+
if (await fs.pathExists(filePath)) {
|
|
71
|
+
const ext = path.extname(filePath);
|
|
72
|
+
const contentType = getContentType(ext);
|
|
73
|
+
const content = await fs.readFile(filePath);
|
|
74
|
+
res.writeHead(200, { 'Content-Type': contentType });
|
|
75
|
+
res.end(content);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
// 404 for unknown paths
|
|
79
|
+
res.writeHead(404, { 'Content-Type': 'text/plain' });
|
|
80
|
+
res.end('Not Found');
|
|
81
|
+
}
|
|
82
|
+
catch (error) {
|
|
83
|
+
logger.error('Error serving file:', error);
|
|
84
|
+
res.writeHead(500, { 'Content-Type': 'text/plain' });
|
|
85
|
+
res.end('Internal Server Error');
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
server.listen(port, host, () => {
|
|
89
|
+
logger.info(`Preview server running at http://${host}:${port}`);
|
|
90
|
+
// Handle graceful shutdown
|
|
91
|
+
server.on('close', () => {
|
|
92
|
+
logger.debug('Preview server closed');
|
|
93
|
+
});
|
|
94
|
+
resolve();
|
|
95
|
+
});
|
|
96
|
+
server.on('error', (error) => {
|
|
97
|
+
if (error.code === 'EADDRINUSE') {
|
|
98
|
+
reject(new Error(`Port ${port} is already in use`));
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
reject(error);
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
// Store server reference for shutdown
|
|
105
|
+
global.__previewServer = server;
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
function getContentType(ext) {
|
|
109
|
+
const contentTypes = {
|
|
110
|
+
'.html': 'text/html; charset=utf-8',
|
|
111
|
+
'.css': 'text/css; charset=utf-8',
|
|
112
|
+
'.js': 'application/javascript; charset=utf-8',
|
|
113
|
+
'.json': 'application/json',
|
|
114
|
+
'.png': 'image/png',
|
|
115
|
+
'.jpg': 'image/jpeg',
|
|
116
|
+
'.jpeg': 'image/jpeg',
|
|
117
|
+
'.svg': 'image/svg+xml',
|
|
118
|
+
'.ico': 'image/x-icon',
|
|
119
|
+
'.woff': 'font/woff',
|
|
120
|
+
'.woff2': 'font/woff2',
|
|
121
|
+
'.ttf': 'font/ttf',
|
|
122
|
+
'.eot': 'application/vnd.ms-fontobject',
|
|
123
|
+
};
|
|
124
|
+
return contentTypes[ext] || 'application/octet-stream';
|
|
125
|
+
}
|
|
126
|
+
function generateDefaultHtml(port, host) {
|
|
127
|
+
return `<!DOCTYPE html>
|
|
128
|
+
<html lang="en">
|
|
129
|
+
<head>
|
|
130
|
+
<meta charset="UTF-8">
|
|
131
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
132
|
+
<title>Fsdk Preview</title>
|
|
133
|
+
<style>
|
|
134
|
+
body {
|
|
135
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
136
|
+
display: flex;
|
|
137
|
+
justify-content: center;
|
|
138
|
+
align-items: center;
|
|
139
|
+
min-height: 100vh;
|
|
140
|
+
margin: 0;
|
|
141
|
+
background: #f5f5f5;
|
|
142
|
+
}
|
|
143
|
+
.container {
|
|
144
|
+
text-align: center;
|
|
145
|
+
padding: 40px;
|
|
146
|
+
background: white;
|
|
147
|
+
border-radius: 8px;
|
|
148
|
+
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
|
149
|
+
}
|
|
150
|
+
h1 { color: #333; }
|
|
151
|
+
p { color: #666; }
|
|
152
|
+
</style>
|
|
153
|
+
</head>
|
|
154
|
+
<body>
|
|
155
|
+
<div class="container">
|
|
156
|
+
<h1>Fsdk Preview</h1>
|
|
157
|
+
<p>Server running at http://${host}:${port}</p>
|
|
158
|
+
</div>
|
|
159
|
+
</body>
|
|
160
|
+
</html>
|
|
161
|
+
`;
|
|
162
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sync-template.d.ts","sourceRoot":"","sources":["../../src/commands/sync-template.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,wBAAsB,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,GAAE,mBAAwB,GAAG,OAAO,CAAC,IAAI,CAAC,CA2ChG"}
|