create-portfoliable 1.0.3
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/bin/create-portfoliable.mjs +425 -0
- package/cli/portfoliable.mjs +836 -0
- package/index.html +18 -0
- package/package.json +102 -0
- package/public/favicon.ico +0 -0
- package/public/favicon.png +0 -0
- package/scripts/check-updates.mjs +31 -0
- package/scripts/ensure-valence-index-css.mjs +245 -0
- package/scripts/generate-password-hash.mjs +25 -0
- package/scripts/release-orchestrator.mjs +363 -0
- package/scripts/scaffold-case.mjs +460 -0
- package/scripts/scaffold-consumer.mjs +87 -0
- package/scripts/smoke-homeview.mjs +133 -0
- package/scripts/smoke-initializer.mjs +129 -0
- package/scripts/smoke-packed.mjs +184 -0
- package/scripts/switch-valence-source.mjs +162 -0
- package/scripts/sync-template-shared.mjs +136 -0
- package/scripts/terminal-ui.mjs +274 -0
- package/scripts/verify-integration.mjs +72 -0
- package/src/App.js +10015 -0
- package/src/header-visibility.contract.js +31 -0
- package/src/header-visibility.contract.test.js +41 -0
- package/src/main.js +12 -0
- package/src/parser/markdown.js +855 -0
- package/src/style.css +88 -0
- package/templates/README.md +242 -0
- package/templates/configs/i18n/i18n.config.js +230 -0
- package/templates/configs/i18n/i18n.labels.js +188 -0
- package/templates/configs/portfoliable.a11y.config.js +262 -0
- package/templates/configs/portfoliable.design.config.js +651 -0
- package/templates/gitignore +9 -0
- package/templates/index.html +20 -0
- package/templates/public/api/.htaccess +4 -0
- package/templates/public/api/README.md +27 -0
- package/templates/public/api/password.config.example.json +16 -0
- package/templates/public/api/password.config.json +16 -0
- package/templates/public/api/unlock-case.php +56 -0
- package/templates/public/favicon.ico +0 -0
- package/templates/public/favicon.png +0 -0
- package/templates/public/portfoliable-logo.svg +3 -0
- package/templates/scripts/add-language.mjs +253 -0
- package/templates/scripts/convert-video-to-mp4.mjs +207 -0
- package/templates/scripts/convert-wav-to-mp3.mjs +184 -0
- package/templates/scripts/delete-case.mjs +320 -0
- package/templates/scripts/delete-language.mjs +195 -0
- package/templates/scripts/generate-password-hash.mjs +98 -0
- package/templates/scripts/i18n-config-utils.mjs +123 -0
- package/templates/scripts/scaffold-case.mjs +444 -0
- package/templates/scripts/sync-locales.mjs +1048 -0
- package/templates/scripts/validate-content.mjs +257 -0
- package/templates/scripts/validate-protection.mjs +187 -0
- package/templates/src/cases/index.js +188 -0
- package/templates/src/content/about/ABOUTME.md +202 -0
- package/templates/src/content/cases/template-case/case.md +479 -0
- package/templates/src/content/cases/template-case/cover.png +0 -0
- package/templates/src/content/cases/template-case/he-cover.png +0 -0
- package/templates/src/content/cases/template-case/template-audio.mp3 +0 -0
- package/templates/src/content/cases/template-case/template-video.mp4 +0 -0
- package/templates/src/content/thumbnail-options.generated.json +1044 -0
- package/templates/src/main.js +23 -0
- package/templates/src/parser/markdown.js +805 -0
- package/vite.config.js +111 -0
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// File: create-portfoliable/bin/create-portfoliable.mjs
|
|
3
|
+
// Purpose: Create a new Portfoliable consumer app from starter templates.
|
|
4
|
+
// Author: Lio Schimanko
|
|
5
|
+
|
|
6
|
+
// MARK: IMPORTS
|
|
7
|
+
import fs from 'node:fs';
|
|
8
|
+
import path from 'node:path';
|
|
9
|
+
import { spawnSync } from 'node:child_process';
|
|
10
|
+
import { fileURLToPath } from 'node:url';
|
|
11
|
+
import {
|
|
12
|
+
ui,
|
|
13
|
+
printRailSegment,
|
|
14
|
+
isInteractiveTerminal,
|
|
15
|
+
promptFancyDotsPreference,
|
|
16
|
+
readProjectUiPreferences,
|
|
17
|
+
writeProjectUiPreferences
|
|
18
|
+
} from '../scripts/terminal-ui.mjs';
|
|
19
|
+
|
|
20
|
+
// MARK: DEFAULTS
|
|
21
|
+
// Defines default target folder when no project name is provided.
|
|
22
|
+
const DEFAULT_TARGET = 'my-portfolio';
|
|
23
|
+
|
|
24
|
+
// MARK: CLI DISPLAY HELPERS
|
|
25
|
+
// Wraps a message with ANSI color code and reset sequence.
|
|
26
|
+
function color(code, message) {
|
|
27
|
+
return `\x1b[${code}m${message}\x1b[0m`;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Resolves runtime dependency version for generated app package.json.
|
|
31
|
+
function resolveRuntimeDependencyVersion(currentFilePath) {
|
|
32
|
+
// Allows explicit overrides for local integration and smoke tests.
|
|
33
|
+
const override = process.env.PORTFOLIABLE_RUNTIME_DEP;
|
|
34
|
+
if (override) {
|
|
35
|
+
return override;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
// Resolves published package manifest adjacent to this bin entrypoint.
|
|
40
|
+
const packageJsonPath = path.resolve(path.dirname(currentFilePath), '..', 'package.json');
|
|
41
|
+
// Reads package version to keep scaffolded dependency aligned with published artifact.
|
|
42
|
+
const packageVersion = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'))?.version;
|
|
43
|
+
if (typeof packageVersion === 'string' && /^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/.test(packageVersion)) {
|
|
44
|
+
return `^${packageVersion}`;
|
|
45
|
+
}
|
|
46
|
+
} catch {
|
|
47
|
+
// Falls back below with warning to avoid hardcoded stale versions.
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
console.warn(
|
|
51
|
+
color(
|
|
52
|
+
'33',
|
|
53
|
+
'Warning: could not resolve package version for create-portfoliable default dependency. Falling back to latest.'
|
|
54
|
+
)
|
|
55
|
+
);
|
|
56
|
+
return 'latest';
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// MARK: ARGUMENT PARSING
|
|
60
|
+
// Parses initializer arguments and returns normalized options.
|
|
61
|
+
function parseArgs(argv) {
|
|
62
|
+
// Extracts runtime args excluding node executable and script path.
|
|
63
|
+
const args = argv.slice(2);
|
|
64
|
+
// Initializes parser defaults.
|
|
65
|
+
const options = {
|
|
66
|
+
target: DEFAULT_TARGET,
|
|
67
|
+
force: false,
|
|
68
|
+
install: true,
|
|
69
|
+
launch: true
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
// Iterates all user-provided CLI flags and positional values.
|
|
73
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
74
|
+
// Reads the current token under evaluation.
|
|
75
|
+
const arg = args[i];
|
|
76
|
+
|
|
77
|
+
if (arg === '--force') {
|
|
78
|
+
options.force = true;
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (arg === '--no-install') {
|
|
83
|
+
options.install = false;
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (arg === '--no-preview') {
|
|
88
|
+
options.launch = false;
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (arg === '--no-dev') {
|
|
93
|
+
options.launch = false;
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (arg === '--help' || arg === '-h') {
|
|
98
|
+
options.help = true;
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (arg === '--no-interactive') {
|
|
103
|
+
options.interactive = false;
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (arg === '--interactive') {
|
|
108
|
+
options.interactive = true;
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (!arg.startsWith('-') && options.target === DEFAULT_TARGET) {
|
|
113
|
+
options.target = arg;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return options;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Prints help/usage text for initializer flags.
|
|
121
|
+
function printHelp() {
|
|
122
|
+
console.log('Usage: npm create portfoliable@latest [project-name] [-- --force] [-- --no-install] [-- --no-preview] [-- --no-dev] [-- --no-interactive]');
|
|
123
|
+
console.log(' npm create portfoliable [project-name] [-- --force] [-- --no-install] [-- --no-preview] [-- --no-dev] [-- --no-interactive]');
|
|
124
|
+
console.log('');
|
|
125
|
+
console.log('Options:');
|
|
126
|
+
console.log(' --force Create in a non-empty directory');
|
|
127
|
+
console.log(' --no-install Skip npm install');
|
|
128
|
+
console.log(' --no-preview Skip auto-starting live preview (compat alias for --no-dev)');
|
|
129
|
+
console.log(' --no-dev Skip auto-starting live preview');
|
|
130
|
+
console.log(' --no-interactive Disable first-run interactive prompts');
|
|
131
|
+
console.log(' --interactive Force first-run interactive prompts when TTY is available');
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// MARK: NAME AND PATH HELPERS
|
|
135
|
+
// Converts a freeform project name into npm-safe package name format.
|
|
136
|
+
function sanitizePackageName(input) {
|
|
137
|
+
return input
|
|
138
|
+
.trim()
|
|
139
|
+
.toLowerCase()
|
|
140
|
+
.replace(/\s+/g, '-')
|
|
141
|
+
.replace(/[^a-z0-9-_.]/g, '-')
|
|
142
|
+
.replace(/^-+|-+$/g, '') || 'my-portfolio';
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Returns true when a directory does not exist or contains no meaningful files.
|
|
146
|
+
function isDirectoryEmpty(dirPath) {
|
|
147
|
+
if (!fs.existsSync(dirPath)) return true;
|
|
148
|
+
// Ignores macOS metadata file when checking emptiness.
|
|
149
|
+
const files = fs.readdirSync(dirPath).filter((entry) => entry !== '.DS_Store');
|
|
150
|
+
return files.length === 0;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Copies one template file, applying optional text transforms before writing.
|
|
154
|
+
function writeFileFromTemplate(templateRoot, relativePath, targetRoot, transforms = []) {
|
|
155
|
+
// Resolves template source file path.
|
|
156
|
+
const sourcePath = path.join(templateRoot, relativePath);
|
|
157
|
+
// Resolves destination file path in target project.
|
|
158
|
+
const destinationPath = path.join(targetRoot, relativePath);
|
|
159
|
+
fs.mkdirSync(path.dirname(destinationPath), { recursive: true });
|
|
160
|
+
|
|
161
|
+
// Reads source file content for transform pipeline.
|
|
162
|
+
let content = fs.readFileSync(sourcePath, 'utf8');
|
|
163
|
+
// Applies each provided content transform sequentially.
|
|
164
|
+
for (const transform of transforms) {
|
|
165
|
+
content = transform(content);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
fs.writeFileSync(destinationPath, content, 'utf8');
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Writes .gitignore from template fallback names to final destination.
|
|
172
|
+
function writeGitignoreTemplate(templateRoot, targetRoot) {
|
|
173
|
+
// Defines acceptable template filenames used for gitignore source.
|
|
174
|
+
const candidates = ['.gitignore', 'gitignore'];
|
|
175
|
+
|
|
176
|
+
// Selects first existing candidate and writes it as .gitignore.
|
|
177
|
+
for (const candidate of candidates) {
|
|
178
|
+
// Resolves candidate template path.
|
|
179
|
+
const sourcePath = path.join(templateRoot, candidate);
|
|
180
|
+
if (!fs.existsSync(sourcePath)) continue;
|
|
181
|
+
|
|
182
|
+
// Resolves destination .gitignore path.
|
|
183
|
+
const destinationPath = path.join(targetRoot, '.gitignore');
|
|
184
|
+
fs.mkdirSync(path.dirname(destinationPath), { recursive: true });
|
|
185
|
+
fs.writeFileSync(destinationPath, fs.readFileSync(sourcePath, 'utf8'), 'utf8');
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
throw new Error('Missing gitignore template file in create-portfoliable package.');
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Copies a required template file path as-is.
|
|
193
|
+
function copyTemplateTree(templateRoot, relativePath, targetRoot) {
|
|
194
|
+
// Resolves source template path.
|
|
195
|
+
const sourcePath = path.join(templateRoot, relativePath);
|
|
196
|
+
// Resolves destination path in output project.
|
|
197
|
+
const destinationPath = path.join(targetRoot, relativePath);
|
|
198
|
+
|
|
199
|
+
if (!fs.existsSync(sourcePath)) {
|
|
200
|
+
throw new Error(`Missing template file: ${sourcePath}`);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
fs.mkdirSync(path.dirname(destinationPath), { recursive: true });
|
|
204
|
+
fs.writeFileSync(destinationPath, fs.readFileSync(sourcePath, 'utf8'), 'utf8');
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Recursively copies a template directory tree into target project.
|
|
208
|
+
function copyTemplateDirectory(templateRoot, relativeDirPath, targetRoot) {
|
|
209
|
+
// Resolves source directory path.
|
|
210
|
+
const sourceDir = path.join(templateRoot, relativeDirPath);
|
|
211
|
+
// Resolves destination directory path.
|
|
212
|
+
const destinationDir = path.join(targetRoot, relativeDirPath);
|
|
213
|
+
|
|
214
|
+
if (!fs.existsSync(sourceDir) || !fs.statSync(sourceDir).isDirectory()) {
|
|
215
|
+
throw new Error(`Missing template directory: ${sourceDir}`);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
fs.mkdirSync(destinationDir, { recursive: true });
|
|
219
|
+
|
|
220
|
+
// Iterates source directory entries for recursive copy.
|
|
221
|
+
for (const entry of fs.readdirSync(sourceDir, { withFileTypes: true })) {
|
|
222
|
+
// Resolves source entry path.
|
|
223
|
+
const sourcePath = path.join(sourceDir, entry.name);
|
|
224
|
+
// Resolves destination entry path.
|
|
225
|
+
const destinationPath = path.join(destinationDir, entry.name);
|
|
226
|
+
|
|
227
|
+
if (entry.isDirectory()) {
|
|
228
|
+
copyTemplateDirectory(templateRoot, path.join(relativeDirPath, entry.name), targetRoot);
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
fs.mkdirSync(path.dirname(destinationPath), { recursive: true });
|
|
233
|
+
fs.copyFileSync(sourcePath, destinationPath);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// MARK: INITIALIZER ORCHESTRATION
|
|
238
|
+
// Executes full scaffold flow: parse options, create files, install deps, and optional launch.
|
|
239
|
+
async function run() {
|
|
240
|
+
// Parses command-line options.
|
|
241
|
+
const options = parseArgs(process.argv);
|
|
242
|
+
|
|
243
|
+
if (options.help) {
|
|
244
|
+
printHelp();
|
|
245
|
+
process.exit(0);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Resolves current working directory.
|
|
249
|
+
const cwd = process.cwd();
|
|
250
|
+
// Resolves current script path for template root discovery.
|
|
251
|
+
const currentFile = fileURLToPath(import.meta.url);
|
|
252
|
+
// Derives human-facing project name from target path.
|
|
253
|
+
const projectName = path.basename(options.target);
|
|
254
|
+
// Derives npm-safe package name.
|
|
255
|
+
const packageName = sanitizePackageName(projectName);
|
|
256
|
+
// Resolves absolute target output directory.
|
|
257
|
+
const targetDir = path.resolve(cwd, options.target);
|
|
258
|
+
// Resolves runtime dependency version for generated app dependencies.
|
|
259
|
+
const runtimeDependencyVersion = resolveRuntimeDependencyVersion(currentFile);
|
|
260
|
+
// Resolves current terminal interaction policy.
|
|
261
|
+
const interactiveEnabled = options.interactive !== false;
|
|
262
|
+
|
|
263
|
+
if (!options.force && !isDirectoryEmpty(targetDir)) {
|
|
264
|
+
console.error(color('31', `Refusing to create in non-empty directory: ${targetDir}`));
|
|
265
|
+
console.error(color('33', 'Use --force if you want to continue.'));
|
|
266
|
+
process.exit(1);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Ensures target directory exists before writing scaffold files.
|
|
270
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
271
|
+
|
|
272
|
+
// Resolves template root directory bundled with create-portfoliable.
|
|
273
|
+
const templateRoot = path.resolve(path.dirname(currentFile), '..', 'templates');
|
|
274
|
+
|
|
275
|
+
// Builds generated package.json content for consumer app.
|
|
276
|
+
const packageJsonTemplate = JSON.stringify(
|
|
277
|
+
{
|
|
278
|
+
name: packageName,
|
|
279
|
+
version: '1.0.0',
|
|
280
|
+
private: true,
|
|
281
|
+
type: 'module',
|
|
282
|
+
scripts: {
|
|
283
|
+
portfoliable: 'portfoliable dev',
|
|
284
|
+
dev: 'portfoliable dev',
|
|
285
|
+
build: 'portfoliable build',
|
|
286
|
+
preview: 'portfoliable preview',
|
|
287
|
+
'validate:content': 'node ./scripts/validate-content.mjs',
|
|
288
|
+
'validate:protection': 'node ./scripts/validate-protection.mjs',
|
|
289
|
+
'convert:audio': 'node ./scripts/convert-wav-to-mp3.mjs',
|
|
290
|
+
'convert:video': 'node ./scripts/convert-video-to-mp4.mjs',
|
|
291
|
+
'password:hash': 'node ./scripts/generate-password-hash.mjs',
|
|
292
|
+
'create:case': 'node ./scripts/scaffold-case.mjs',
|
|
293
|
+
'delete:case': 'node ./scripts/delete-case.mjs',
|
|
294
|
+
'sync:locales': 'node ./scripts/sync-locales.mjs',
|
|
295
|
+
'sync:locales:watch': 'node ./scripts/sync-locales.mjs --watch',
|
|
296
|
+
'add:language': 'node ./scripts/add-language.mjs',
|
|
297
|
+
'delete:language': 'node ./scripts/delete-language.mjs',
|
|
298
|
+
'portfoliable-build': 'portfoliable build',
|
|
299
|
+
'portfoliable-preview': 'portfoliable preview',
|
|
300
|
+
'portfoliable-thumbnail-options': 'portfoliable thumbnail-options',
|
|
301
|
+
'portfoliable-create-case': 'portfoliable create-case',
|
|
302
|
+
'portfoliable-delete-case': 'portfoliable delete-case',
|
|
303
|
+
'portfoliable-sync-locales': 'portfoliable sync-locales'
|
|
304
|
+
},
|
|
305
|
+
dependencies: {
|
|
306
|
+
'@portfoliablejs/valence': '^1.0.1',
|
|
307
|
+
'create-portfoliable': runtimeDependencyVersion,
|
|
308
|
+
'markdown-it': '^14.1.0',
|
|
309
|
+
'markdown-it-deflist': '^2.1.0',
|
|
310
|
+
'markdown-it-footnote': '^4.0.0',
|
|
311
|
+
'markdown-it-task-lists': '^2.1.1'
|
|
312
|
+
}
|
|
313
|
+
},
|
|
314
|
+
null,
|
|
315
|
+
2
|
|
316
|
+
);
|
|
317
|
+
|
|
318
|
+
// Writes generated package.json to target project.
|
|
319
|
+
fs.writeFileSync(path.join(targetDir, 'package.json'), `${packageJsonTemplate}\n`, 'utf8');
|
|
320
|
+
|
|
321
|
+
writeGitignoreTemplate(templateRoot, targetDir);
|
|
322
|
+
writeFileFromTemplate(templateRoot, 'index.html', targetDir);
|
|
323
|
+
writeFileFromTemplate(templateRoot, path.join('src', 'main.js'), targetDir);
|
|
324
|
+
writeFileFromTemplate(templateRoot, path.join('src', 'cases', 'index.js'), targetDir);
|
|
325
|
+
writeFileFromTemplate(templateRoot, path.join('src', 'parser', 'markdown.js'), targetDir);
|
|
326
|
+
// Resolves optional template assets directory.
|
|
327
|
+
const templateAssetsDir = path.join(templateRoot, 'src', 'assets');
|
|
328
|
+
// Copies assets directory when present.
|
|
329
|
+
if (fs.existsSync(templateAssetsDir) && fs.statSync(templateAssetsDir).isDirectory()) {
|
|
330
|
+
copyTemplateDirectory(templateRoot, path.join('src', 'assets'), targetDir);
|
|
331
|
+
}
|
|
332
|
+
copyTemplateDirectory(templateRoot, 'scripts', targetDir);
|
|
333
|
+
copyTemplateDirectory(templateRoot, path.join('src', 'content'), targetDir);
|
|
334
|
+
copyTemplateDirectory(templateRoot, 'configs', targetDir);
|
|
335
|
+
copyTemplateDirectory(templateRoot, 'public', targetDir);
|
|
336
|
+
copyTemplateTree(templateRoot, 'README.md', targetDir);
|
|
337
|
+
|
|
338
|
+
printRailSegment();
|
|
339
|
+
printRailSegment({ dot: true, label: `${color('36', 'Creating project structure...')}` });
|
|
340
|
+
console.log(color('36', `Created ${projectName} at ${targetDir}`));
|
|
341
|
+
|
|
342
|
+
const existingPreferences = readProjectUiPreferences(targetDir);
|
|
343
|
+
const isFirstRun = !existingPreferences;
|
|
344
|
+
let fancyDots = existingPreferences?.fancyDots ?? true;
|
|
345
|
+
|
|
346
|
+
if (isFirstRun) {
|
|
347
|
+
if (interactiveEnabled && isInteractiveTerminal()) {
|
|
348
|
+
printRailSegment();
|
|
349
|
+
printRailSegment({ dot: true, label: `${ui.dim}First-time setup${ui.reset}` });
|
|
350
|
+
fancyDots = await promptFancyDotsPreference({
|
|
351
|
+
question: 'Use decorative terminal lines and dots in this project?',
|
|
352
|
+
defaultValue: true,
|
|
353
|
+
width: 86
|
|
354
|
+
});
|
|
355
|
+
} else {
|
|
356
|
+
console.log(color('33', 'Interactive first-run selection skipped (non-interactive terminal).'));
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
writeProjectUiPreferences(targetDir, fancyDots);
|
|
360
|
+
console.log(color('36', `Saved terminal UI preference: ${fancyDots ? 'decorative' : 'simple'}.`));
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
if (!options.install) {
|
|
364
|
+
console.log(color('33', 'Skipped npm install (--no-install).'));
|
|
365
|
+
console.log(`Next steps:\n cd ${options.target}\n npm install\n npm run portfoliable`);
|
|
366
|
+
console.log(color('36', 'After launch, Portfoliable will ask if you want to open the command guide.'));
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
printRailSegment();
|
|
371
|
+
printRailSegment({ dot: true, label: `${color('36', 'Installing dependencies...')}` });
|
|
372
|
+
console.log(color('36', 'Installing dependencies...'));
|
|
373
|
+
// Runs npm install in newly scaffolded project.
|
|
374
|
+
const installResult = spawnSync('npm', ['install'], {
|
|
375
|
+
cwd: targetDir,
|
|
376
|
+
stdio: 'inherit'
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
if (installResult.status !== 0) {
|
|
380
|
+
console.error(color('31', 'npm install failed. Run it manually in the new project folder.'));
|
|
381
|
+
process.exit(installResult.status || 1);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
console.log(color('32', 'Setup complete.'));
|
|
385
|
+
printRailSegment({ dot: true, label: `${color('32', 'Dependencies installed.')}` });
|
|
386
|
+
console.log(color('36', 'Command guide is available after launch via interactive prompt.'));
|
|
387
|
+
|
|
388
|
+
if (options.launch) {
|
|
389
|
+
printRailSegment();
|
|
390
|
+
printRailSegment({ dot: true, label: `${color('36', 'Preparing first build...')}` });
|
|
391
|
+
console.log(color('36', 'Building starter app...'));
|
|
392
|
+
// Executes initial production build to validate scaffold output.
|
|
393
|
+
const buildResult = spawnSync('npm', ['run', 'portfoliable-build'], {
|
|
394
|
+
cwd: targetDir,
|
|
395
|
+
stdio: 'inherit'
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
if (buildResult.status !== 0) {
|
|
399
|
+
console.error(color('31', 'Build failed. Run npm run portfoliable-build manually to inspect issues.'));
|
|
400
|
+
process.exit(buildResult.status || 1);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
printRailSegment({ dot: true, label: `${color('32', 'Starter build complete.')}` });
|
|
404
|
+
console.log(color('36', 'Launching development server (auto-opens in browser)...'));
|
|
405
|
+
// Starts development server with auto-open for immediate verification.
|
|
406
|
+
const devResult = spawnSync('npm', ['run', 'portfoliable', '--', '--open'], {
|
|
407
|
+
cwd: targetDir,
|
|
408
|
+
stdio: 'inherit'
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
if (devResult.status !== 0) {
|
|
412
|
+
process.exit(devResult.status || 1);
|
|
413
|
+
}
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
console.log(`Next steps:\n cd ${options.target}\n npm run portfoliable`);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// MARK: SCRIPT ENTRYPOINT
|
|
421
|
+
// Executes initializer orchestration.
|
|
422
|
+
run().catch((error) => {
|
|
423
|
+
console.error(color('31', error?.stack || String(error)));
|
|
424
|
+
process.exit(1);
|
|
425
|
+
});
|