@portosaur/cli 0.1.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.
Files changed (40) hide show
  1. package/README.md +52 -0
  2. package/bin/porto.mjs +71 -0
  3. package/package.json +36 -0
  4. package/src/commands/build.mjs +85 -0
  5. package/src/commands/dev.mjs +61 -0
  6. package/src/commands/init.mjs +523 -0
  7. package/src/commands/initCi.mjs +227 -0
  8. package/src/commands/providers.mjs +170 -0
  9. package/src/commands/schema.mjs +208 -0
  10. package/src/commands/serve.mjs +29 -0
  11. package/src/index.d.ts +49 -0
  12. package/src/index.mjs +8 -0
  13. package/src/templates/README.md +58 -0
  14. package/src/templates/blog/authors.yml +4 -0
  15. package/src/templates/blog/welcome.md +11 -0
  16. package/src/templates/config.yml +150 -0
  17. package/src/templates/gitignore +9 -0
  18. package/src/templates/notes/index.mdx +9 -0
  19. package/src/templates/notes/welcome.mdx +9 -0
  20. package/src/templates/package.json +14 -0
  21. package/src/templates/registry.yml +107 -0
  22. package/src/templates/static/.nojekyll +0 -0
  23. package/src/templates/static/README.md +1 -0
  24. package/src/templates/workflows/codeberg/.forgejo/workflows/deploy.yml +39 -0
  25. package/src/templates/workflows/github/.github/workflows/deploy.yml +55 -0
  26. package/src/templates/workflows/gitlab/.gitlab-ci.yml +13 -0
  27. package/src/templates/workflows/netlify/netlify.toml +6 -0
  28. package/src/templates/workflows/surge/codeberg/.forgejo/workflows/deploy.yml +23 -0
  29. package/src/templates/workflows/surge/github/.github/workflows/deploy.yml +23 -0
  30. package/src/templates/workflows/surge/gitlab/.gitlab-ci.yml +16 -0
  31. package/src/templates/workflows/surge/sourcehut/.build.yml +26 -0
  32. package/src/templates/workflows/woodpecker/.woodpecker/deploy.yml +21 -0
  33. package/src/utils/git.mjs +52 -0
  34. package/src/utils/index.mjs +7 -0
  35. package/src/utils/interaction.mjs +24 -0
  36. package/src/utils/packageManager.mjs +85 -0
  37. package/src/utils/paths.mjs +33 -0
  38. package/src/utils/platforms.mjs +130 -0
  39. package/src/utils/projectName.mjs +20 -0
  40. package/src/utils/runner.mjs +192 -0
@@ -0,0 +1,227 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import yaml from "js-yaml";
4
+
5
+ import { runWizard, cancel } from "@portosaur/wizard";
6
+
7
+ import { logger, colors } from "@portosaur/logger";
8
+ import { mirrorSync, porto } from "@portosaur/core";
9
+ import {
10
+ Paths,
11
+ detectVcsProvider,
12
+ getGitConfig,
13
+ getWorkflowMarkers,
14
+ resolvePlatformKey,
15
+ getPlatformUserGuess,
16
+ validateProject,
17
+ printWorkflowTips,
18
+ warnIfRepoNameMismatch,
19
+ isInteractive as getInteractivity,
20
+ looksLikeTestProject,
21
+ } from "../utils/index.mjs";
22
+
23
+ /**
24
+ * Initializes CI/CD configuration for an existing Portosaur project.
25
+ */
26
+ export async function initCiCommand(options = {}) {
27
+ //
28
+ //====================== Constants ======================
29
+
30
+ const projectDir = process.cwd();
31
+
32
+ const registry = yaml.load(fs.readFileSync(Paths.registry, "utf8"));
33
+
34
+ const gitConfig = getGitConfig();
35
+ const vcsProviderId = detectVcsProvider(registry);
36
+
37
+ const isInteractive = getInteractivity(options);
38
+
39
+ // Validate project existence
40
+ try {
41
+ validateProject(projectDir);
42
+ } catch (error) {
43
+ logger.error(error.message);
44
+ process.exit(1);
45
+ }
46
+
47
+ // Detect existing CI configurations based on workflow templates
48
+ const ciMarkers = getWorkflowMarkers(registry);
49
+ const detectedMarkers = ciMarkers.filter((marker) =>
50
+ fs.existsSync(path.join(projectDir, marker)),
51
+ );
52
+
53
+ let state = {
54
+ hostingPlatform: options.hosting || null,
55
+ };
56
+
57
+ /*
58
+ * ====================== Interactive mode ======================
59
+ */
60
+ if (isInteractive) {
61
+ const vcs = registry.vcs_providers[vcsProviderId];
62
+
63
+ const wizardState = await runWizard({
64
+ initialState: state,
65
+
66
+ intro: "Setting up Portosaur CI/CD",
67
+ outro: false,
68
+
69
+ steps: [
70
+ {
71
+ id: "overwrite",
72
+ type: "confirm",
73
+ prompt: "Existing CI configuration detected. Overwrite?",
74
+ hint: `Detected: ${detectedMarkers.join(", ")}`,
75
+ runIf: () => detectedMarkers.length > 0,
76
+ initialValue: false,
77
+ },
78
+ {
79
+ id: "hostingPlatform",
80
+ type: "select",
81
+ prompt: "Select target hosting platform",
82
+ hint: "The service where your portfolio will be deployed",
83
+ options: Object.keys(registry.hosting_platforms).map((key) => ({
84
+ value: key,
85
+ label: registry.hosting_platforms[key].name,
86
+ hint: key === vcs?.default_hosting ? "recommended" : "",
87
+ })),
88
+ initialValue: () => vcs?.default_hosting || "none",
89
+ },
90
+ {
91
+ id: "confirm",
92
+ type: "confirm",
93
+ prompt: "Are you sure you want to proceed?",
94
+ hint: (state) =>
95
+ state.overwrite
96
+ ? "This will DELETE existing CI files and create new ones"
97
+ : "This will create CI configuration files",
98
+ initialValue: true,
99
+ },
100
+ ],
101
+ });
102
+
103
+ if (
104
+ !wizardState.confirm ||
105
+ (detectedMarkers.length > 0 && wizardState.overwrite === false)
106
+ ) {
107
+ cancel("Setup cancelled.");
108
+ process.exit(0);
109
+ }
110
+
111
+ state = { ...state, ...wizardState };
112
+
113
+ /*
114
+ * ====================== Non-Interactive Mode ======================
115
+ */
116
+ } else {
117
+ //
118
+ // Normalize hosting platform
119
+ if (state.hostingPlatform && state.hostingPlatform !== "none") {
120
+ state.hostingPlatform =
121
+ resolvePlatformKey(registry.hosting_platforms, state.hostingPlatform) ||
122
+ state.hostingPlatform;
123
+ }
124
+
125
+ const vcs = registry.vcs_providers[vcsProviderId];
126
+
127
+ state.hostingPlatform =
128
+ state.hostingPlatform || vcs?.default_hosting || "none";
129
+
130
+ if (state.hostingPlatform !== "none") {
131
+ logger.info(
132
+ `Detected ${vcs?.name || vcsProviderId} provider, defaulting to ${state.hostingPlatform}.`,
133
+ );
134
+ }
135
+ }
136
+
137
+ /*
138
+ * ====================== Execution ======================
139
+ */
140
+
141
+ // Final canonical resolution
142
+ const resolved = resolvePlatformKey(
143
+ registry.hosting_platforms,
144
+ state.hostingPlatform,
145
+ );
146
+
147
+ if (!resolved || resolved === "none") {
148
+ logger.error(
149
+ `Unknown or invalid hosting platform: ${state.hostingPlatform || "none"}`,
150
+ );
151
+ process.exit(1);
152
+ }
153
+
154
+ state.hostingPlatform = resolved;
155
+
156
+ //--------- Clear the console ----------------
157
+ logger.resetView();
158
+ logger.info.box("Portosaur CI/CD Setup", {
159
+ title: ` v${porto.version} `,
160
+ });
161
+
162
+ // Gather variables for template replacement
163
+ const isTestProject = looksLikeTestProject(path.basename(projectDir));
164
+ const portoVer = isTestProject ? "link:portosaur" : porto.version || "0.0.0";
165
+
166
+ const userName = getPlatformUserGuess(vcsProviderId, gitConfig) || "user";
167
+ const fullName = gitConfig["user.name"] || "User";
168
+
169
+ const templateVars = {
170
+ projectName: path.basename(projectDir),
171
+ userName,
172
+ fullName,
173
+ portoVer,
174
+ };
175
+
176
+ const hConfig = registry.hosting_platforms[state.hostingPlatform];
177
+
178
+ logger.info(`Configuring CI/CD for ${hConfig.name}...`);
179
+
180
+ try {
181
+ let template = hConfig.template_dir;
182
+ if (typeof template === "object") {
183
+ template = template[vcsProviderId] || Object.values(template)[0];
184
+ }
185
+
186
+ const workflowTemplateDir = path.join(Paths.workflows, template);
187
+
188
+ if (!fs.existsSync(workflowTemplateDir)) {
189
+ throw new Error(
190
+ `Platform template for "${state.hostingPlatform}" not found.`,
191
+ );
192
+ }
193
+
194
+ // Clean up existing CI files if requested
195
+ if (state.overwrite) {
196
+ logger.info("Cleaning up existing CI configuration...");
197
+
198
+ for (const marker of detectedMarkers) {
199
+ const fullPath = path.join(projectDir, marker);
200
+ if (fs.existsSync(fullPath)) {
201
+ fs.rmSync(fullPath, { recursive: true, force: true });
202
+ }
203
+ }
204
+ }
205
+
206
+ mirrorSync(workflowTemplateDir, projectDir, templateVars, []);
207
+
208
+ /*
209
+ * ====================== Final Output ======================
210
+ */
211
+
212
+ logger.resetView();
213
+ logger.success.box(`CI/CD successfully configured!`);
214
+
215
+ printWorkflowTips(
216
+ registry.hosting_platforms,
217
+ state.hostingPlatform,
218
+ logger,
219
+ templateVars,
220
+ );
221
+
222
+ logger.newLine();
223
+ } catch (error) {
224
+ logger.error(`Configuration failed: ${error.message}`);
225
+ process.exit(1);
226
+ }
227
+ }
@@ -0,0 +1,170 @@
1
+ import { readFileSync } from "fs";
2
+ import yaml from "js-yaml";
3
+ import { colors } from "@portosaur/logger";
4
+ import { Paths } from "../utils/index.mjs";
5
+
6
+ /**
7
+ * Displays available VCS providers and hosting platforms.
8
+ */
9
+ export async function providersCommand(subcommand = null) {
10
+ const registry = yaml.load(readFileSync(Paths.registry, "utf8"));
11
+
12
+ const vcsProviders = registry.vcs_providers || {};
13
+ const hostingPlatforms = registry.hosting_platforms || {};
14
+
15
+ switch (subcommand) {
16
+ case "vcs":
17
+ displayVcsProviders(vcsProviders);
18
+ break;
19
+
20
+ case "hosting":
21
+ displayHostingPlatforms(hostingPlatforms, vcsProviders);
22
+ break;
23
+
24
+ default:
25
+ displayAll(vcsProviders, hostingPlatforms);
26
+ }
27
+ }
28
+
29
+ /**
30
+ * Format template variables for display (e.g., {{user}} -> <username>).
31
+ */
32
+ function formatTemplateVars(str) {
33
+ if (!str) return str;
34
+ return str.replace(/\{\{(\w+)\}\}/g, "<$1>");
35
+ }
36
+
37
+ /**
38
+ * Strip ANSI color codes to get the actual display width.
39
+ */
40
+ function stripAnsi(str) {
41
+ return String(str).replace(/\x1b\[[0-9;]*m/g, "");
42
+ }
43
+
44
+ /**
45
+ * Create a simple ASCII table.
46
+ */
47
+ function createTable(rows, columns) {
48
+ const colWidths = columns.map((col) => col.length);
49
+
50
+ for (const row of rows) {
51
+ for (let i = 0; i < columns.length; i++) {
52
+ const val = stripAnsi(row[i] || "");
53
+ colWidths[i] = Math.max(colWidths[i], val.length);
54
+ }
55
+ }
56
+
57
+ const lines = [];
58
+
59
+ // Header
60
+ const headerLine = columns
61
+ .map((col, i) => colors.bold(col.padEnd(colWidths[i])))
62
+ .join(" ");
63
+ lines.push(headerLine);
64
+
65
+ // Separator
66
+ const sepLine = colWidths.map((w) => "─".repeat(w)).join("──");
67
+ lines.push(colors.dim(sepLine));
68
+
69
+ // Rows
70
+ for (const row of rows) {
71
+ const line = row
72
+ .map((val, i) => {
73
+ const plain = stripAnsi(val || "");
74
+ const padded = plain.padEnd(colWidths[i]);
75
+ // If the value is colored, we need to apply color to the padded version
76
+ const colored = val || "";
77
+ if (stripAnsi(colored) !== colored) {
78
+ // Has color codes - reapply coloring after padding
79
+ const colorCodes = colored.match(/\x1b\[[0-9;]*m/g) || [];
80
+ const resetCode = "\x1b[0m";
81
+ return colored + resetCode + padded.slice(plain.length);
82
+ }
83
+ return padded;
84
+ })
85
+ .join(" ");
86
+ lines.push(line);
87
+ }
88
+
89
+ return lines.join("\n");
90
+ }
91
+
92
+ /**
93
+ * Display all providers and platforms.
94
+ */
95
+ function displayAll(vcsProviders, hostingPlatforms) {
96
+ console.log("📦 " + colors.bold("Available Providers"));
97
+ console.log("");
98
+
99
+ displayVcsProviders(vcsProviders);
100
+ console.log("");
101
+
102
+ displayHostingPlatforms(hostingPlatforms, vcsProviders);
103
+ }
104
+
105
+ /**
106
+ * Display VCS providers.
107
+ */
108
+ function displayVcsProviders(vcsProviders) {
109
+ console.log(colors.bold("VCS Providers:"));
110
+ console.log("");
111
+
112
+ const rows = Object.entries(vcsProviders).map(([id, config]) => [
113
+ colors.cyan(id),
114
+ config.name || "N/A",
115
+ config.domain || "N/A",
116
+ config.default_hosting || "N/A",
117
+ ]);
118
+
119
+ console.log(createTable(rows, ["ID", "Name", "Domain", "Default Hosting"]));
120
+ }
121
+
122
+ /**
123
+ * Display hosting platforms with supported providers.
124
+ */
125
+ function displayHostingPlatforms(hostingPlatforms, vcsProviders) {
126
+ console.log(colors.bold("Hosting Platforms:"));
127
+ console.log("");
128
+
129
+ const rows = Object.entries(hostingPlatforms).map(([id, config]) => {
130
+ const supportedProviders = getSupportedProviders(config, vcsProviders);
131
+ const idealName = config.repo?.ideal_name
132
+ ? formatTemplateVars(config.repo.ideal_name)
133
+ : "N/A";
134
+
135
+ return [
136
+ colors.cyan(id),
137
+ config.name,
138
+ config.domain || "N/A",
139
+ supportedProviders.join(", "),
140
+ idealName,
141
+ ];
142
+ });
143
+
144
+ console.log(
145
+ createTable(rows, [
146
+ "ID",
147
+ "Name",
148
+ "Domain",
149
+ "Supported Providers",
150
+ "Ideal Repo Name",
151
+ ]),
152
+ );
153
+ }
154
+
155
+ /**
156
+ * Get supported providers for a hosting platform.
157
+ */
158
+ function getSupportedProviders(platform, vcsProviders) {
159
+ const supportedProviders = platform.supported_providers;
160
+
161
+ if (Array.isArray(supportedProviders)) {
162
+ return supportedProviders;
163
+ }
164
+
165
+ if (supportedProviders === "all") {
166
+ return Object.keys(vcsProviders);
167
+ }
168
+
169
+ return ["none"];
170
+ }
@@ -0,0 +1,208 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import { logger } from "@portosaur/logger";
4
+
5
+ /**
6
+ * Portosaur Discovery-Based Schema Generator
7
+ *
8
+ * This command "discovers" the configuration schema by parsing the actual
9
+ * Docusaurus config building logic. Every key accessed via the `get()` helper
10
+ * is recorded and mapped to a JSON schema.
11
+ */
12
+ export async function schemaCommand(options = {}) {
13
+ // Resolve paths for the monorepo structure
14
+ const pkgDir = path.resolve(import.meta.dirname, "../");
15
+ const coreDir = path.resolve(pkgDir, "../../core");
16
+
17
+ const SOURCE_FILE =
18
+ typeof options.config === "string"
19
+ ? path.resolve(process.cwd(), options.config)
20
+ : path.resolve(coreDir, "src/index.mjs");
21
+
22
+ const OUTPUT_FILE =
23
+ typeof options.output === "string"
24
+ ? path.resolve(process.cwd(), options.output)
25
+ : path.resolve(pkgDir, "../../../configSchema.json");
26
+
27
+ const discoveredSchema = {
28
+ $schema: "http://json-schema.org/draft-07/schema#",
29
+ title: "Portosaur Project Configuration",
30
+ type: "object",
31
+ properties: {},
32
+ required: [],
33
+ additionalProperties: true,
34
+ };
35
+
36
+ // State for Balanced Parenthesis Walker
37
+ const getStartRegex = /get\(\s*["']([^"']+)["']\s*,\s*/g;
38
+ let match;
39
+
40
+ try {
41
+ if (!fs.existsSync(SOURCE_FILE)) {
42
+ throw new Error(`Source file not found: ${SOURCE_FILE}`);
43
+ }
44
+
45
+ logger.info(`Discovering keys from ${SOURCE_FILE}...`);
46
+ const sourceCode = fs.readFileSync(SOURCE_FILE, "utf8");
47
+
48
+ // Discovery Loop
49
+ while ((match = getStartRegex.exec(sourceCode)) !== null) {
50
+ const keyPath = match[1];
51
+ const startIdx = match.index + match[0].length;
52
+
53
+ let braceCount = 1;
54
+ let currentIdx = startIdx;
55
+ let defaultValueRaw = "";
56
+ let inString = null;
57
+ let inComment = null;
58
+ let escaped = false;
59
+
60
+ // Extract raw default value using balanced parenthesis walker
61
+ while (braceCount > 0 && currentIdx < sourceCode.length) {
62
+ const char = sourceCode[currentIdx];
63
+ const nextChar = sourceCode[currentIdx + 1];
64
+
65
+ if (escaped) {
66
+ escaped = false;
67
+ } else if (char === "\\") {
68
+ escaped = true;
69
+ } else if (!inString && !inComment) {
70
+ if (char === "'" || char === '"' || char === "`") inString = char;
71
+ else if (char === "/" && nextChar === "/") inComment = "//";
72
+ else if (char === "/" && nextChar === "*") inComment = "/*";
73
+ else if (char === "(") braceCount++;
74
+ else if (char === ")") braceCount--;
75
+ } else if (inString) {
76
+ if (char === inString) inString = null;
77
+ } else if (inComment === "//") {
78
+ if (char === "\n") inComment = null;
79
+ } else if (inComment === "/*") {
80
+ if (char === "*" && nextChar === "/") {
81
+ inComment = null;
82
+ defaultValueRaw += "*/";
83
+ currentIdx += 2;
84
+ continue;
85
+ }
86
+ }
87
+
88
+ if (braceCount > 0) defaultValueRaw += char;
89
+ currentIdx++;
90
+ }
91
+
92
+ // Extract Documentation Comments
93
+ const beforeMatch = sourceCode.slice(0, match.index);
94
+ const currentLineStart = beforeMatch.lastIndexOf("\n");
95
+ const linesBefore = sourceCode
96
+ .slice(0, currentLineStart === -1 ? 0 : currentLineStart)
97
+ .split("\n");
98
+
99
+ let i = linesBefore.length - 1;
100
+ let foundComment = [];
101
+ let inDocBlock = false;
102
+
103
+ while (i >= 0 && foundComment.length < 15) {
104
+ let line = linesBefore[i].trim();
105
+ if (!line && !inDocBlock) break;
106
+ if (line.endsWith("*/")) inDocBlock = true;
107
+
108
+ if (inDocBlock) {
109
+ const content = line.replace(/^\/\*\*?|\*\/|\*/g, "").trim();
110
+ if (content) foundComment.unshift(content);
111
+ if (line.startsWith("/*") || line.startsWith("/**"))
112
+ inDocBlock = false;
113
+ } else if (line.startsWith("//")) {
114
+ let content = line.replace(/^\/\/\s?/, "").trim();
115
+ if (!content.match(/^(TODO|FIXME|NOTE|SECTION|---)/i)) {
116
+ foundComment.unshift(content);
117
+ }
118
+ } else if (line && !line.startsWith("/") && !line.startsWith("*")) {
119
+ break;
120
+ }
121
+ i--;
122
+ }
123
+ const description = foundComment.join("\n").trim();
124
+
125
+ // Basic Type Inference and Argument Parsing
126
+ let type = "string";
127
+ let defaultValue = undefined;
128
+
129
+ let args = [];
130
+ let currentArg = "";
131
+ let depth = 0;
132
+ let argInString = null;
133
+
134
+ for (let i = 0; i < defaultValueRaw.length; i++) {
135
+ const c = defaultValueRaw[i];
136
+ if (!argInString) {
137
+ if (c === "'" || c === '"' || c === "`") argInString = c;
138
+ else if (c === "(" || c === "[" || c === "{") depth++;
139
+ else if (c === ")" || c === "]" || c === "}") depth--;
140
+ else if (c === "," && depth === 0) {
141
+ args.push(currentArg.trim());
142
+ currentArg = "";
143
+ continue;
144
+ }
145
+ } else if (c === argInString && defaultValueRaw[i - 1] !== "\\") {
146
+ argInString = null;
147
+ }
148
+ currentArg += c;
149
+ }
150
+ if (currentArg.trim()) args.push(currentArg.trim());
151
+
152
+ let val = (args[args.length - 1] || "").trim();
153
+
154
+ if (val === "true" || val === "false") {
155
+ type = "boolean";
156
+ defaultValue = val === "true";
157
+ } else if (!isNaN(val) && val !== "") {
158
+ type = "number";
159
+ defaultValue = Number(val);
160
+ } else if (
161
+ val.startsWith("[") ||
162
+ val.includes(".map(") ||
163
+ val.includes(".filter(") ||
164
+ val.includes(".slice(")
165
+ ) {
166
+ type = "array";
167
+ } else if (val.startsWith("{")) {
168
+ type = "object";
169
+ } else if (val) {
170
+ defaultValue = val.replace(/^["'`](.*)["'`]$/s, "$1").trim();
171
+ }
172
+
173
+ // Map to Discovered Schema Object
174
+ const parts = keyPath.split(".");
175
+ let current = discoveredSchema.properties;
176
+
177
+ parts.forEach((part, index) => {
178
+ const isLast = index === parts.length - 1;
179
+
180
+ if (!current[part]) {
181
+ if (isLast) {
182
+ current[part] = { type };
183
+ if (description) current[part].description = description;
184
+ if (defaultValue !== undefined)
185
+ current[part].default = defaultValue;
186
+ } else {
187
+ current[part] = { type: "object", properties: {} };
188
+ }
189
+ } else if (!isLast && current[part].type !== "object") {
190
+ current[part] = { type: "object", properties: {} };
191
+ }
192
+
193
+ if (!isLast) {
194
+ if (!current[part].properties) current[part].properties = {};
195
+ current = current[part].properties;
196
+ }
197
+ });
198
+ }
199
+
200
+ fs.writeFileSync(OUTPUT_FILE, JSON.stringify(discoveredSchema, null, 2));
201
+ logger.success(
202
+ `Successfully discovered and wrote schema to: ${OUTPUT_FILE}`,
203
+ );
204
+ } catch (error) {
205
+ logger.error(`Discovery failed: ${error.message}`);
206
+ process.exit(1);
207
+ }
208
+ }
@@ -0,0 +1,29 @@
1
+ import path from "path";
2
+ import { runDocusaurus, validateProject } from "../utils/index.mjs";
3
+ import { logger } from "@portosaur/logger";
4
+
5
+ /**
6
+ * Serves the built Portosaur site locally.
7
+ *
8
+ * Note: Docusaurus 'serve' doesn't require a config shim,
9
+ * as it serves the already built static files from the output directory.
10
+ */
11
+ export async function serveCommand(siteDir, extraArgs = []) {
12
+ const UserRoot = siteDir
13
+ ? path.resolve(process.cwd(), siteDir)
14
+ : process.cwd();
15
+
16
+ // ------- Setup -------
17
+
18
+ validateProject(UserRoot);
19
+
20
+ try {
21
+ logger.info("Serving built site...");
22
+
23
+ // Docusaurus serve looks for the 'build' directory by default.
24
+ await runDocusaurus("serve", UserRoot, "", extraArgs);
25
+ } catch (error) {
26
+ logger.error(`Failed to serve site: ${error.message}`);
27
+ process.exit(1);
28
+ }
29
+ }
package/src/index.d.ts ADDED
@@ -0,0 +1,49 @@
1
+ export interface InitOptions {
2
+ /** VCS Provider ID (e.g., "github", "gitlab") */
3
+ vcsProvider?: string;
4
+ /** Hosting Platform ID (e.g., "github_pages", "surge") */
5
+ hosting?: string;
6
+ /** VCS username */
7
+ username?: string;
8
+ /** Full name for the portfolio */
9
+ name?: string;
10
+ /** Project name/directory */
11
+ projectName?: string;
12
+ /** Whether to install dependencies after initialization (default: true) */
13
+ install?: boolean;
14
+ }
15
+
16
+ export interface InitCiOptions {
17
+ /** Hosting Platform ID */
18
+ hosting?: string;
19
+ }
20
+
21
+ /**
22
+ * Initializes a new Portosaur project.
23
+ */
24
+ export function initCommand(options?: InitOptions): Promise<void>;
25
+
26
+ /**
27
+ * Sets up CI/CD workflows for an existing project.
28
+ */
29
+ export function initCiCommand(options?: InitCiOptions): Promise<void>;
30
+
31
+ /**
32
+ * Starts the Docusaurus development server.
33
+ */
34
+ export function devCommand(siteDir?: string, extraArgs?: string[]): void;
35
+
36
+ /**
37
+ * Builds the Docusaurus static site.
38
+ */
39
+ export function buildCommand(siteDir?: string, extraArgs?: string[]): void;
40
+
41
+ /**
42
+ * Serves the built static site locally.
43
+ */
44
+ export function serveCommand(siteDir?: string): void;
45
+
46
+ /**
47
+ * Generates the config schema.
48
+ */
49
+ export function schemaCommand(outPath?: string, srcPath?: string): void;
package/src/index.mjs ADDED
@@ -0,0 +1,8 @@
1
+ export * from "./commands/init.mjs";
2
+ export * from "./commands/initCi.mjs";
3
+ export * from "./commands/dev.mjs";
4
+ export * from "./commands/build.mjs";
5
+ export * from "./commands/serve.mjs";
6
+ export * from "./commands/schema.mjs";
7
+ export * from "./commands/providers.mjs";
8
+ export * from "./utils/index.mjs";