slicejs-cli 3.6.3 → 3.6.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/LICENSE +21 -21
- package/README.md +226 -226
- package/client.js +744 -744
- package/commands/Print.js +163 -163
- package/commands/Validations.js +92 -92
- package/commands/build/build.js +40 -40
- package/commands/buildProduction/buildProduction.js +577 -579
- package/commands/bundle/bundle.js +234 -234
- package/commands/createComponent/VisualComponentTemplate.js +55 -55
- package/commands/createComponent/createComponent.js +128 -128
- package/commands/deleteComponent/deleteComponent.js +81 -81
- package/commands/doctor/doctor.js +440 -440
- package/commands/getComponent/getComponent.js +701 -701
- package/commands/init/init.js +467 -467
- package/commands/listComponents/listComponents.js +172 -172
- package/commands/startServer/startServer.js +261 -261
- package/commands/startServer/watchServer.js +66 -66
- package/commands/types/types.js +580 -580
- package/commands/utils/LocalCliDelegation.js +53 -53
- package/commands/utils/PackageManager.js +148 -148
- package/commands/utils/PathHelper.js +75 -75
- package/commands/utils/VersionChecker.js +169 -169
- package/commands/utils/bundling/BundleGenerator.js +2525 -2525
- package/commands/utils/bundling/DependencyAnalyzer.js +925 -925
- package/commands/utils/loadConfig.js +31 -31
- package/commands/utils/sliceScripts.js +23 -23
- package/commands/utils/updateManager.js +471 -471
- package/package.json +73 -73
- package/post.js +60 -60
package/commands/init/init.js
CHANGED
|
@@ -1,467 +1,467 @@
|
|
|
1
|
-
import fs from 'fs-extra';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
import { fileURLToPath } from 'url';
|
|
4
|
-
import ora from 'ora';
|
|
5
|
-
import Print from '../Print.js';
|
|
6
|
-
import { getProjectRoot, getApiPath, getSrcPath, getPath } from '../utils/PathHelper.js';
|
|
7
|
-
import { execSync } from 'child_process';
|
|
8
|
-
import {
|
|
9
|
-
resolvePackageManager,
|
|
10
|
-
getPackageManagerVersion,
|
|
11
|
-
installCommand
|
|
12
|
-
} from '../utils/PackageManager.js';
|
|
13
|
-
import { SLICE_SCRIPTS } from '../utils/sliceScripts.js';
|
|
14
|
-
|
|
15
|
-
// Import ComponentRegistry class from getComponent
|
|
16
|
-
import { ComponentRegistry } from '../getComponent/getComponent.js';
|
|
17
|
-
|
|
18
|
-
// Fetch the latest published version straight from the npm registry. This is
|
|
19
|
-
// informational only (we never pin installs to it): it avoids depending on
|
|
20
|
-
// `npm view` (absent on pnpm-only machines) and plays nice with pnpm's
|
|
21
|
-
// minimumReleaseAge quarantine, which may legitimately resolve an older version.
|
|
22
|
-
async function fetchLatestVersion(packageName) {
|
|
23
|
-
try {
|
|
24
|
-
const response = await fetch(`https://registry.npmjs.org/${packageName}/latest`, {
|
|
25
|
-
headers: { 'Accept': 'application/json' }
|
|
26
|
-
});
|
|
27
|
-
if (!response.ok) return null;
|
|
28
|
-
const data = await response.json();
|
|
29
|
-
return data.version || null;
|
|
30
|
-
} catch {
|
|
31
|
-
return null;
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function getRunningCliVersion() {
|
|
36
|
-
try {
|
|
37
|
-
const cliRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..');
|
|
38
|
-
const cliPkg = fs.readJsonSync(path.join(cliRoot, 'package.json'));
|
|
39
|
-
return typeof cliPkg.version === 'string' ? cliPkg.version : null;
|
|
40
|
-
} catch {
|
|
41
|
-
return null;
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
async function ensurePnpmAllowBuilds(projectRoot) {
|
|
46
|
-
const workspacePath = path.join(projectRoot, 'pnpm-workspace.yaml');
|
|
47
|
-
const allowBuildLine = ' slicejs-cli: true';
|
|
48
|
-
|
|
49
|
-
if (!(await fs.pathExists(workspacePath))) {
|
|
50
|
-
await fs.writeFile(workspacePath, `allowBuilds:\n${allowBuildLine}\n`, 'utf8');
|
|
51
|
-
return;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
const raw = await fs.readFile(workspacePath, 'utf8');
|
|
55
|
-
const lines = raw.split(/\r?\n/);
|
|
56
|
-
const allowIdx = lines.findIndex((line) => /^allowBuilds:\s*$/.test(line));
|
|
57
|
-
|
|
58
|
-
if (allowIdx === -1) {
|
|
59
|
-
const suffix = raw.endsWith('\n') ? '' : '\n';
|
|
60
|
-
await fs.writeFile(workspacePath, `${raw}${suffix}allowBuilds:\n${allowBuildLine}\n`, 'utf8');
|
|
61
|
-
return;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
let blockEnd = lines.length;
|
|
65
|
-
for (let i = allowIdx + 1; i < lines.length; i++) {
|
|
66
|
-
const line = lines[i];
|
|
67
|
-
if (!line.trim()) continue;
|
|
68
|
-
if (!/^\s/.test(line)) {
|
|
69
|
-
blockEnd = i;
|
|
70
|
-
break;
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
let found = false;
|
|
75
|
-
for (let i = allowIdx + 1; i < blockEnd; i++) {
|
|
76
|
-
if (/^\s+slicejs-cli\s*:/.test(lines[i])) {
|
|
77
|
-
lines[i] = allowBuildLine;
|
|
78
|
-
found = true;
|
|
79
|
-
break;
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
if (!found) {
|
|
84
|
-
lines.splice(blockEnd, 0, allowBuildLine);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
await fs.writeFile(workspacePath, `${lines.join('\n').replace(/\n*$/, '\n')}`, 'utf8');
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// Create the project manifest BEFORE any install runs. Without a package.json in
|
|
91
|
-
// the project folder, npm/pnpm walk up the directory tree looking for the nearest
|
|
92
|
-
// manifest and anchor node_modules (and the dependency entry) OUTSIDE the project.
|
|
93
|
-
// Exported for tests (init-project-isolation.test.js).
|
|
94
|
-
export async function ensureProjectManifest(projectRoot, packageManager) {
|
|
95
|
-
const pkgPath = path.join(projectRoot, 'package.json');
|
|
96
|
-
if (await fs.pathExists(pkgPath)) return pkgPath;
|
|
97
|
-
|
|
98
|
-
const pkg = {
|
|
99
|
-
name: path.basename(projectRoot),
|
|
100
|
-
version: '1.0.0',
|
|
101
|
-
description: 'Slice.js project',
|
|
102
|
-
main: 'api/index.js',
|
|
103
|
-
type: 'module',
|
|
104
|
-
engines: { node: '>=20.0.0' },
|
|
105
|
-
scripts: {}
|
|
106
|
-
};
|
|
107
|
-
|
|
108
|
-
// Persist the chosen package manager (corepack convention) so every later
|
|
109
|
-
// command — slice update, slice doctor — detects it deterministically.
|
|
110
|
-
const pmVersion = getPackageManagerVersion(packageManager);
|
|
111
|
-
if (pmVersion) {
|
|
112
|
-
pkg.packageManager = `${packageManager}@${pmVersion}`;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
await fs.writeFile(pkgPath, JSON.stringify(pkg, null, 2), 'utf8');
|
|
116
|
-
return pkgPath;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
// Visual components used by the App Shell + MultiRoute starter project.
|
|
120
|
-
// We install only these on init; newcomers add more on demand with `slice get <Name>`.
|
|
121
|
-
const STARTER_VISUAL_COMPONENTS = [
|
|
122
|
-
'Button',
|
|
123
|
-
'Link',
|
|
124
|
-
'Loading',
|
|
125
|
-
'MultiRoute',
|
|
126
|
-
'Navbar',
|
|
127
|
-
'NotFound',
|
|
128
|
-
'Route'
|
|
129
|
-
];
|
|
130
|
-
|
|
131
|
-
// Service components are now also pulled from the registry on init (instead of
|
|
132
|
-
// being vendored in the framework package), so Visual and Service share a single
|
|
133
|
-
// source of truth. Newcomers add more on demand with `slice get <Name>`.
|
|
134
|
-
const STARTER_SERVICE_COMPONENTS = [
|
|
135
|
-
'FetchManager',
|
|
136
|
-
'IndexedDbManager',
|
|
137
|
-
'LocalStorageManager'
|
|
138
|
-
];
|
|
139
|
-
|
|
140
|
-
export default async function initializeProject(options = {}) {
|
|
141
|
-
try {
|
|
142
|
-
const projectRoot = getProjectRoot(import.meta.url);
|
|
143
|
-
const destinationApi = getApiPath(import.meta.url);
|
|
144
|
-
const destinationSrc = getSrcPath(import.meta.url);
|
|
145
|
-
|
|
146
|
-
// Resolve the package manager chosen in `slice init` (or detect it when
|
|
147
|
-
// initializeProject is invoked directly, e.g. inside an existing folder).
|
|
148
|
-
const packageManager = options.packageManager
|
|
149
|
-
|| resolvePackageManager(projectRoot).name;
|
|
150
|
-
|
|
151
|
-
if (packageManager === 'pnpm') {
|
|
152
|
-
await ensurePnpmAllowBuilds(projectRoot);
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
// 0. CREATE PROJECT MANIFEST FIRST — must exist before any install so the
|
|
156
|
-
// package manager anchors node_modules inside the project folder.
|
|
157
|
-
await ensureProjectManifest(projectRoot, packageManager);
|
|
158
|
-
|
|
159
|
-
const fwSpinner = ora('Ensuring latest Slice framework...').start();
|
|
160
|
-
let latestVersion = null;
|
|
161
|
-
let installedVersion = null;
|
|
162
|
-
let sliceBaseDir;
|
|
163
|
-
try {
|
|
164
|
-
latestVersion = await fetchLatestVersion('slicejs-web-framework');
|
|
165
|
-
const frameworkPackage = latestVersion
|
|
166
|
-
? `slicejs-web-framework@${latestVersion}`
|
|
167
|
-
: 'slicejs-web-framework';
|
|
168
|
-
const installedPkgPath = getPath(import.meta.url, 'node_modules', 'slicejs-web-framework', 'package.json');
|
|
169
|
-
let installed = null;
|
|
170
|
-
if (await fs.pathExists(installedPkgPath)) {
|
|
171
|
-
const pkg = await fs.readJson(installedPkgPath);
|
|
172
|
-
installed = pkg.version;
|
|
173
|
-
}
|
|
174
|
-
if (!installed || (latestVersion && installed !== latestVersion)) {
|
|
175
|
-
execSync(installCommand(packageManager, frameworkPackage), { cwd: projectRoot, stdio: 'inherit' });
|
|
176
|
-
}
|
|
177
|
-
if (await fs.pathExists(installedPkgPath)) {
|
|
178
|
-
const pkg = await fs.readJson(installedPkgPath);
|
|
179
|
-
installedVersion = pkg.version;
|
|
180
|
-
}
|
|
181
|
-
sliceBaseDir = getPath(import.meta.url, 'node_modules', 'slicejs-web-framework');
|
|
182
|
-
fwSpinner.succeed(`slicejs-web-framework@${installedVersion || 'unknown'} ready`);
|
|
183
|
-
if (latestVersion && installedVersion && installedVersion !== latestVersion) {
|
|
184
|
-
Print.info(`Latest published is ${latestVersion}; your package manager resolved ${installedVersion} (release-age policy or cached registry).`);
|
|
185
|
-
}
|
|
186
|
-
} catch (err) {
|
|
187
|
-
// Fallback uses __dirname-style path because it looks for a local development copy,
|
|
188
|
-
// not a project-relative path — the install failed, so we fall back to monorepo sibling.
|
|
189
|
-
const fallback = path.join(path.dirname(fileURLToPath(import.meta.url)), '../../../slicejs-web-framework');
|
|
190
|
-
if (await fs.pathExists(fallback)) {
|
|
191
|
-
sliceBaseDir = fallback;
|
|
192
|
-
fwSpinner.warn('Using local slicejs-web-framework fallback');
|
|
193
|
-
} else {
|
|
194
|
-
fwSpinner.fail('Failed to ensure latest slicejs-web-framework');
|
|
195
|
-
Print.error(err.message);
|
|
196
|
-
return;
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
// 0b. INSTALL THE CLI LOCALLY (devDependency) so the generated scripts
|
|
201
|
-
// (`npm run dev` → `slice dev`) resolve via local delegation to a version
|
|
202
|
-
// pinned per project, as the docs recommend.
|
|
203
|
-
const cliSpinner = ora('Installing slicejs-cli as devDependency...').start();
|
|
204
|
-
try {
|
|
205
|
-
const cliPkgPath = getPath(import.meta.url, 'node_modules', 'slicejs-cli', 'package.json');
|
|
206
|
-
const currentCliVersion = getRunningCliVersion();
|
|
207
|
-
const cliPackage = currentCliVersion
|
|
208
|
-
? `slicejs-cli@${currentCliVersion}`
|
|
209
|
-
: 'slicejs-cli';
|
|
210
|
-
|
|
211
|
-
let installedCliVersion = null;
|
|
212
|
-
if (await fs.pathExists(cliPkgPath)) {
|
|
213
|
-
const pkg = await fs.readJson(cliPkgPath);
|
|
214
|
-
installedCliVersion = pkg.version;
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
if (!installedCliVersion || (currentCliVersion && installedCliVersion !== currentCliVersion)) {
|
|
218
|
-
execSync(installCommand(packageManager, cliPackage, { dev: true }), { cwd: projectRoot, stdio: 'inherit' });
|
|
219
|
-
}
|
|
220
|
-
cliSpinner.succeed('slicejs-cli installed locally');
|
|
221
|
-
} catch (err) {
|
|
222
|
-
cliSpinner.warn('Could not install slicejs-cli locally — scripts will use the global CLI');
|
|
223
|
-
Print.info(`You can add it later with: ${installCommand(packageManager, `slicejs-cli@${getRunningCliVersion() || 'latest'}`, { dev: true })}`);
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
// These derive from sliceBaseDir (which comes from npm install or fallback),
|
|
227
|
-
// so they're already dynamic — no PathHelper needed.
|
|
228
|
-
const apiDir = path.join(sliceBaseDir, 'api');
|
|
229
|
-
const srcDir = path.join(sliceBaseDir, 'src');
|
|
230
|
-
|
|
231
|
-
try {
|
|
232
|
-
if (fs.existsSync(destinationApi)) throw new Error(`The "api" directory already exists: ${destinationApi}`);
|
|
233
|
-
if (fs.existsSync(destinationSrc)) throw new Error(`The "src" directory already exists: ${destinationSrc}`);
|
|
234
|
-
} catch (error) {
|
|
235
|
-
Print.error('Validating destination directories:', error.message);
|
|
236
|
-
return;
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
// 1. COPY API FOLDER (keep original logic)
|
|
240
|
-
const apiSpinner = ora('Copying API structure...').start();
|
|
241
|
-
try {
|
|
242
|
-
if (!fs.existsSync(apiDir)) throw new Error(`API folder not found: ${apiDir}`);
|
|
243
|
-
await fs.copy(apiDir, destinationApi, { recursive: true });
|
|
244
|
-
apiSpinner.succeed('API structure created successfully');
|
|
245
|
-
} catch (error) {
|
|
246
|
-
apiSpinner.fail('Error copying API structure');
|
|
247
|
-
Print.error(error.message);
|
|
248
|
-
return;
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
// 2. CREATE BASIC SRC STRUCTURE (without copying Visual components)
|
|
252
|
-
const srcSpinner = ora('Creating src structure...').start();
|
|
253
|
-
try {
|
|
254
|
-
if (!fs.existsSync(srcDir)) throw new Error(`src folder not found: ${srcDir}`);
|
|
255
|
-
|
|
256
|
-
// Copy only base src files, excluding Components/Visual
|
|
257
|
-
await fs.ensureDir(destinationSrc);
|
|
258
|
-
|
|
259
|
-
// Copy src files and folders except Components/Visual
|
|
260
|
-
const srcItems = await fs.readdir(srcDir);
|
|
261
|
-
|
|
262
|
-
for (const item of srcItems) {
|
|
263
|
-
const srcItemPath = path.join(srcDir, item);
|
|
264
|
-
const destItemPath = path.join(destinationSrc, item);
|
|
265
|
-
const stat = await fs.stat(srcItemPath);
|
|
266
|
-
|
|
267
|
-
if (stat.isDirectory()) {
|
|
268
|
-
if (item === 'Components') {
|
|
269
|
-
// Create Components structure but without copying Visual or Service
|
|
270
|
-
await fs.ensureDir(destItemPath);
|
|
271
|
-
|
|
272
|
-
const componentItems = await fs.readdir(srcItemPath);
|
|
273
|
-
for (const componentItem of componentItems) {
|
|
274
|
-
const componentItemPath = path.join(srcItemPath, componentItem);
|
|
275
|
-
const destComponentItemPath = path.join(destItemPath, componentItem);
|
|
276
|
-
|
|
277
|
-
if (componentItem !== 'Visual' && componentItem !== 'Service') {
|
|
278
|
-
// Copy AppComponents and other template types from the framework
|
|
279
|
-
await fs.copy(componentItemPath, destComponentItemPath, { recursive: true });
|
|
280
|
-
} else {
|
|
281
|
-
// Visual and Service are installed from the registry below
|
|
282
|
-
await fs.ensureDir(destComponentItemPath);
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
} else {
|
|
286
|
-
// Copy other folders normally
|
|
287
|
-
await fs.copy(srcItemPath, destItemPath, { recursive: true });
|
|
288
|
-
}
|
|
289
|
-
} else {
|
|
290
|
-
// Copy files normally
|
|
291
|
-
await fs.copy(srcItemPath, destItemPath);
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
srcSpinner.succeed('Source structure created successfully');
|
|
296
|
-
} catch (error) {
|
|
297
|
-
srcSpinner.fail('Error creating source structure');
|
|
298
|
-
Print.error(error.message);
|
|
299
|
-
return;
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
// 3. DOWNLOAD ALL VISUAL COMPONENTS FROM OFFICIAL REPOSITORY
|
|
303
|
-
let visualResults = [];
|
|
304
|
-
const componentsSpinner = ora('Loading component registry...').start();
|
|
305
|
-
try {
|
|
306
|
-
const registry = new ComponentRegistry();
|
|
307
|
-
await registry.loadRegistry();
|
|
308
|
-
|
|
309
|
-
// Install only the Visual components the starter project uses.
|
|
310
|
-
const allVisualComponents = STARTER_VISUAL_COMPONENTS;
|
|
311
|
-
|
|
312
|
-
if (allVisualComponents.length > 0) {
|
|
313
|
-
componentsSpinner.text = `Installing ${allVisualComponents.length} starter Visual components...`;
|
|
314
|
-
|
|
315
|
-
visualResults = await registry.installMultipleComponents(
|
|
316
|
-
allVisualComponents,
|
|
317
|
-
'Visual',
|
|
318
|
-
true // force = true for initial installation
|
|
319
|
-
);
|
|
320
|
-
|
|
321
|
-
const successful = visualResults.filter(r => r.success).length;
|
|
322
|
-
const failed = visualResults.filter(r => !r.success).length;
|
|
323
|
-
|
|
324
|
-
if (successful > 0 && failed === 0) {
|
|
325
|
-
componentsSpinner.succeed(`All ${successful} Visual components installed successfully`);
|
|
326
|
-
} else if (successful > 0) {
|
|
327
|
-
componentsSpinner.warn(`${successful} components installed, ${failed} failed`);
|
|
328
|
-
} else {
|
|
329
|
-
componentsSpinner.fail('Failed to install Visual components');
|
|
330
|
-
}
|
|
331
|
-
} else {
|
|
332
|
-
componentsSpinner.warn('No Visual components found in registry');
|
|
333
|
-
Print.info(`You can add components later using "${packageManager} run get -- <component-name>"`);
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
} catch (error) {
|
|
337
|
-
componentsSpinner.fail('Could not download Visual components from official repository');
|
|
338
|
-
Print.error(`Repository error: ${error.message}`);
|
|
339
|
-
Print.info('Project initialized without Visual components');
|
|
340
|
-
Print.info(`You can add them later using "${packageManager} run get -- <component-name>"`);
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
// 3b. DOWNLOAD STARTER SERVICE COMPONENTS FROM OFFICIAL REPOSITORY
|
|
344
|
-
let serviceResults = [];
|
|
345
|
-
const serviceSpinner = ora('Installing starter Service components...').start();
|
|
346
|
-
try {
|
|
347
|
-
const registry = new ComponentRegistry();
|
|
348
|
-
await registry.loadRegistry();
|
|
349
|
-
|
|
350
|
-
if (STARTER_SERVICE_COMPONENTS.length > 0) {
|
|
351
|
-
serviceSpinner.text = `Installing ${STARTER_SERVICE_COMPONENTS.length} starter Service components...`;
|
|
352
|
-
|
|
353
|
-
serviceResults = await registry.installMultipleComponents(
|
|
354
|
-
STARTER_SERVICE_COMPONENTS,
|
|
355
|
-
'Service',
|
|
356
|
-
true // force = true for initial installation
|
|
357
|
-
);
|
|
358
|
-
|
|
359
|
-
const successful = serviceResults.filter(r => r.success).length;
|
|
360
|
-
const failed = serviceResults.filter(r => !r.success).length;
|
|
361
|
-
|
|
362
|
-
if (successful > 0 && failed === 0) {
|
|
363
|
-
serviceSpinner.succeed(`All ${successful} Service components installed successfully`);
|
|
364
|
-
} else if (successful > 0) {
|
|
365
|
-
serviceSpinner.warn(`${successful} Service components installed, ${failed} failed`);
|
|
366
|
-
} else {
|
|
367
|
-
serviceSpinner.fail('Failed to install Service components');
|
|
368
|
-
}
|
|
369
|
-
} else {
|
|
370
|
-
serviceSpinner.succeed('No starter Service components to install');
|
|
371
|
-
}
|
|
372
|
-
} catch (error) {
|
|
373
|
-
serviceSpinner.fail('Could not download Service components from official repository');
|
|
374
|
-
Print.error(`Repository error: ${error.message}`);
|
|
375
|
-
Print.info(`You can add them later using "${packageManager} run get -- <component-name>"`);
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
// 4. CONFIGURE SCRIPTS IN PROJECT package.json
|
|
379
|
-
const pkgSpinner = ora('Configuring npm scripts...').start();
|
|
380
|
-
try {
|
|
381
|
-
const projectRoot = getProjectRoot(import.meta.url);
|
|
382
|
-
const pkgPath = getPath(import.meta.url, 'package.json');
|
|
383
|
-
|
|
384
|
-
let pkg;
|
|
385
|
-
if (await fs.pathExists(pkgPath)) {
|
|
386
|
-
pkg = await fs.readJson(pkgPath);
|
|
387
|
-
} else {
|
|
388
|
-
pkg = {
|
|
389
|
-
name: path.basename(projectRoot),
|
|
390
|
-
version: '1.0.0',
|
|
391
|
-
description: 'Slice.js project',
|
|
392
|
-
main: 'api/index.js',
|
|
393
|
-
scripts: {}
|
|
394
|
-
};
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
pkg.scripts = pkg.scripts || {};
|
|
398
|
-
pkg.dependencies = pkg.dependencies || {};
|
|
399
|
-
|
|
400
|
-
// Main scripts (local CLI path, no global launcher dependency)
|
|
401
|
-
pkg.scripts['dev'] = SLICE_SCRIPTS['slice:dev'];
|
|
402
|
-
pkg.scripts['build'] = SLICE_SCRIPTS['slice:build'];
|
|
403
|
-
pkg.scripts['start'] = SLICE_SCRIPTS['slice:start'];
|
|
404
|
-
|
|
405
|
-
// Component management
|
|
406
|
-
pkg.scripts['component:create'] = SLICE_SCRIPTS['slice:create'];
|
|
407
|
-
pkg.scripts['component:list'] = SLICE_SCRIPTS['slice:list'];
|
|
408
|
-
pkg.scripts['component:delete'] = SLICE_SCRIPTS['slice:delete'];
|
|
409
|
-
|
|
410
|
-
// Registry shortcuts
|
|
411
|
-
pkg.scripts['get'] = SLICE_SCRIPTS['slice:get'];
|
|
412
|
-
pkg.scripts['browse'] = SLICE_SCRIPTS['slice:browse'];
|
|
413
|
-
pkg.scripts['sync'] = SLICE_SCRIPTS['slice:sync'];
|
|
414
|
-
|
|
415
|
-
// slice:* namespaced set — shared with post.js and `slice postinstall`
|
|
416
|
-
// (commands/utils/sliceScripts.js) so the three never drift apart.
|
|
417
|
-
Object.assign(pkg.scripts, SLICE_SCRIPTS);
|
|
418
|
-
pkg.scripts['run'] = SLICE_SCRIPTS['slice:dev'];
|
|
419
|
-
|
|
420
|
-
// Module configuration
|
|
421
|
-
pkg.type = 'module';
|
|
422
|
-
pkg.engines = pkg.engines || { node: '>=20.0.0' };
|
|
423
|
-
|
|
424
|
-
// Ensure framework dependency is present (the install above normally
|
|
425
|
-
// already wrote it; this is a fallback for the monorepo-sibling path).
|
|
426
|
-
if (!pkg.dependencies['slicejs-web-framework']) {
|
|
427
|
-
pkg.dependencies['slicejs-web-framework'] = installedVersion ? `^${installedVersion}` : 'latest';
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
await fs.writeFile(pkgPath, JSON.stringify(pkg, null, 2), 'utf8');
|
|
431
|
-
pkgSpinner.succeed('Package scripts configured successfully');
|
|
432
|
-
|
|
433
|
-
Print.title('New recommended commands:');
|
|
434
|
-
console.log(` ${packageManager} run dev - Start development server`);
|
|
435
|
-
console.log(` ${packageManager} run get - Install components`);
|
|
436
|
-
console.log(` ${packageManager} run browse - Browse components`);
|
|
437
|
-
} catch (error) {
|
|
438
|
-
pkgSpinner.fail('Failed to configure npm scripts');
|
|
439
|
-
Print.error(error.message);
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
const projectName = path.basename(process.cwd());
|
|
443
|
-
|
|
444
|
-
const failedCount = [...visualResults, ...serviceResults].filter(r => !r.success).length;
|
|
445
|
-
if (failedCount > 0) {
|
|
446
|
-
Print.warning(`${failedCount} component(s) failed to install — run "${packageManager} run get -- <name>" to retry`);
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
Print.success(`Project initialized successfully in "${projectName}/"`);
|
|
450
|
-
Print.newLine();
|
|
451
|
-
Print.title('Next steps:');
|
|
452
|
-
console.log(` cd ${projectName}`);
|
|
453
|
-
console.log(` ${packageManager} run dev - Start development server`);
|
|
454
|
-
console.log(` ${packageManager} run browse - View available components`);
|
|
455
|
-
console.log(` ${packageManager} run get -- Button - Install specific components`);
|
|
456
|
-
console.log(` ${packageManager} run sync - Update all components to latest versions`);
|
|
457
|
-
|
|
458
|
-
} catch (error) {
|
|
459
|
-
Print.error('Unexpected error initializing project:', error.message);
|
|
460
|
-
}
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
// NOTE: `slice init` installs only STARTER_VISUAL_COMPONENTS and
|
|
464
|
-
// STARTER_SERVICE_COMPONENTS (see top of file); both Visual and Service are pulled
|
|
465
|
-
// from the registry rather than vendored in the framework package.
|
|
466
|
-
// To install every registry component instead, iterate
|
|
467
|
-
// `Object.keys(registry.getAvailableComponents('Visual'))` (and likewise 'Service').
|
|
1
|
+
import fs from 'fs-extra';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
import ora from 'ora';
|
|
5
|
+
import Print from '../Print.js';
|
|
6
|
+
import { getProjectRoot, getApiPath, getSrcPath, getPath } from '../utils/PathHelper.js';
|
|
7
|
+
import { execSync } from 'child_process';
|
|
8
|
+
import {
|
|
9
|
+
resolvePackageManager,
|
|
10
|
+
getPackageManagerVersion,
|
|
11
|
+
installCommand
|
|
12
|
+
} from '../utils/PackageManager.js';
|
|
13
|
+
import { SLICE_SCRIPTS } from '../utils/sliceScripts.js';
|
|
14
|
+
|
|
15
|
+
// Import ComponentRegistry class from getComponent
|
|
16
|
+
import { ComponentRegistry } from '../getComponent/getComponent.js';
|
|
17
|
+
|
|
18
|
+
// Fetch the latest published version straight from the npm registry. This is
|
|
19
|
+
// informational only (we never pin installs to it): it avoids depending on
|
|
20
|
+
// `npm view` (absent on pnpm-only machines) and plays nice with pnpm's
|
|
21
|
+
// minimumReleaseAge quarantine, which may legitimately resolve an older version.
|
|
22
|
+
async function fetchLatestVersion(packageName) {
|
|
23
|
+
try {
|
|
24
|
+
const response = await fetch(`https://registry.npmjs.org/${packageName}/latest`, {
|
|
25
|
+
headers: { 'Accept': 'application/json' }
|
|
26
|
+
});
|
|
27
|
+
if (!response.ok) return null;
|
|
28
|
+
const data = await response.json();
|
|
29
|
+
return data.version || null;
|
|
30
|
+
} catch {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function getRunningCliVersion() {
|
|
36
|
+
try {
|
|
37
|
+
const cliRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..');
|
|
38
|
+
const cliPkg = fs.readJsonSync(path.join(cliRoot, 'package.json'));
|
|
39
|
+
return typeof cliPkg.version === 'string' ? cliPkg.version : null;
|
|
40
|
+
} catch {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function ensurePnpmAllowBuilds(projectRoot) {
|
|
46
|
+
const workspacePath = path.join(projectRoot, 'pnpm-workspace.yaml');
|
|
47
|
+
const allowBuildLine = ' slicejs-cli: true';
|
|
48
|
+
|
|
49
|
+
if (!(await fs.pathExists(workspacePath))) {
|
|
50
|
+
await fs.writeFile(workspacePath, `allowBuilds:\n${allowBuildLine}\n`, 'utf8');
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const raw = await fs.readFile(workspacePath, 'utf8');
|
|
55
|
+
const lines = raw.split(/\r?\n/);
|
|
56
|
+
const allowIdx = lines.findIndex((line) => /^allowBuilds:\s*$/.test(line));
|
|
57
|
+
|
|
58
|
+
if (allowIdx === -1) {
|
|
59
|
+
const suffix = raw.endsWith('\n') ? '' : '\n';
|
|
60
|
+
await fs.writeFile(workspacePath, `${raw}${suffix}allowBuilds:\n${allowBuildLine}\n`, 'utf8');
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
let blockEnd = lines.length;
|
|
65
|
+
for (let i = allowIdx + 1; i < lines.length; i++) {
|
|
66
|
+
const line = lines[i];
|
|
67
|
+
if (!line.trim()) continue;
|
|
68
|
+
if (!/^\s/.test(line)) {
|
|
69
|
+
blockEnd = i;
|
|
70
|
+
break;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
let found = false;
|
|
75
|
+
for (let i = allowIdx + 1; i < blockEnd; i++) {
|
|
76
|
+
if (/^\s+slicejs-cli\s*:/.test(lines[i])) {
|
|
77
|
+
lines[i] = allowBuildLine;
|
|
78
|
+
found = true;
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (!found) {
|
|
84
|
+
lines.splice(blockEnd, 0, allowBuildLine);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
await fs.writeFile(workspacePath, `${lines.join('\n').replace(/\n*$/, '\n')}`, 'utf8');
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Create the project manifest BEFORE any install runs. Without a package.json in
|
|
91
|
+
// the project folder, npm/pnpm walk up the directory tree looking for the nearest
|
|
92
|
+
// manifest and anchor node_modules (and the dependency entry) OUTSIDE the project.
|
|
93
|
+
// Exported for tests (init-project-isolation.test.js).
|
|
94
|
+
export async function ensureProjectManifest(projectRoot, packageManager) {
|
|
95
|
+
const pkgPath = path.join(projectRoot, 'package.json');
|
|
96
|
+
if (await fs.pathExists(pkgPath)) return pkgPath;
|
|
97
|
+
|
|
98
|
+
const pkg = {
|
|
99
|
+
name: path.basename(projectRoot),
|
|
100
|
+
version: '1.0.0',
|
|
101
|
+
description: 'Slice.js project',
|
|
102
|
+
main: 'api/index.js',
|
|
103
|
+
type: 'module',
|
|
104
|
+
engines: { node: '>=20.0.0' },
|
|
105
|
+
scripts: {}
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
// Persist the chosen package manager (corepack convention) so every later
|
|
109
|
+
// command — slice update, slice doctor — detects it deterministically.
|
|
110
|
+
const pmVersion = getPackageManagerVersion(packageManager);
|
|
111
|
+
if (pmVersion) {
|
|
112
|
+
pkg.packageManager = `${packageManager}@${pmVersion}`;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
await fs.writeFile(pkgPath, JSON.stringify(pkg, null, 2), 'utf8');
|
|
116
|
+
return pkgPath;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Visual components used by the App Shell + MultiRoute starter project.
|
|
120
|
+
// We install only these on init; newcomers add more on demand with `slice get <Name>`.
|
|
121
|
+
const STARTER_VISUAL_COMPONENTS = [
|
|
122
|
+
'Button',
|
|
123
|
+
'Link',
|
|
124
|
+
'Loading',
|
|
125
|
+
'MultiRoute',
|
|
126
|
+
'Navbar',
|
|
127
|
+
'NotFound',
|
|
128
|
+
'Route'
|
|
129
|
+
];
|
|
130
|
+
|
|
131
|
+
// Service components are now also pulled from the registry on init (instead of
|
|
132
|
+
// being vendored in the framework package), so Visual and Service share a single
|
|
133
|
+
// source of truth. Newcomers add more on demand with `slice get <Name>`.
|
|
134
|
+
const STARTER_SERVICE_COMPONENTS = [
|
|
135
|
+
'FetchManager',
|
|
136
|
+
'IndexedDbManager',
|
|
137
|
+
'LocalStorageManager'
|
|
138
|
+
];
|
|
139
|
+
|
|
140
|
+
export default async function initializeProject(options = {}) {
|
|
141
|
+
try {
|
|
142
|
+
const projectRoot = getProjectRoot(import.meta.url);
|
|
143
|
+
const destinationApi = getApiPath(import.meta.url);
|
|
144
|
+
const destinationSrc = getSrcPath(import.meta.url);
|
|
145
|
+
|
|
146
|
+
// Resolve the package manager chosen in `slice init` (or detect it when
|
|
147
|
+
// initializeProject is invoked directly, e.g. inside an existing folder).
|
|
148
|
+
const packageManager = options.packageManager
|
|
149
|
+
|| resolvePackageManager(projectRoot).name;
|
|
150
|
+
|
|
151
|
+
if (packageManager === 'pnpm') {
|
|
152
|
+
await ensurePnpmAllowBuilds(projectRoot);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// 0. CREATE PROJECT MANIFEST FIRST — must exist before any install so the
|
|
156
|
+
// package manager anchors node_modules inside the project folder.
|
|
157
|
+
await ensureProjectManifest(projectRoot, packageManager);
|
|
158
|
+
|
|
159
|
+
const fwSpinner = ora('Ensuring latest Slice framework...').start();
|
|
160
|
+
let latestVersion = null;
|
|
161
|
+
let installedVersion = null;
|
|
162
|
+
let sliceBaseDir;
|
|
163
|
+
try {
|
|
164
|
+
latestVersion = await fetchLatestVersion('slicejs-web-framework');
|
|
165
|
+
const frameworkPackage = latestVersion
|
|
166
|
+
? `slicejs-web-framework@${latestVersion}`
|
|
167
|
+
: 'slicejs-web-framework';
|
|
168
|
+
const installedPkgPath = getPath(import.meta.url, 'node_modules', 'slicejs-web-framework', 'package.json');
|
|
169
|
+
let installed = null;
|
|
170
|
+
if (await fs.pathExists(installedPkgPath)) {
|
|
171
|
+
const pkg = await fs.readJson(installedPkgPath);
|
|
172
|
+
installed = pkg.version;
|
|
173
|
+
}
|
|
174
|
+
if (!installed || (latestVersion && installed !== latestVersion)) {
|
|
175
|
+
execSync(installCommand(packageManager, frameworkPackage), { cwd: projectRoot, stdio: 'inherit' });
|
|
176
|
+
}
|
|
177
|
+
if (await fs.pathExists(installedPkgPath)) {
|
|
178
|
+
const pkg = await fs.readJson(installedPkgPath);
|
|
179
|
+
installedVersion = pkg.version;
|
|
180
|
+
}
|
|
181
|
+
sliceBaseDir = getPath(import.meta.url, 'node_modules', 'slicejs-web-framework');
|
|
182
|
+
fwSpinner.succeed(`slicejs-web-framework@${installedVersion || 'unknown'} ready`);
|
|
183
|
+
if (latestVersion && installedVersion && installedVersion !== latestVersion) {
|
|
184
|
+
Print.info(`Latest published is ${latestVersion}; your package manager resolved ${installedVersion} (release-age policy or cached registry).`);
|
|
185
|
+
}
|
|
186
|
+
} catch (err) {
|
|
187
|
+
// Fallback uses __dirname-style path because it looks for a local development copy,
|
|
188
|
+
// not a project-relative path — the install failed, so we fall back to monorepo sibling.
|
|
189
|
+
const fallback = path.join(path.dirname(fileURLToPath(import.meta.url)), '../../../slicejs-web-framework');
|
|
190
|
+
if (await fs.pathExists(fallback)) {
|
|
191
|
+
sliceBaseDir = fallback;
|
|
192
|
+
fwSpinner.warn('Using local slicejs-web-framework fallback');
|
|
193
|
+
} else {
|
|
194
|
+
fwSpinner.fail('Failed to ensure latest slicejs-web-framework');
|
|
195
|
+
Print.error(err.message);
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// 0b. INSTALL THE CLI LOCALLY (devDependency) so the generated scripts
|
|
201
|
+
// (`npm run dev` → `slice dev`) resolve via local delegation to a version
|
|
202
|
+
// pinned per project, as the docs recommend.
|
|
203
|
+
const cliSpinner = ora('Installing slicejs-cli as devDependency...').start();
|
|
204
|
+
try {
|
|
205
|
+
const cliPkgPath = getPath(import.meta.url, 'node_modules', 'slicejs-cli', 'package.json');
|
|
206
|
+
const currentCliVersion = getRunningCliVersion();
|
|
207
|
+
const cliPackage = currentCliVersion
|
|
208
|
+
? `slicejs-cli@${currentCliVersion}`
|
|
209
|
+
: 'slicejs-cli';
|
|
210
|
+
|
|
211
|
+
let installedCliVersion = null;
|
|
212
|
+
if (await fs.pathExists(cliPkgPath)) {
|
|
213
|
+
const pkg = await fs.readJson(cliPkgPath);
|
|
214
|
+
installedCliVersion = pkg.version;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (!installedCliVersion || (currentCliVersion && installedCliVersion !== currentCliVersion)) {
|
|
218
|
+
execSync(installCommand(packageManager, cliPackage, { dev: true }), { cwd: projectRoot, stdio: 'inherit' });
|
|
219
|
+
}
|
|
220
|
+
cliSpinner.succeed('slicejs-cli installed locally');
|
|
221
|
+
} catch (err) {
|
|
222
|
+
cliSpinner.warn('Could not install slicejs-cli locally — scripts will use the global CLI');
|
|
223
|
+
Print.info(`You can add it later with: ${installCommand(packageManager, `slicejs-cli@${getRunningCliVersion() || 'latest'}`, { dev: true })}`);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// These derive from sliceBaseDir (which comes from npm install or fallback),
|
|
227
|
+
// so they're already dynamic — no PathHelper needed.
|
|
228
|
+
const apiDir = path.join(sliceBaseDir, 'api');
|
|
229
|
+
const srcDir = path.join(sliceBaseDir, 'src');
|
|
230
|
+
|
|
231
|
+
try {
|
|
232
|
+
if (fs.existsSync(destinationApi)) throw new Error(`The "api" directory already exists: ${destinationApi}`);
|
|
233
|
+
if (fs.existsSync(destinationSrc)) throw new Error(`The "src" directory already exists: ${destinationSrc}`);
|
|
234
|
+
} catch (error) {
|
|
235
|
+
Print.error('Validating destination directories:', error.message);
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// 1. COPY API FOLDER (keep original logic)
|
|
240
|
+
const apiSpinner = ora('Copying API structure...').start();
|
|
241
|
+
try {
|
|
242
|
+
if (!fs.existsSync(apiDir)) throw new Error(`API folder not found: ${apiDir}`);
|
|
243
|
+
await fs.copy(apiDir, destinationApi, { recursive: true });
|
|
244
|
+
apiSpinner.succeed('API structure created successfully');
|
|
245
|
+
} catch (error) {
|
|
246
|
+
apiSpinner.fail('Error copying API structure');
|
|
247
|
+
Print.error(error.message);
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// 2. CREATE BASIC SRC STRUCTURE (without copying Visual components)
|
|
252
|
+
const srcSpinner = ora('Creating src structure...').start();
|
|
253
|
+
try {
|
|
254
|
+
if (!fs.existsSync(srcDir)) throw new Error(`src folder not found: ${srcDir}`);
|
|
255
|
+
|
|
256
|
+
// Copy only base src files, excluding Components/Visual
|
|
257
|
+
await fs.ensureDir(destinationSrc);
|
|
258
|
+
|
|
259
|
+
// Copy src files and folders except Components/Visual
|
|
260
|
+
const srcItems = await fs.readdir(srcDir);
|
|
261
|
+
|
|
262
|
+
for (const item of srcItems) {
|
|
263
|
+
const srcItemPath = path.join(srcDir, item);
|
|
264
|
+
const destItemPath = path.join(destinationSrc, item);
|
|
265
|
+
const stat = await fs.stat(srcItemPath);
|
|
266
|
+
|
|
267
|
+
if (stat.isDirectory()) {
|
|
268
|
+
if (item === 'Components') {
|
|
269
|
+
// Create Components structure but without copying Visual or Service
|
|
270
|
+
await fs.ensureDir(destItemPath);
|
|
271
|
+
|
|
272
|
+
const componentItems = await fs.readdir(srcItemPath);
|
|
273
|
+
for (const componentItem of componentItems) {
|
|
274
|
+
const componentItemPath = path.join(srcItemPath, componentItem);
|
|
275
|
+
const destComponentItemPath = path.join(destItemPath, componentItem);
|
|
276
|
+
|
|
277
|
+
if (componentItem !== 'Visual' && componentItem !== 'Service') {
|
|
278
|
+
// Copy AppComponents and other template types from the framework
|
|
279
|
+
await fs.copy(componentItemPath, destComponentItemPath, { recursive: true });
|
|
280
|
+
} else {
|
|
281
|
+
// Visual and Service are installed from the registry below
|
|
282
|
+
await fs.ensureDir(destComponentItemPath);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
} else {
|
|
286
|
+
// Copy other folders normally
|
|
287
|
+
await fs.copy(srcItemPath, destItemPath, { recursive: true });
|
|
288
|
+
}
|
|
289
|
+
} else {
|
|
290
|
+
// Copy files normally
|
|
291
|
+
await fs.copy(srcItemPath, destItemPath);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
srcSpinner.succeed('Source structure created successfully');
|
|
296
|
+
} catch (error) {
|
|
297
|
+
srcSpinner.fail('Error creating source structure');
|
|
298
|
+
Print.error(error.message);
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// 3. DOWNLOAD ALL VISUAL COMPONENTS FROM OFFICIAL REPOSITORY
|
|
303
|
+
let visualResults = [];
|
|
304
|
+
const componentsSpinner = ora('Loading component registry...').start();
|
|
305
|
+
try {
|
|
306
|
+
const registry = new ComponentRegistry();
|
|
307
|
+
await registry.loadRegistry();
|
|
308
|
+
|
|
309
|
+
// Install only the Visual components the starter project uses.
|
|
310
|
+
const allVisualComponents = STARTER_VISUAL_COMPONENTS;
|
|
311
|
+
|
|
312
|
+
if (allVisualComponents.length > 0) {
|
|
313
|
+
componentsSpinner.text = `Installing ${allVisualComponents.length} starter Visual components...`;
|
|
314
|
+
|
|
315
|
+
visualResults = await registry.installMultipleComponents(
|
|
316
|
+
allVisualComponents,
|
|
317
|
+
'Visual',
|
|
318
|
+
true // force = true for initial installation
|
|
319
|
+
);
|
|
320
|
+
|
|
321
|
+
const successful = visualResults.filter(r => r.success).length;
|
|
322
|
+
const failed = visualResults.filter(r => !r.success).length;
|
|
323
|
+
|
|
324
|
+
if (successful > 0 && failed === 0) {
|
|
325
|
+
componentsSpinner.succeed(`All ${successful} Visual components installed successfully`);
|
|
326
|
+
} else if (successful > 0) {
|
|
327
|
+
componentsSpinner.warn(`${successful} components installed, ${failed} failed`);
|
|
328
|
+
} else {
|
|
329
|
+
componentsSpinner.fail('Failed to install Visual components');
|
|
330
|
+
}
|
|
331
|
+
} else {
|
|
332
|
+
componentsSpinner.warn('No Visual components found in registry');
|
|
333
|
+
Print.info(`You can add components later using "${packageManager} run get -- <component-name>"`);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
} catch (error) {
|
|
337
|
+
componentsSpinner.fail('Could not download Visual components from official repository');
|
|
338
|
+
Print.error(`Repository error: ${error.message}`);
|
|
339
|
+
Print.info('Project initialized without Visual components');
|
|
340
|
+
Print.info(`You can add them later using "${packageManager} run get -- <component-name>"`);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// 3b. DOWNLOAD STARTER SERVICE COMPONENTS FROM OFFICIAL REPOSITORY
|
|
344
|
+
let serviceResults = [];
|
|
345
|
+
const serviceSpinner = ora('Installing starter Service components...').start();
|
|
346
|
+
try {
|
|
347
|
+
const registry = new ComponentRegistry();
|
|
348
|
+
await registry.loadRegistry();
|
|
349
|
+
|
|
350
|
+
if (STARTER_SERVICE_COMPONENTS.length > 0) {
|
|
351
|
+
serviceSpinner.text = `Installing ${STARTER_SERVICE_COMPONENTS.length} starter Service components...`;
|
|
352
|
+
|
|
353
|
+
serviceResults = await registry.installMultipleComponents(
|
|
354
|
+
STARTER_SERVICE_COMPONENTS,
|
|
355
|
+
'Service',
|
|
356
|
+
true // force = true for initial installation
|
|
357
|
+
);
|
|
358
|
+
|
|
359
|
+
const successful = serviceResults.filter(r => r.success).length;
|
|
360
|
+
const failed = serviceResults.filter(r => !r.success).length;
|
|
361
|
+
|
|
362
|
+
if (successful > 0 && failed === 0) {
|
|
363
|
+
serviceSpinner.succeed(`All ${successful} Service components installed successfully`);
|
|
364
|
+
} else if (successful > 0) {
|
|
365
|
+
serviceSpinner.warn(`${successful} Service components installed, ${failed} failed`);
|
|
366
|
+
} else {
|
|
367
|
+
serviceSpinner.fail('Failed to install Service components');
|
|
368
|
+
}
|
|
369
|
+
} else {
|
|
370
|
+
serviceSpinner.succeed('No starter Service components to install');
|
|
371
|
+
}
|
|
372
|
+
} catch (error) {
|
|
373
|
+
serviceSpinner.fail('Could not download Service components from official repository');
|
|
374
|
+
Print.error(`Repository error: ${error.message}`);
|
|
375
|
+
Print.info(`You can add them later using "${packageManager} run get -- <component-name>"`);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// 4. CONFIGURE SCRIPTS IN PROJECT package.json
|
|
379
|
+
const pkgSpinner = ora('Configuring npm scripts...').start();
|
|
380
|
+
try {
|
|
381
|
+
const projectRoot = getProjectRoot(import.meta.url);
|
|
382
|
+
const pkgPath = getPath(import.meta.url, 'package.json');
|
|
383
|
+
|
|
384
|
+
let pkg;
|
|
385
|
+
if (await fs.pathExists(pkgPath)) {
|
|
386
|
+
pkg = await fs.readJson(pkgPath);
|
|
387
|
+
} else {
|
|
388
|
+
pkg = {
|
|
389
|
+
name: path.basename(projectRoot),
|
|
390
|
+
version: '1.0.0',
|
|
391
|
+
description: 'Slice.js project',
|
|
392
|
+
main: 'api/index.js',
|
|
393
|
+
scripts: {}
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
pkg.scripts = pkg.scripts || {};
|
|
398
|
+
pkg.dependencies = pkg.dependencies || {};
|
|
399
|
+
|
|
400
|
+
// Main scripts (local CLI path, no global launcher dependency)
|
|
401
|
+
pkg.scripts['dev'] = SLICE_SCRIPTS['slice:dev'];
|
|
402
|
+
pkg.scripts['build'] = SLICE_SCRIPTS['slice:build'];
|
|
403
|
+
pkg.scripts['start'] = SLICE_SCRIPTS['slice:start'];
|
|
404
|
+
|
|
405
|
+
// Component management
|
|
406
|
+
pkg.scripts['component:create'] = SLICE_SCRIPTS['slice:create'];
|
|
407
|
+
pkg.scripts['component:list'] = SLICE_SCRIPTS['slice:list'];
|
|
408
|
+
pkg.scripts['component:delete'] = SLICE_SCRIPTS['slice:delete'];
|
|
409
|
+
|
|
410
|
+
// Registry shortcuts
|
|
411
|
+
pkg.scripts['get'] = SLICE_SCRIPTS['slice:get'];
|
|
412
|
+
pkg.scripts['browse'] = SLICE_SCRIPTS['slice:browse'];
|
|
413
|
+
pkg.scripts['sync'] = SLICE_SCRIPTS['slice:sync'];
|
|
414
|
+
|
|
415
|
+
// slice:* namespaced set — shared with post.js and `slice postinstall`
|
|
416
|
+
// (commands/utils/sliceScripts.js) so the three never drift apart.
|
|
417
|
+
Object.assign(pkg.scripts, SLICE_SCRIPTS);
|
|
418
|
+
pkg.scripts['run'] = SLICE_SCRIPTS['slice:dev'];
|
|
419
|
+
|
|
420
|
+
// Module configuration
|
|
421
|
+
pkg.type = 'module';
|
|
422
|
+
pkg.engines = pkg.engines || { node: '>=20.0.0' };
|
|
423
|
+
|
|
424
|
+
// Ensure framework dependency is present (the install above normally
|
|
425
|
+
// already wrote it; this is a fallback for the monorepo-sibling path).
|
|
426
|
+
if (!pkg.dependencies['slicejs-web-framework']) {
|
|
427
|
+
pkg.dependencies['slicejs-web-framework'] = installedVersion ? `^${installedVersion}` : 'latest';
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
await fs.writeFile(pkgPath, JSON.stringify(pkg, null, 2), 'utf8');
|
|
431
|
+
pkgSpinner.succeed('Package scripts configured successfully');
|
|
432
|
+
|
|
433
|
+
Print.title('New recommended commands:');
|
|
434
|
+
console.log(` ${packageManager} run dev - Start development server`);
|
|
435
|
+
console.log(` ${packageManager} run get - Install components`);
|
|
436
|
+
console.log(` ${packageManager} run browse - Browse components`);
|
|
437
|
+
} catch (error) {
|
|
438
|
+
pkgSpinner.fail('Failed to configure npm scripts');
|
|
439
|
+
Print.error(error.message);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
const projectName = path.basename(process.cwd());
|
|
443
|
+
|
|
444
|
+
const failedCount = [...visualResults, ...serviceResults].filter(r => !r.success).length;
|
|
445
|
+
if (failedCount > 0) {
|
|
446
|
+
Print.warning(`${failedCount} component(s) failed to install — run "${packageManager} run get -- <name>" to retry`);
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
Print.success(`Project initialized successfully in "${projectName}/"`);
|
|
450
|
+
Print.newLine();
|
|
451
|
+
Print.title('Next steps:');
|
|
452
|
+
console.log(` cd ${projectName}`);
|
|
453
|
+
console.log(` ${packageManager} run dev - Start development server`);
|
|
454
|
+
console.log(` ${packageManager} run browse - View available components`);
|
|
455
|
+
console.log(` ${packageManager} run get -- Button - Install specific components`);
|
|
456
|
+
console.log(` ${packageManager} run sync - Update all components to latest versions`);
|
|
457
|
+
|
|
458
|
+
} catch (error) {
|
|
459
|
+
Print.error('Unexpected error initializing project:', error.message);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
// NOTE: `slice init` installs only STARTER_VISUAL_COMPONENTS and
|
|
464
|
+
// STARTER_SERVICE_COMPONENTS (see top of file); both Visual and Service are pulled
|
|
465
|
+
// from the registry rather than vendored in the framework package.
|
|
466
|
+
// To install every registry component instead, iterate
|
|
467
|
+
// `Object.keys(registry.getAvailableComponents('Visual'))` (and likewise 'Service').
|