slicejs-cli 3.4.0 → 3.5.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/AGENTS.md +247 -0
- package/client.js +63 -64
- package/commands/Print.js +11 -15
- package/commands/Validations.js +12 -23
- package/commands/buildProduction/buildProduction.js +23 -26
- package/commands/bundle/bundle.js +10 -11
- package/commands/createComponent/createComponent.js +14 -16
- package/commands/deleteComponent/deleteComponent.js +6 -6
- package/commands/doctor/doctor.js +11 -14
- package/commands/getComponent/getComponent.js +99 -162
- package/commands/init/init.js +77 -26
- package/commands/listComponents/listComponents.js +18 -21
- package/commands/startServer/startServer.js +21 -24
- package/commands/startServer/watchServer.js +7 -7
- package/commands/types/types.js +53 -18
- package/commands/utils/PathHelper.js +9 -2
- package/commands/utils/VersionChecker.js +3 -3
- package/commands/utils/bundling/DependencyAnalyzer.js +8 -16
- package/commands/utils/loadConfig.js +31 -0
- package/commands/utils/updateManager.js +3 -4
- package/docs/superpowers/specs/2026-05-10-pwa-generate-design.md +105 -105
- package/package.json +14 -2
- package/post.js +2 -2
- package/tests/bundle-generator.test.js +3 -20
- package/tests/component-registry-parse.test.js +34 -0
- package/tests/fixtures/components.js +8 -0
- package/tests/fixtures/sliceConfig.json +74 -0
- package/tests/getcomponent.test.js +407 -0
- package/tests/helpers/setup.js +97 -0
- package/tests/init-command-contract.test.js +46 -0
- package/tests/local-cli-delegation.test.js +7 -5
- package/tests/path-helper.test.js +206 -0
- package/tests/types-breakage.test.js +491 -0
- package/tests/types-generator-errors.test.js +361 -0
- package/tests/types-generator.test.js +172 -184
package/commands/init/init.js
CHANGED
|
@@ -3,12 +3,10 @@ import path from 'path';
|
|
|
3
3
|
import { fileURLToPath } from 'url';
|
|
4
4
|
import ora from 'ora';
|
|
5
5
|
import Print from '../Print.js';
|
|
6
|
-
import { getProjectRoot, getApiPath, getSrcPath } from '../utils/PathHelper.js';
|
|
6
|
+
import { getProjectRoot, getApiPath, getSrcPath, getPath } from '../utils/PathHelper.js';
|
|
7
7
|
import { execSync } from 'child_process';
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
// Importar la clase ComponentRegistry del getComponent
|
|
9
|
+
// Import ComponentRegistry class from getComponent
|
|
12
10
|
import { ComponentRegistry } from '../getComponent/getComponent.js';
|
|
13
11
|
|
|
14
12
|
// Visual components used by the App Shell + MultiRoute starter project.
|
|
@@ -23,6 +21,15 @@ const STARTER_VISUAL_COMPONENTS = [
|
|
|
23
21
|
'Route'
|
|
24
22
|
];
|
|
25
23
|
|
|
24
|
+
// Service components are now also pulled from the registry on init (instead of
|
|
25
|
+
// being vendored in the framework package), so Visual and Service share a single
|
|
26
|
+
// source of truth. Newcomers add more on demand with `slice get <Name>`.
|
|
27
|
+
const STARTER_SERVICE_COMPONENTS = [
|
|
28
|
+
'FetchManager',
|
|
29
|
+
'IndexedDbManager',
|
|
30
|
+
'LocalStorageManager'
|
|
31
|
+
];
|
|
32
|
+
|
|
26
33
|
export default async function initializeProject(projectType) {
|
|
27
34
|
try {
|
|
28
35
|
const projectRoot = getProjectRoot(import.meta.url);
|
|
@@ -35,7 +42,7 @@ export default async function initializeProject(projectType) {
|
|
|
35
42
|
try {
|
|
36
43
|
const latest = execSync('npm view slicejs-web-framework version', { cwd: projectRoot }).toString().trim();
|
|
37
44
|
latestVersion = latest;
|
|
38
|
-
const installedPkgPath =
|
|
45
|
+
const installedPkgPath = getPath(import.meta.url, 'node_modules', 'slicejs-web-framework', 'package.json');
|
|
39
46
|
let installed = null;
|
|
40
47
|
if (await fs.pathExists(installedPkgPath)) {
|
|
41
48
|
const pkg = await fs.readJson(installedPkgPath);
|
|
@@ -44,9 +51,11 @@ export default async function initializeProject(projectType) {
|
|
|
44
51
|
if (installed !== latest) {
|
|
45
52
|
execSync(`npm install slicejs-web-framework@${latest} --save`, { cwd: projectRoot, stdio: 'inherit' });
|
|
46
53
|
}
|
|
47
|
-
sliceBaseDir =
|
|
54
|
+
sliceBaseDir = getPath(import.meta.url, 'node_modules', 'slicejs-web-framework');
|
|
48
55
|
fwSpinner.succeed(`slicejs-web-framework@${latest} ready`);
|
|
49
56
|
} catch (err) {
|
|
57
|
+
// Fallback uses __dirname-style path because it looks for a local development copy,
|
|
58
|
+
// not a project-relative path — npm install failed, so we fall back to monorepo sibling.
|
|
50
59
|
const fallback = path.join(path.dirname(fileURLToPath(import.meta.url)), '../../../slicejs-web-framework');
|
|
51
60
|
if (await fs.pathExists(fallback)) {
|
|
52
61
|
sliceBaseDir = fallback;
|
|
@@ -58,6 +67,8 @@ export default async function initializeProject(projectType) {
|
|
|
58
67
|
}
|
|
59
68
|
}
|
|
60
69
|
|
|
70
|
+
// These derive from sliceBaseDir (which comes from npm install or fallback),
|
|
71
|
+
// so they're already dynamic — no PathHelper needed.
|
|
61
72
|
const apiDir = path.join(sliceBaseDir, 'api');
|
|
62
73
|
const srcDir = path.join(sliceBaseDir, 'src');
|
|
63
74
|
|
|
@@ -69,7 +80,7 @@ export default async function initializeProject(projectType) {
|
|
|
69
80
|
return;
|
|
70
81
|
}
|
|
71
82
|
|
|
72
|
-
// 1.
|
|
83
|
+
// 1. COPY API FOLDER (keep original logic)
|
|
73
84
|
const apiSpinner = ora('Copying API structure...').start();
|
|
74
85
|
try {
|
|
75
86
|
if (!fs.existsSync(apiDir)) throw new Error(`API folder not found: ${apiDir}`);
|
|
@@ -81,15 +92,15 @@ export default async function initializeProject(projectType) {
|
|
|
81
92
|
return;
|
|
82
93
|
}
|
|
83
94
|
|
|
84
|
-
// 2.
|
|
95
|
+
// 2. CREATE BASIC SRC STRUCTURE (without copying Visual components)
|
|
85
96
|
const srcSpinner = ora('Creating src structure...').start();
|
|
86
97
|
try {
|
|
87
98
|
if (!fs.existsSync(srcDir)) throw new Error(`src folder not found: ${srcDir}`);
|
|
88
99
|
|
|
89
|
-
//
|
|
100
|
+
// Copy only base src files, excluding Components/Visual
|
|
90
101
|
await fs.ensureDir(destinationSrc);
|
|
91
102
|
|
|
92
|
-
//
|
|
103
|
+
// Copy src files and folders except Components/Visual
|
|
93
104
|
const srcItems = await fs.readdir(srcDir);
|
|
94
105
|
|
|
95
106
|
for (const item of srcItems) {
|
|
@@ -99,7 +110,7 @@ export default async function initializeProject(projectType) {
|
|
|
99
110
|
|
|
100
111
|
if (stat.isDirectory()) {
|
|
101
112
|
if (item === 'Components') {
|
|
102
|
-
//
|
|
113
|
+
// Create Components structure but without copying Visual or Service
|
|
103
114
|
await fs.ensureDir(destItemPath);
|
|
104
115
|
|
|
105
116
|
const componentItems = await fs.readdir(srcItemPath);
|
|
@@ -107,11 +118,11 @@ export default async function initializeProject(projectType) {
|
|
|
107
118
|
const componentItemPath = path.join(srcItemPath, componentItem);
|
|
108
119
|
const destComponentItemPath = path.join(destItemPath, componentItem);
|
|
109
120
|
|
|
110
|
-
if (componentItem !== 'Visual') {
|
|
111
|
-
// Copy
|
|
121
|
+
if (componentItem !== 'Visual' && componentItem !== 'Service') {
|
|
122
|
+
// Copy AppComponents and other template types from the framework
|
|
112
123
|
await fs.copy(componentItemPath, destComponentItemPath, { recursive: true });
|
|
113
124
|
} else {
|
|
114
|
-
//
|
|
125
|
+
// Visual and Service are installed from the registry below
|
|
115
126
|
await fs.ensureDir(destComponentItemPath);
|
|
116
127
|
}
|
|
117
128
|
}
|
|
@@ -132,7 +143,7 @@ export default async function initializeProject(projectType) {
|
|
|
132
143
|
return;
|
|
133
144
|
}
|
|
134
145
|
|
|
135
|
-
// 3.
|
|
146
|
+
// 3. DOWNLOAD ALL VISUAL COMPONENTS FROM OFFICIAL REPOSITORY
|
|
136
147
|
const componentsSpinner = ora('Loading component registry...').start();
|
|
137
148
|
try {
|
|
138
149
|
const registry = new ComponentRegistry();
|
|
@@ -148,7 +159,7 @@ export default async function initializeProject(projectType) {
|
|
|
148
159
|
const results = await registry.installMultipleComponents(
|
|
149
160
|
allVisualComponents,
|
|
150
161
|
'Visual',
|
|
151
|
-
true // force = true
|
|
162
|
+
true // force = true for initial installation
|
|
152
163
|
);
|
|
153
164
|
|
|
154
165
|
const successful = results.filter(r => r.success).length;
|
|
@@ -174,11 +185,47 @@ export default async function initializeProject(projectType) {
|
|
|
174
185
|
Print.info('You can add them later using "slice get <component-name>"');
|
|
175
186
|
}
|
|
176
187
|
|
|
177
|
-
//
|
|
188
|
+
// 3b. DOWNLOAD STARTER SERVICE COMPONENTS FROM OFFICIAL REPOSITORY
|
|
189
|
+
const serviceSpinner = ora('Installing starter Service components...').start();
|
|
190
|
+
try {
|
|
191
|
+
const registry = new ComponentRegistry();
|
|
192
|
+
await registry.loadRegistry();
|
|
193
|
+
|
|
194
|
+
if (STARTER_SERVICE_COMPONENTS.length > 0) {
|
|
195
|
+
Print.info(`Installing ${STARTER_SERVICE_COMPONENTS.length} starter Service components: ${STARTER_SERVICE_COMPONENTS.join(', ')}`);
|
|
196
|
+
serviceSpinner.text = `Installing ${STARTER_SERVICE_COMPONENTS.length} starter Service components...`;
|
|
197
|
+
|
|
198
|
+
const results = await registry.installMultipleComponents(
|
|
199
|
+
STARTER_SERVICE_COMPONENTS,
|
|
200
|
+
'Service',
|
|
201
|
+
true // force = true for initial installation
|
|
202
|
+
);
|
|
203
|
+
|
|
204
|
+
const successful = results.filter(r => r.success).length;
|
|
205
|
+
const failed = results.filter(r => !r.success).length;
|
|
206
|
+
|
|
207
|
+
if (successful > 0 && failed === 0) {
|
|
208
|
+
serviceSpinner.succeed(`All ${successful} Service components installed successfully`);
|
|
209
|
+
} else if (successful > 0) {
|
|
210
|
+
serviceSpinner.warn(`${successful} Service components installed, ${failed} failed`);
|
|
211
|
+
Print.info('You can install failed components later using "slice get <component-name>"');
|
|
212
|
+
} else {
|
|
213
|
+
serviceSpinner.fail('Failed to install Service components');
|
|
214
|
+
}
|
|
215
|
+
} else {
|
|
216
|
+
serviceSpinner.succeed('No starter Service components to install');
|
|
217
|
+
}
|
|
218
|
+
} catch (error) {
|
|
219
|
+
serviceSpinner.fail('Could not download Service components from official repository');
|
|
220
|
+
Print.error(`Repository error: ${error.message}`);
|
|
221
|
+
Print.info('You can add them later using "slice get <component-name>"');
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// 4. CONFIGURE SCRIPTS IN PROJECT package.json
|
|
178
225
|
const pkgSpinner = ora('Configuring npm scripts...').start();
|
|
179
226
|
try {
|
|
180
227
|
const projectRoot = getProjectRoot(import.meta.url);
|
|
181
|
-
const pkgPath =
|
|
228
|
+
const pkgPath = getPath(import.meta.url, 'package.json');
|
|
182
229
|
|
|
183
230
|
let pkg;
|
|
184
231
|
if (await fs.pathExists(pkgPath)) {
|
|
@@ -200,7 +247,7 @@ export default async function initializeProject(projectType) {
|
|
|
200
247
|
pkg.scripts['dev'] = 'slice dev';
|
|
201
248
|
pkg.scripts['start'] = 'slice start';
|
|
202
249
|
|
|
203
|
-
//
|
|
250
|
+
// Component management
|
|
204
251
|
pkg.scripts['component:create'] = 'slice component create';
|
|
205
252
|
pkg.scripts['component:list'] = 'slice component list';
|
|
206
253
|
pkg.scripts['component:delete'] = 'slice component delete';
|
|
@@ -215,7 +262,7 @@ export default async function initializeProject(projectType) {
|
|
|
215
262
|
pkg.scripts['slice:update'] = 'slice update';
|
|
216
263
|
pkg.scripts['slice:types'] = 'slice types generate';
|
|
217
264
|
|
|
218
|
-
// Legacy (
|
|
265
|
+
// Legacy (compatibility)
|
|
219
266
|
pkg.scripts['slice:init'] = 'slice init';
|
|
220
267
|
pkg.scripts['slice:start'] = 'slice start';
|
|
221
268
|
pkg.scripts['slice:dev'] = 'slice dev';
|
|
@@ -227,7 +274,7 @@ export default async function initializeProject(projectType) {
|
|
|
227
274
|
pkg.scripts['slice:sync'] = 'slice sync';
|
|
228
275
|
pkg.scripts['run'] = 'slice dev';
|
|
229
276
|
|
|
230
|
-
//
|
|
277
|
+
// Module configuration
|
|
231
278
|
pkg.type = 'module';
|
|
232
279
|
pkg.engines = pkg.engines || { node: '>=20.0.0' };
|
|
233
280
|
|
|
@@ -239,7 +286,7 @@ export default async function initializeProject(projectType) {
|
|
|
239
286
|
await fs.writeFile(pkgPath, JSON.stringify(pkg, null, 2), 'utf8');
|
|
240
287
|
pkgSpinner.succeed('npm scripts configured successfully');
|
|
241
288
|
|
|
242
|
-
|
|
289
|
+
Print.title('New recommended commands:');
|
|
243
290
|
console.log(' npm run dev - Start development server');
|
|
244
291
|
console.log(' npm run get - Install components');
|
|
245
292
|
console.log(' npm run browse - Browse components');
|
|
@@ -248,9 +295,11 @@ export default async function initializeProject(projectType) {
|
|
|
248
295
|
Print.error(error.message);
|
|
249
296
|
}
|
|
250
297
|
|
|
251
|
-
|
|
298
|
+
const projectName = path.basename(process.cwd());
|
|
299
|
+
Print.success(`Project initialized successfully in "${projectName}/"`);
|
|
252
300
|
Print.newLine();
|
|
253
|
-
Print.
|
|
301
|
+
Print.title('Next steps:');
|
|
302
|
+
console.log(` cd ${projectName}`);
|
|
254
303
|
console.log(' slice browse - View available components');
|
|
255
304
|
console.log(' slice get Button - Install specific components');
|
|
256
305
|
console.log(' slice sync - Update all components to latest versions');
|
|
@@ -260,6 +309,8 @@ export default async function initializeProject(projectType) {
|
|
|
260
309
|
}
|
|
261
310
|
}
|
|
262
311
|
|
|
263
|
-
// NOTE: `slice init`
|
|
312
|
+
// NOTE: `slice init` installs only STARTER_VISUAL_COMPONENTS and
|
|
313
|
+
// STARTER_SERVICE_COMPONENTS (see top of file); both Visual and Service are pulled
|
|
314
|
+
// from the registry rather than vendored in the framework package.
|
|
264
315
|
// To install every registry component instead, iterate
|
|
265
|
-
// `Object.keys(registry.getAvailableComponents('Visual'))
|
|
316
|
+
// `Object.keys(registry.getAvailableComponents('Visual'))` (and likewise 'Service').
|
|
@@ -1,16 +1,13 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
|
-
import { fileURLToPath } from 'url';
|
|
4
3
|
import Table from 'cli-table3';
|
|
5
4
|
import chalk from 'chalk';
|
|
6
5
|
import Print from '../Print.js';
|
|
7
6
|
import { getSrcPath, getComponentsJsPath, getConfigPath } from '../utils/PathHelper.js';
|
|
8
7
|
|
|
9
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
10
|
-
|
|
11
8
|
/**
|
|
12
|
-
*
|
|
13
|
-
* @returns {object} -
|
|
9
|
+
* Loads configuration from sliceConfig.json
|
|
10
|
+
* @returns {object} - Configuration object
|
|
14
11
|
*/
|
|
15
12
|
const loadConfig = () => {
|
|
16
13
|
try {
|
|
@@ -30,9 +27,9 @@ const loadConfig = () => {
|
|
|
30
27
|
};
|
|
31
28
|
|
|
32
29
|
/**
|
|
33
|
-
*
|
|
34
|
-
* @param {string} folderPath -
|
|
35
|
-
* @returns {string[]} -
|
|
30
|
+
* Lists files in a given folder, filtering only .js files
|
|
31
|
+
* @param {string} folderPath - Path of the folder to read
|
|
32
|
+
* @returns {string[]} - List of found files
|
|
36
33
|
*/
|
|
37
34
|
const listComponents = (folderPath) => {
|
|
38
35
|
try {
|
|
@@ -48,7 +45,7 @@ const listComponents = (folderPath) => {
|
|
|
48
45
|
};
|
|
49
46
|
|
|
50
47
|
/**
|
|
51
|
-
*
|
|
48
|
+
* Counts files in a component directory
|
|
52
49
|
*/
|
|
53
50
|
const countComponentFiles = (componentPath) => {
|
|
54
51
|
try {
|
|
@@ -61,14 +58,14 @@ const countComponentFiles = (componentPath) => {
|
|
|
61
58
|
};
|
|
62
59
|
|
|
63
60
|
/**
|
|
64
|
-
*
|
|
65
|
-
* @returns {object} -
|
|
61
|
+
* Gets components dynamically from sliceConfig.json
|
|
62
|
+
* @returns {object} - Component mapping with their category
|
|
66
63
|
*/
|
|
67
64
|
const getComponents = () => {
|
|
68
65
|
const config = loadConfig();
|
|
69
66
|
if (!config) return {};
|
|
70
67
|
|
|
71
|
-
const folderSuffix = 'src'; //
|
|
68
|
+
const folderSuffix = 'src'; // Always use 'src' for development
|
|
72
69
|
const componentPaths = config.paths?.components || {};
|
|
73
70
|
let allComponents = new Map();
|
|
74
71
|
|
|
@@ -91,7 +88,7 @@ const getComponents = () => {
|
|
|
91
88
|
|
|
92
89
|
function listComponentsReal() {
|
|
93
90
|
try {
|
|
94
|
-
//
|
|
91
|
+
// Get components dynamically
|
|
95
92
|
const components = getComponents();
|
|
96
93
|
|
|
97
94
|
if (Object.keys(components).length === 0) {
|
|
@@ -100,7 +97,7 @@ function listComponentsReal() {
|
|
|
100
97
|
return;
|
|
101
98
|
}
|
|
102
99
|
|
|
103
|
-
//
|
|
100
|
+
// Create table with cli-table3
|
|
104
101
|
const table = new Table({
|
|
105
102
|
head: [
|
|
106
103
|
chalk.cyan.bold('Component'),
|
|
@@ -114,7 +111,7 @@ function listComponentsReal() {
|
|
|
114
111
|
}
|
|
115
112
|
});
|
|
116
113
|
|
|
117
|
-
//
|
|
114
|
+
// Group by category for better visualization
|
|
118
115
|
const byCategory = {};
|
|
119
116
|
Object.entries(components).forEach(([name, data]) => {
|
|
120
117
|
if (!byCategory[data.category]) {
|
|
@@ -123,18 +120,18 @@ function listComponentsReal() {
|
|
|
123
120
|
byCategory[data.category].push({ name, files: data.files });
|
|
124
121
|
});
|
|
125
122
|
|
|
126
|
-
//
|
|
123
|
+
// Add rows to the table
|
|
127
124
|
Object.entries(byCategory).forEach(([category, comps]) => {
|
|
128
125
|
comps.forEach((comp, index) => {
|
|
129
126
|
if (index === 0) {
|
|
130
|
-
//
|
|
127
|
+
// First row of the category
|
|
131
128
|
table.push([
|
|
132
129
|
chalk.bold(comp.name),
|
|
133
130
|
chalk.yellow(category),
|
|
134
131
|
comp.files.toString()
|
|
135
132
|
]);
|
|
136
133
|
} else {
|
|
137
|
-
//
|
|
134
|
+
// Rest of components in the category
|
|
138
135
|
table.push([
|
|
139
136
|
chalk.bold(comp.name),
|
|
140
137
|
chalk.gray('″'), // Ditto mark
|
|
@@ -151,16 +148,16 @@ function listComponentsReal() {
|
|
|
151
148
|
Print.newLine();
|
|
152
149
|
Print.info(`Total: ${Object.keys(components).length} component${Object.keys(components).length !== 1 ? 's' : ''} found`);
|
|
153
150
|
|
|
154
|
-
//
|
|
151
|
+
// Path where components.js will be generated
|
|
155
152
|
const outputPath = getComponentsJsPath(import.meta.url);
|
|
156
153
|
|
|
157
|
-
//
|
|
154
|
+
// Ensure the directory exists
|
|
158
155
|
const outputDir = path.dirname(outputPath);
|
|
159
156
|
if (!fs.existsSync(outputDir)) {
|
|
160
157
|
fs.mkdirSync(outputDir, { recursive: true });
|
|
161
158
|
}
|
|
162
159
|
|
|
163
|
-
//
|
|
160
|
+
// Generate components.js file with detected components
|
|
164
161
|
const componentsForExport = Object.fromEntries(
|
|
165
162
|
Object.entries(components).map(([name, data]) => [name, data.category])
|
|
166
163
|
);
|
|
@@ -1,19 +1,16 @@
|
|
|
1
|
-
// commands/startServer/startServer.js -
|
|
1
|
+
// commands/startServer/startServer.js - IMPROVED WITH VALIDATION AND FEEDBACK
|
|
2
2
|
|
|
3
3
|
import fs from 'fs-extra';
|
|
4
4
|
import path from 'path';
|
|
5
|
-
import { fileURLToPath } from 'url';
|
|
6
5
|
import { spawn } from 'child_process';
|
|
7
6
|
import { createServer } from 'net';
|
|
8
7
|
import setupWatcher, { stopWatcher } from './watchServer.js';
|
|
9
8
|
import Print from '../Print.js';
|
|
10
|
-
import { getConfigPath, getApiPath, getSrcPath, getDistPath } from '../utils/PathHelper.js';
|
|
9
|
+
import { getConfigPath, getApiPath, getSrcPath, getDistPath, getPath } from '../utils/PathHelper.js';
|
|
11
10
|
import build from '../build/build.js';
|
|
12
11
|
|
|
13
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
14
|
-
|
|
15
12
|
/**
|
|
16
|
-
*
|
|
13
|
+
* Loads configuration from sliceConfig.json
|
|
17
14
|
*/
|
|
18
15
|
const loadConfig = () => {
|
|
19
16
|
try {
|
|
@@ -27,7 +24,7 @@ const loadConfig = () => {
|
|
|
27
24
|
};
|
|
28
25
|
|
|
29
26
|
/**
|
|
30
|
-
*
|
|
27
|
+
* Checks if a port is available
|
|
31
28
|
*/
|
|
32
29
|
async function isPortAvailable(port) {
|
|
33
30
|
return new Promise((resolve) => {
|
|
@@ -51,16 +48,16 @@ async function isPortAvailable(port) {
|
|
|
51
48
|
}
|
|
52
49
|
|
|
53
50
|
/**
|
|
54
|
-
*
|
|
51
|
+
* Checks if a production build exists
|
|
55
52
|
*/
|
|
56
53
|
async function checkProductionBuild() {
|
|
57
54
|
const distDir = getDistPath(import.meta.url);
|
|
58
|
-
const bundleConfigPath =
|
|
55
|
+
const bundleConfigPath = getPath(import.meta.url, 'dist', 'bundles', 'bundle.config.json');
|
|
59
56
|
return (await fs.pathExists(distDir)) && (await fs.pathExists(bundleConfigPath));
|
|
60
57
|
}
|
|
61
58
|
|
|
62
59
|
/**
|
|
63
|
-
*
|
|
60
|
+
* Checks if the development structure exists
|
|
64
61
|
*/
|
|
65
62
|
async function checkDevelopmentStructure() {
|
|
66
63
|
const srcDir = getSrcPath(import.meta.url);
|
|
@@ -70,13 +67,13 @@ async function checkDevelopmentStructure() {
|
|
|
70
67
|
}
|
|
71
68
|
|
|
72
69
|
/**
|
|
73
|
-
*
|
|
70
|
+
* Starts the Node.js server with arguments and improved feedback
|
|
74
71
|
*/
|
|
75
72
|
function startNodeServer(port, mode) {
|
|
76
73
|
return new Promise((resolve, reject) => {
|
|
77
74
|
const apiIndexPath = getApiPath(import.meta.url, 'index.js');
|
|
78
75
|
|
|
79
|
-
//
|
|
76
|
+
// Verify the file exists
|
|
80
77
|
if (!fs.existsSync(apiIndexPath)) {
|
|
81
78
|
reject(new Error(`Server file not found: ${apiIndexPath}`));
|
|
82
79
|
return;
|
|
@@ -84,7 +81,7 @@ function startNodeServer(port, mode) {
|
|
|
84
81
|
|
|
85
82
|
Print.serverStatus('starting', 'Starting server...');
|
|
86
83
|
|
|
87
|
-
//
|
|
84
|
+
// Build arguments based on mode
|
|
88
85
|
const args = [apiIndexPath];
|
|
89
86
|
if (mode === 'production') {
|
|
90
87
|
args.push('--production');
|
|
@@ -109,12 +106,12 @@ function startNodeServer(port, mode) {
|
|
|
109
106
|
let serverStarted = false;
|
|
110
107
|
let outputBuffer = '';
|
|
111
108
|
|
|
112
|
-
//
|
|
109
|
+
// Capture output to detect when the server is ready
|
|
113
110
|
serverProcess.stdout.on('data', (data) => {
|
|
114
111
|
const output = data.toString();
|
|
115
112
|
outputBuffer += output;
|
|
116
113
|
|
|
117
|
-
//
|
|
114
|
+
// Detect common messages indicating the server has started
|
|
118
115
|
if (!serverStarted && (
|
|
119
116
|
output.includes('Server running') ||
|
|
120
117
|
output.includes('listening on') ||
|
|
@@ -125,7 +122,7 @@ function startNodeServer(port, mode) {
|
|
|
125
122
|
Print.serverReady(port);
|
|
126
123
|
}
|
|
127
124
|
|
|
128
|
-
//
|
|
125
|
+
// Display server output
|
|
129
126
|
process.stdout.write(output);
|
|
130
127
|
});
|
|
131
128
|
|
|
@@ -161,7 +158,7 @@ function startNodeServer(port, mode) {
|
|
|
161
158
|
serverProcess.kill('SIGTERM');
|
|
162
159
|
});
|
|
163
160
|
|
|
164
|
-
//
|
|
161
|
+
// If after 3 seconds we haven't detected startup, assume it's ready
|
|
165
162
|
setTimeout(() => {
|
|
166
163
|
if (!serverStarted) {
|
|
167
164
|
serverStarted = true;
|
|
@@ -173,7 +170,7 @@ function startNodeServer(port, mode) {
|
|
|
173
170
|
}
|
|
174
171
|
|
|
175
172
|
/**
|
|
176
|
-
*
|
|
173
|
+
* Main function to start the server
|
|
177
174
|
*/
|
|
178
175
|
export default async function startServer(options = {}) {
|
|
179
176
|
const config = loadConfig();
|
|
@@ -185,7 +182,7 @@ export default async function startServer(options = {}) {
|
|
|
185
182
|
Print.title(`🚀 Starting Slice.js ${mode} server...`);
|
|
186
183
|
Print.newLine();
|
|
187
184
|
|
|
188
|
-
//
|
|
185
|
+
// Verify project structure
|
|
189
186
|
if (!await checkDevelopmentStructure()) {
|
|
190
187
|
throw new Error('Project structure not found. Run "slice init" first.');
|
|
191
188
|
}
|
|
@@ -204,7 +201,7 @@ export default async function startServer(options = {}) {
|
|
|
204
201
|
Print.newLine();
|
|
205
202
|
|
|
206
203
|
if (mode === 'production') {
|
|
207
|
-
//
|
|
204
|
+
// Verify production build exists
|
|
208
205
|
if (!await checkProductionBuild()) {
|
|
209
206
|
Print.info('No production build found. Running "slice build"...');
|
|
210
207
|
const success = await build({});
|
|
@@ -219,10 +216,10 @@ export default async function startServer(options = {}) {
|
|
|
219
216
|
|
|
220
217
|
Print.newLine();
|
|
221
218
|
|
|
222
|
-
//
|
|
219
|
+
// Start the server with arguments
|
|
223
220
|
let serverProcess = await startNodeServer(actualPort, mode);
|
|
224
221
|
|
|
225
|
-
//
|
|
222
|
+
// Configure watch mode if enabled
|
|
226
223
|
if (watch) {
|
|
227
224
|
Print.newLine();
|
|
228
225
|
const watcher = setupWatcher(serverProcess, async (changedPath) => {
|
|
@@ -253,12 +250,12 @@ export default async function startServer(options = {}) {
|
|
|
253
250
|
|
|
254
251
|
} catch (error) {
|
|
255
252
|
Print.newLine();
|
|
256
|
-
Print.error(error.message);
|
|
253
|
+
Print.error(`Failed to start development server: ${error.message}`);
|
|
257
254
|
throw error;
|
|
258
255
|
}
|
|
259
256
|
}
|
|
260
257
|
|
|
261
258
|
/**
|
|
262
|
-
*
|
|
259
|
+
* Exported utility functions
|
|
263
260
|
*/
|
|
264
261
|
export { checkProductionBuild, checkDevelopmentStructure, isPortAvailable };
|
|
@@ -3,9 +3,9 @@ import chalk from 'chalk';
|
|
|
3
3
|
import Print from '../Print.js';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
7
|
-
* @param {ChildProcess} serverProcess -
|
|
8
|
-
* @returns {FSWatcher} -
|
|
6
|
+
* Configures the watcher for project files
|
|
7
|
+
* @param {ChildProcess} serverProcess - Server process
|
|
8
|
+
* @returns {FSWatcher} - Chokidar watcher
|
|
9
9
|
*/
|
|
10
10
|
export default function setupWatcher(serverProcess, onRestart) {
|
|
11
11
|
Print.info('Watch mode enabled - monitoring file changes...');
|
|
@@ -13,7 +13,7 @@ export default function setupWatcher(serverProcess, onRestart) {
|
|
|
13
13
|
|
|
14
14
|
const watcher = chokidar.watch(['src/**/*', 'api/**/*'], {
|
|
15
15
|
ignored: [
|
|
16
|
-
/(^|[\/\\])\../, //
|
|
16
|
+
/(^|[\/\\])\../, // hidden files
|
|
17
17
|
'**/node_modules/**',
|
|
18
18
|
'**/dist/**',
|
|
19
19
|
'**/bundles/**',
|
|
@@ -31,7 +31,7 @@ export default function setupWatcher(serverProcess, onRestart) {
|
|
|
31
31
|
|
|
32
32
|
watcher
|
|
33
33
|
.on('change', (path) => {
|
|
34
|
-
// Debounce
|
|
34
|
+
// Debounce to avoid multiple reloads
|
|
35
35
|
clearTimeout(reloadTimeout);
|
|
36
36
|
reloadTimeout = setTimeout(() => {
|
|
37
37
|
if(onRestart) {
|
|
@@ -68,8 +68,8 @@ export default function setupWatcher(serverProcess, onRestart) {
|
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
/**
|
|
71
|
-
*
|
|
72
|
-
* @param {FSWatcher} watcher - Watcher
|
|
71
|
+
* Stops the watcher safely
|
|
72
|
+
* @param {FSWatcher} watcher - Watcher to stop
|
|
73
73
|
*/
|
|
74
74
|
export function stopWatcher(watcher) {
|
|
75
75
|
if (watcher) {
|