plusui-native 0.2.108 → 0.2.109

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.
@@ -1,82 +1,82 @@
1
- import { mkdir, readdir, readFile, writeFile, copyFile, stat } from 'fs/promises';
2
- import { join, dirname, resolve } from 'path';
3
- import { existsSync } from 'fs';
4
- import { fileURLToPath } from 'url';
5
- import { EnvironmentDoctor } from '../src/doctor/index.js';
6
- import chalk from 'chalk';
7
- import { spawn } from 'child_process';
8
- import { execSync } from 'child_process';
9
-
10
- const __filename = fileURLToPath(import.meta.url);
11
- const __dirname = dirname(__filename);
12
-
13
- export class TemplateManager {
14
- constructor() {
15
- this.templatesDir = __dirname;
16
- this.cliPackageJson = null;
17
- }
18
-
19
- async loadCliVersion() {
20
- if (!this.cliPackageJson) {
21
- const packageJsonPath = join(this.templatesDir, '..', 'package.json');
22
- const content = await readFile(packageJsonPath, 'utf8');
23
- this.cliPackageJson = JSON.parse(content);
24
- }
25
- return this.cliPackageJson.version;
26
- }
27
-
28
- async loadCliPackageJson() {
29
- if (!this.cliPackageJson) {
30
- const packageJsonPath = join(this.templatesDir, '..', 'package.json');
31
- const content = await readFile(packageJsonPath, 'utf8');
32
- this.cliPackageJson = JSON.parse(content);
33
- }
34
- return this.cliPackageJson;
35
- }
36
-
37
- getLatestPublishedVersion(packageName) {
38
- try {
39
- const version = execSync(`npm view ${packageName} version`, {
40
- encoding: 'utf8',
41
- stdio: ['pipe', 'pipe', 'ignore']
42
- }).trim();
43
- return version || null;
44
- } catch {
45
- return null;
46
- }
47
- }
48
-
49
- toCompatibleRange(version) {
50
- if (!version) {
51
- return null;
52
- }
53
- return version.startsWith('^') || version.startsWith('~') ? version : `^${version}`;
54
- }
55
-
56
- async resolveTemplatePackageVersions() {
57
- const cliPkg = await this.loadCliPackageJson();
58
- const cliVersion = cliPkg.version;
59
- const [major, minor] = cliVersion.split('.');
60
-
61
- const fallbackCliRange = `^${major}.${minor}.0`;
62
- const fallbackToolsRange = `^${major}.1.0`;
63
-
64
- const publishedCli = this.getLatestPublishedVersion('plusui-native');
65
- const publishedCore = this.getLatestPublishedVersion('plusui-native-core');
66
- const publishedBuilder = this.getLatestPublishedVersion('plusui-native-builder');
67
- const publishedConnect = this.getLatestPublishedVersion('plusui-native-connect');
68
-
69
- const cliDependencyBuilder = cliPkg.dependencies?.['plusui-native-builder'] || null;
70
- const cliDependencyConnect = cliPkg.dependencies?.['plusui-native-connect'] || null;
71
-
72
- return {
73
- cli: this.toCompatibleRange(publishedCli) || fallbackCliRange,
74
- core: this.toCompatibleRange(publishedCore) || fallbackToolsRange,
75
- builder: this.toCompatibleRange(publishedBuilder) || cliDependencyBuilder || fallbackToolsRange,
76
- connect: this.toCompatibleRange(publishedConnect) || cliDependencyConnect || fallbackToolsRange
77
- };
78
- }
79
-
1
+ import { mkdir, readdir, readFile, writeFile, copyFile, stat } from 'fs/promises';
2
+ import { join, dirname, resolve } from 'path';
3
+ import { existsSync } from 'fs';
4
+ import { fileURLToPath } from 'url';
5
+ import { EnvironmentDoctor } from '../src/doctor/index.js';
6
+ import chalk from 'chalk';
7
+ import { spawn } from 'child_process';
8
+ import { execSync } from 'child_process';
9
+
10
+ const __filename = fileURLToPath(import.meta.url);
11
+ const __dirname = dirname(__filename);
12
+
13
+ export class TemplateManager {
14
+ constructor() {
15
+ this.templatesDir = __dirname;
16
+ this.cliPackageJson = null;
17
+ }
18
+
19
+ async loadCliVersion() {
20
+ if (!this.cliPackageJson) {
21
+ const packageJsonPath = join(this.templatesDir, '..', 'package.json');
22
+ const content = await readFile(packageJsonPath, 'utf8');
23
+ this.cliPackageJson = JSON.parse(content);
24
+ }
25
+ return this.cliPackageJson.version;
26
+ }
27
+
28
+ async loadCliPackageJson() {
29
+ if (!this.cliPackageJson) {
30
+ const packageJsonPath = join(this.templatesDir, '..', 'package.json');
31
+ const content = await readFile(packageJsonPath, 'utf8');
32
+ this.cliPackageJson = JSON.parse(content);
33
+ }
34
+ return this.cliPackageJson;
35
+ }
36
+
37
+ getLatestPublishedVersion(packageName) {
38
+ try {
39
+ const version = execSync(`npm view ${packageName} version`, {
40
+ encoding: 'utf8',
41
+ stdio: ['pipe', 'pipe', 'ignore']
42
+ }).trim();
43
+ return version || null;
44
+ } catch {
45
+ return null;
46
+ }
47
+ }
48
+
49
+ toCompatibleRange(version) {
50
+ if (!version) {
51
+ return null;
52
+ }
53
+ return version.startsWith('^') || version.startsWith('~') ? version : `^${version}`;
54
+ }
55
+
56
+ async resolveTemplatePackageVersions() {
57
+ const cliPkg = await this.loadCliPackageJson();
58
+ const cliVersion = cliPkg.version;
59
+ const [major, minor] = cliVersion.split('.');
60
+
61
+ const fallbackCliRange = `^${major}.${minor}.0`;
62
+ const fallbackToolsRange = `^${major}.1.0`;
63
+
64
+ const publishedCli = this.getLatestPublishedVersion('plusui-native');
65
+ const publishedCore = this.getLatestPublishedVersion('plusui-native-core');
66
+ const publishedBuilder = this.getLatestPublishedVersion('plusui-native-builder');
67
+ const publishedConnect = this.getLatestPublishedVersion('plusui-native-connect');
68
+
69
+ const cliDependencyBuilder = cliPkg.dependencies?.['plusui-native-builder'] || null;
70
+ const cliDependencyConnect = cliPkg.dependencies?.['plusui-native-connect'] || null;
71
+
72
+ return {
73
+ cli: this.toCompatibleRange(publishedCli) || fallbackCliRange,
74
+ core: this.toCompatibleRange(publishedCore) || fallbackToolsRange,
75
+ builder: this.toCompatibleRange(publishedBuilder) || cliDependencyBuilder || fallbackToolsRange,
76
+ connect: this.toCompatibleRange(publishedConnect) || cliDependencyConnect || fallbackToolsRange
77
+ };
78
+ }
79
+
80
80
  async create(projectName, options = {}) {
81
81
  // Validate and sanitize project name
82
82
  const invalidChars = projectName.replace(/[a-zA-Z0-9_-]/g, '');
@@ -104,185 +104,185 @@ export class TemplateManager {
104
104
  }
105
105
 
106
106
  console.log(chalk.bold(`\nCreating PlusUI project: ${targetDirName}\n`));
107
-
108
- // 1. Check environment first
109
- console.log(chalk.blue('Checking environment...'));
110
- const doctor = new EnvironmentDoctor();
111
- const quickCheck = await doctor.quickCheck();
112
-
113
- if (!quickCheck.ready) {
114
- console.log(chalk.red('\n✗ Environment not ready!\n'));
115
- console.log(chalk.yellow('Run: plusui doctor --fix\n'));
116
- throw new Error('Environment check failed');
117
- }
118
-
119
- console.log(chalk.green('✓ Environment ready\n'));
120
-
121
- // 2. Create project directory
122
- console.log(chalk.blue('Creating project structure...'));
123
-
124
- if (projectName !== '.') {
125
- if (existsSync(projectPath)) {
126
- throw new Error(`Directory ${projectName} already exists`);
127
- }
128
- await mkdir(projectPath, { recursive: true });
129
- } else {
130
- // Warn but proceed if using current directory
131
- console.log(chalk.yellow(`Initializing in current directory...`));
132
- }
133
-
134
- // 3. Resolve package versions for generated app dependencies
135
- const resolvedVersions = await this.resolveTemplatePackageVersions();
136
-
137
- // 4. Prepare template variables
138
- const variables = {
139
- PROJECT_NAME: targetDirName,
140
- PROJECT_NAME_LOWER: targetDirName.toLowerCase(),
141
- PROJECT_VERSION: '0.1.0',
142
- PLUSUI_CLI_VERSION: resolvedVersions.cli,
143
- PLUSUI_CORE_VERSION: resolvedVersions.core,
144
- PLUSUI_BUILDER_VERSION: resolvedVersions.builder,
145
- PLUSUI_CONNECT_VERSION: resolvedVersions.connect
146
- };
147
-
148
- console.log(chalk.dim(`Using versions: core=${variables.PLUSUI_CORE_VERSION}, builder=${variables.PLUSUI_BUILDER_VERSION}, connect=${variables.PLUSUI_CONNECT_VERSION}`));
149
-
150
- // 5. Copy template files
151
- await this.copyTemplate(templatePath, projectPath, variables);
152
-
153
- // 5. Copy base files
154
- const basePath = join(this.templatesDir, 'base');
155
- await this.copyTemplate(basePath, projectPath, variables, true);
156
-
157
- console.log(chalk.green('✓ Project structure created\n'));
158
-
159
- // 6. Install npm dependencies
160
- console.log(chalk.blue('Installing dependencies...'));
161
- await this.runNpmInstall(projectPath);
162
- console.log(chalk.green('✓ Dependencies installed\n'));
163
-
164
- // 7. Start dev server in the created project immediately
165
- process.chdir(projectPath);
166
- console.log(chalk.green(`✓ Changed directory to: ${projectPath}`));
167
- console.log(chalk.blue('Starting development server...'));
168
- await this.runPlusuiDev(projectPath);
169
-
170
- return { success: true, path: projectPath };
171
- }
172
-
173
- async copyTemplate(templatePath, destPath, variables, skipSubdirs = false) {
174
- if (!existsSync(templatePath)) {
175
- return;
176
- }
177
-
178
- const entries = await readdir(templatePath, { withFileTypes: true });
179
-
180
- for (const entry of entries) {
181
- const srcPath = join(templatePath, entry.name);
182
- const destName = entry.name.replace('.template', '');
183
- const destFilePath = join(destPath, destName);
184
-
185
- if (entry.isDirectory()) {
186
- if (!skipSubdirs) {
187
- await mkdir(destFilePath, { recursive: true });
188
- await this.copyTemplate(srcPath, destFilePath, variables, false);
189
- }
190
- } else {
191
- // Ensure parent directory exists
192
- await mkdir(dirname(destFilePath), { recursive: true });
193
-
194
- // Read file content
195
- let content = await readFile(srcPath, 'utf8');
196
-
197
- // Replace variables
198
- content = this.replaceVariables(content, variables);
199
-
200
- // Write to destination
201
- await writeFile(destFilePath, content, 'utf8');
202
- }
203
- }
204
- }
205
-
206
- replaceVariables(content, variables) {
207
- let result = content;
208
-
209
- for (const [key, value] of Object.entries(variables)) {
210
- // Replace without spaces (e.g. {{PROJECT_NAME}})
211
- result = result.split(`{{${key}}}`).join(value);
212
- // Replace with spaces (e.g. {{ PROJECT_NAME }})
213
- result = result.split(`{{ ${key} }}`).join(value);
214
- }
215
-
216
- return result;
217
- }
218
-
219
- async runNpmInstall(projectPath) {
220
- return new Promise((resolve, reject) => {
221
- const npm = process.platform === 'win32' ? 'npm.cmd' : 'npm';
222
-
223
- // Install root dependencies
224
- const rootProc = spawn(npm, ['install'], {
225
- cwd: projectPath,
226
- stdio: 'inherit',
227
- shell: true
228
- });
229
-
230
- rootProc.on('close', (code) => {
231
- if (code !== 0) {
232
- reject(new Error(`npm install failed with code ${code}`));
233
- return;
234
- }
235
-
236
- // Install frontend dependencies
237
- const frontendPath = join(projectPath, 'frontend');
238
- if (existsSync(join(frontendPath, 'package.json'))) {
239
- const frontendProc = spawn(npm, ['install'], {
240
- cwd: frontendPath,
241
- stdio: 'inherit',
242
- shell: true
243
- });
244
-
245
- frontendProc.on('close', (feCode) => {
246
- if (feCode === 0) {
247
- resolve();
248
- } else {
249
- reject(new Error(`frontend npm install failed with code ${feCode}`));
250
- }
251
- });
252
-
253
- frontendProc.on('error', reject);
254
- } else {
255
- resolve();
256
- }
257
- });
258
-
259
- rootProc.on('error', reject);
260
- });
261
- }
262
-
263
- async runPlusuiDev(projectPath) {
264
- return new Promise((resolve, reject) => {
265
- const plusuiCmd = process.platform === 'win32' ? 'plusui.cmd' : 'plusui';
266
-
267
- const devProc = spawn(plusuiCmd, ['dev'], {
268
- cwd: projectPath,
269
- stdio: 'inherit',
270
- shell: true,
271
- });
272
-
273
- devProc.on('close', (code, signal) => {
274
- // Normal shutdown via Ctrl+C should not be treated as an error.
275
- if (signal === 'SIGINT' || code === 0 || code === null) {
276
- resolve();
277
- return;
278
- }
279
- reject(new Error(`plusui dev exited with code ${code}`));
280
- });
281
-
282
- devProc.on('error', reject);
283
- });
284
- }
285
-
286
- }
287
-
288
-
107
+
108
+ // 1. Check environment first
109
+ console.log(chalk.blue('Checking environment...'));
110
+ const doctor = new EnvironmentDoctor();
111
+ const quickCheck = await doctor.quickCheck();
112
+
113
+ if (!quickCheck.ready) {
114
+ console.log(chalk.red('\n✗ Environment not ready!\n'));
115
+ console.log(chalk.yellow('Run: plusui doctor --fix\n'));
116
+ throw new Error('Environment check failed');
117
+ }
118
+
119
+ console.log(chalk.green('✓ Environment ready\n'));
120
+
121
+ // 2. Create project directory
122
+ console.log(chalk.blue('Creating project structure...'));
123
+
124
+ if (projectName !== '.') {
125
+ if (existsSync(projectPath)) {
126
+ throw new Error(`Directory ${projectName} already exists`);
127
+ }
128
+ await mkdir(projectPath, { recursive: true });
129
+ } else {
130
+ // Warn but proceed if using current directory
131
+ console.log(chalk.yellow(`Initializing in current directory...`));
132
+ }
133
+
134
+ // 3. Resolve package versions for generated app dependencies
135
+ const resolvedVersions = await this.resolveTemplatePackageVersions();
136
+
137
+ // 4. Prepare template variables
138
+ const variables = {
139
+ PROJECT_NAME: targetDirName,
140
+ PROJECT_NAME_LOWER: targetDirName.toLowerCase(),
141
+ PROJECT_VERSION: '0.1.0',
142
+ PLUSUI_CLI_VERSION: resolvedVersions.cli,
143
+ PLUSUI_CORE_VERSION: resolvedVersions.core,
144
+ PLUSUI_BUILDER_VERSION: resolvedVersions.builder,
145
+ PLUSUI_CONNECT_VERSION: resolvedVersions.connect
146
+ };
147
+
148
+ console.log(chalk.dim(`Using versions: core=${variables.PLUSUI_CORE_VERSION}, builder=${variables.PLUSUI_BUILDER_VERSION}, connect=${variables.PLUSUI_CONNECT_VERSION}`));
149
+
150
+ // 5. Copy template files
151
+ await this.copyTemplate(templatePath, projectPath, variables);
152
+
153
+ // 5. Copy base files
154
+ const basePath = join(this.templatesDir, 'base');
155
+ await this.copyTemplate(basePath, projectPath, variables, true);
156
+
157
+ console.log(chalk.green('✓ Project structure created\n'));
158
+
159
+ // 6. Install npm dependencies
160
+ console.log(chalk.blue('Installing dependencies...'));
161
+ await this.runNpmInstall(projectPath);
162
+ console.log(chalk.green('✓ Dependencies installed\n'));
163
+
164
+ // 7. Start dev server in the created project immediately
165
+ process.chdir(projectPath);
166
+ console.log(chalk.green(`✓ Changed directory to: ${projectPath}`));
167
+ console.log(chalk.blue('Starting development server...'));
168
+ await this.runPlusuiDev(projectPath);
169
+
170
+ return { success: true, path: projectPath };
171
+ }
172
+
173
+ async copyTemplate(templatePath, destPath, variables, skipSubdirs = false) {
174
+ if (!existsSync(templatePath)) {
175
+ return;
176
+ }
177
+
178
+ const entries = await readdir(templatePath, { withFileTypes: true });
179
+
180
+ for (const entry of entries) {
181
+ const srcPath = join(templatePath, entry.name);
182
+ const destName = entry.name.replace('.template', '');
183
+ const destFilePath = join(destPath, destName);
184
+
185
+ if (entry.isDirectory()) {
186
+ if (!skipSubdirs) {
187
+ await mkdir(destFilePath, { recursive: true });
188
+ await this.copyTemplate(srcPath, destFilePath, variables, false);
189
+ }
190
+ } else {
191
+ // Ensure parent directory exists
192
+ await mkdir(dirname(destFilePath), { recursive: true });
193
+
194
+ // Read file content
195
+ let content = await readFile(srcPath, 'utf8');
196
+
197
+ // Replace variables
198
+ content = this.replaceVariables(content, variables);
199
+
200
+ // Write to destination
201
+ await writeFile(destFilePath, content, 'utf8');
202
+ }
203
+ }
204
+ }
205
+
206
+ replaceVariables(content, variables) {
207
+ let result = content;
208
+
209
+ for (const [key, value] of Object.entries(variables)) {
210
+ // Replace without spaces (e.g. {{PROJECT_NAME}})
211
+ result = result.split(`{{${key}}}`).join(value);
212
+ // Replace with spaces (e.g. {{ PROJECT_NAME }})
213
+ result = result.split(`{{ ${key} }}`).join(value);
214
+ }
215
+
216
+ return result;
217
+ }
218
+
219
+ async runNpmInstall(projectPath) {
220
+ return new Promise((resolve, reject) => {
221
+ const npm = process.platform === 'win32' ? 'npm.cmd' : 'npm';
222
+
223
+ // Install root dependencies
224
+ const rootProc = spawn(npm, ['install'], {
225
+ cwd: projectPath,
226
+ stdio: 'inherit',
227
+ shell: true
228
+ });
229
+
230
+ rootProc.on('close', (code) => {
231
+ if (code !== 0) {
232
+ reject(new Error(`npm install failed with code ${code}`));
233
+ return;
234
+ }
235
+
236
+ // Install frontend dependencies
237
+ const frontendPath = join(projectPath, 'frontend');
238
+ if (existsSync(join(frontendPath, 'package.json'))) {
239
+ const frontendProc = spawn(npm, ['install'], {
240
+ cwd: frontendPath,
241
+ stdio: 'inherit',
242
+ shell: true
243
+ });
244
+
245
+ frontendProc.on('close', (feCode) => {
246
+ if (feCode === 0) {
247
+ resolve();
248
+ } else {
249
+ reject(new Error(`frontend npm install failed with code ${feCode}`));
250
+ }
251
+ });
252
+
253
+ frontendProc.on('error', reject);
254
+ } else {
255
+ resolve();
256
+ }
257
+ });
258
+
259
+ rootProc.on('error', reject);
260
+ });
261
+ }
262
+
263
+ async runPlusuiDev(projectPath) {
264
+ return new Promise((resolve, reject) => {
265
+ const plusuiCmd = process.platform === 'win32' ? 'plusui.cmd' : 'plusui';
266
+
267
+ const devProc = spawn(plusuiCmd, ['dev'], {
268
+ cwd: projectPath,
269
+ stdio: 'inherit',
270
+ shell: true,
271
+ });
272
+
273
+ devProc.on('close', (code, signal) => {
274
+ // Normal shutdown via Ctrl+C should not be treated as an error.
275
+ if (signal === 'SIGINT' || code === 0 || code === null) {
276
+ resolve();
277
+ return;
278
+ }
279
+ reject(new Error(`plusui dev exited with code ${code}`));
280
+ });
281
+
282
+ devProc.on('error', reject);
283
+ });
284
+ }
285
+
286
+ }
287
+
288
+