create-ardo 3.1.0 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,329 +1,311 @@
1
1
  #!/usr/bin/env node
2
-
3
- // src/index.ts
4
- import fs2 from "fs";
5
- import path2 from "path";
2
+ import { blue, cyan, dim, green, red, reset, yellow } from "kolorist";
3
+ import fs from "node:fs";
4
+ import path from "node:path";
6
5
  import prompts from "prompts";
7
- import { blue, cyan, green, red, reset, yellow, dim } from "kolorist";
8
-
9
- // src/scaffold.ts
10
- import fs from "fs";
11
- import path from "path";
12
- import { fileURLToPath } from "url";
13
- var __dirname = path.dirname(fileURLToPath(import.meta.url));
14
- var templatesRoot = path.resolve(__dirname, "..", "templates");
15
- var packageJsonPath = path.resolve(__dirname, "..", "package.json");
6
+ //#region src/scaffold.ts
7
+ const __dirname = import.meta.dirname;
8
+ const templatesRoot = path.resolve(__dirname, "..", "templates");
9
+ const packageJsonPath = path.resolve(__dirname, "..", "package.json");
16
10
  function getCliVersion() {
17
- const pkg = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
18
- return pkg.version;
11
+ const pkg = readJsonObject(packageJsonPath);
12
+ return typeof pkg?.version === "string" ? pkg.version : "0.0.0";
19
13
  }
20
- var templates = [
21
- {
22
- name: "minimal",
23
- display: "Minimal",
24
- description: "Basic setup with essential files only"
25
- }
26
- ];
14
+ const templates = [{
15
+ name: "minimal",
16
+ display: "Minimal",
17
+ description: "Basic setup with essential files only"
18
+ }];
27
19
  function createProjectStructure(root, template, options) {
28
- const templateDir = path.join(templatesRoot, template);
29
- const vars = {
30
- SITE_TITLE: options.siteTitle,
31
- PROJECT_NAME: options.projectName,
32
- ARDO_VERSION: getCliVersion(),
33
- TYPEDOC_CONFIG: options.typedoc ? "typedoc: true," : "// typedoc: true, // Uncomment to enable API docs",
34
- GITHUB_PAGES_CONFIG: options.githubPages ? "// GitHub Pages: base path auto-detected from git remote" : "githubPages: false, // Disabled for non-GitHub Pages deployment",
35
- GITHUB_PAGES_BASENAME_IMPORT: options.githubPages ? 'import { detectGitHubBasename } from "ardo/vite"' : "",
36
- GITHUB_PAGES_BASENAME: options.githubPages ? "basename: detectGitHubBasename()," : "// basename: detectGitHubBasename(), // Uncomment for GitHub Pages",
37
- DESCRIPTION: options.description,
38
- TYPEDOC_NAV: options.typedoc ? "{ text: 'API', link: '/api-reference' }," : "",
39
- TYPEDOC_SIDEBAR: options.typedoc ? "{ text: 'API Reference', link: '/api-reference' }," : ""
40
- };
41
- copyDir(templateDir, root, vars);
20
+ copyDir(path.join(templatesRoot, template), root, {
21
+ SITE_TITLE: options.siteTitle,
22
+ PROJECT_NAME: options.projectName,
23
+ ARDO_VERSION: getCliVersion(),
24
+ TYPEDOC_CONFIG: options.typedoc ? "typedoc: true," : "// typedoc: true, // Uncomment to enable API docs",
25
+ GITHUB_PAGES_CONFIG: options.githubPages ? "// GitHub Pages: base path auto-detected from git remote" : "githubPages: false, // Disabled for non-GitHub Pages deployment",
26
+ GITHUB_PAGES_BASENAME_IMPORT: options.githubPages ? "import { detectGitHubBasename } from \"ardo/vite\"" : "",
27
+ GITHUB_PAGES_BASENAME: options.githubPages ? "basename: detectGitHubBasename()," : "// basename: detectGitHubBasename(), // Uncomment for GitHub Pages",
28
+ DESCRIPTION: options.description
29
+ });
42
30
  }
43
31
  function copyDir(src, dest, vars) {
44
- fs.mkdirSync(dest, { recursive: true });
45
- for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
46
- const srcPath = path.join(src, entry.name);
47
- const destName = entry.name === "_gitignore" ? ".gitignore" : entry.name;
48
- const destPath = path.join(dest, destName);
49
- if (entry.isDirectory()) {
50
- copyDir(srcPath, destPath, vars);
51
- } else {
52
- let content = fs.readFileSync(srcPath, "utf-8");
53
- for (const [key, value] of Object.entries(vars)) {
54
- content = content.replaceAll(`{{${key}}}`, value);
55
- }
56
- fs.writeFileSync(destPath, content);
57
- }
58
- }
32
+ fs.mkdirSync(dest, { recursive: true });
33
+ for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
34
+ const srcPath = path.join(src, entry.name);
35
+ const destName = entry.name === "_gitignore" ? ".gitignore" : entry.name;
36
+ const destPath = path.join(dest, destName);
37
+ if (entry.isDirectory()) copyDir(srcPath, destPath, vars);
38
+ else {
39
+ let content = fs.readFileSync(srcPath, "utf8");
40
+ for (const [key, value] of Object.entries(vars)) content = content.replaceAll(`{{${key}}}`, value);
41
+ fs.writeFileSync(destPath, content);
42
+ }
43
+ }
59
44
  }
60
45
  function formatTargetDir(targetDir) {
61
- return targetDir?.trim().replace(/\/+$/g, "");
46
+ if (targetDir === void 0) return;
47
+ let normalized = targetDir.trim();
48
+ while (normalized.endsWith("/")) normalized = normalized.slice(0, -1);
49
+ return normalized;
62
50
  }
63
51
  function isEmpty(dirPath) {
64
- const files = fs.readdirSync(dirPath);
65
- return files.length === 0 || files.length === 1 && files[0] === ".git";
52
+ const files = fs.readdirSync(dirPath);
53
+ return files.length === 0 || files.length === 1 && files[0] === ".git";
66
54
  }
67
55
  function emptyDir(dir) {
68
- if (!fs.existsSync(dir)) {
69
- return;
70
- }
71
- for (const file of fs.readdirSync(dir)) {
72
- if (file === ".git") {
73
- continue;
74
- }
75
- fs.rmSync(path.join(dir, file), { recursive: true, force: true });
76
- }
56
+ if (!fs.existsSync(dir)) return;
57
+ for (const file of fs.readdirSync(dir)) {
58
+ if (file === ".git") continue;
59
+ fs.rmSync(path.join(dir, file), {
60
+ recursive: true,
61
+ force: true
62
+ });
63
+ }
77
64
  }
78
65
  function isValidTemplate(template) {
79
- return templates.some((t) => t.name === template);
66
+ return templates.some((t) => t.name === template);
80
67
  }
81
68
  function detectProjectDescription(targetDir) {
82
- for (const dir of [path.dirname(targetDir), targetDir]) {
83
- const pkgPath = path.join(dir, "package.json");
84
- try {
85
- const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
86
- if (pkg.description) {
87
- return pkg.description;
88
- }
89
- } catch {
90
- }
91
- }
92
- return void 0;
69
+ for (const dir of [path.dirname(targetDir), targetDir]) {
70
+ const pkg = readJsonObject(path.join(dir, "package.json"));
71
+ if (pkg === void 0) continue;
72
+ if (typeof pkg.description === "string" && pkg.description !== "") return pkg.description;
73
+ }
93
74
  }
94
75
  function isArdoProject(dir) {
95
- try {
96
- const pkgPath = path.join(dir, "package.json");
97
- const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
98
- return Boolean(pkg.dependencies?.ardo);
99
- } catch {
100
- return false;
101
- }
76
+ const pkg = readJsonObject(path.join(dir, "package.json"));
77
+ if (pkg === void 0) return false;
78
+ return toStringRecord(pkg.dependencies)?.ardo !== void 0;
79
+ }
80
+ function copySkeletonFiles(root, templateDir, result) {
81
+ for (const file of [
82
+ "app/entry.client.tsx",
83
+ "app/entry.server.tsx",
84
+ "app/root.tsx",
85
+ "tsconfig.json"
86
+ ]) {
87
+ const src = path.join(templateDir, file);
88
+ const dest = path.join(root, file);
89
+ if (fs.existsSync(src)) {
90
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
91
+ fs.copyFileSync(src, dest);
92
+ result.updated.push(file);
93
+ } else result.skipped.push(file);
94
+ }
95
+ }
96
+ function mergePackageJson(root, templateDir, cliVersion) {
97
+ const userPkgPath = path.join(root, "package.json");
98
+ const userPkg = readJsonObject(userPkgPath);
99
+ const templatePkg = readJsonObject(path.join(templateDir, "package.json"));
100
+ if (userPkg === void 0 || templatePkg === void 0) throw new Error("Could not parse package.json while upgrading project");
101
+ const userDeps = ensureStringRecord(userPkg, "dependencies");
102
+ const userDevDeps = ensureStringRecord(userPkg, "devDependencies");
103
+ const templateDeps = toStringRecord(templatePkg.dependencies) ?? {};
104
+ const templateDevDeps = toStringRecord(templatePkg.devDependencies) ?? {};
105
+ userDeps.ardo = `^${cliVersion}`;
106
+ for (const [dep, version] of Object.entries(templateDeps)) if (dep !== "ardo" && !(dep in userDeps)) userDeps[dep] = version;
107
+ for (const [dep, version] of Object.entries(templateDevDeps)) userDevDeps[dep] = version;
108
+ fs.writeFileSync(userPkgPath, `${JSON.stringify(userPkg, null, 2)}\n`);
109
+ }
110
+ function deleteObsoleteFiles(root, result) {
111
+ for (const file of ["app/vite-env.d.ts"]) {
112
+ const filePath = path.join(root, file);
113
+ if (fs.existsSync(filePath)) {
114
+ fs.unlinkSync(filePath);
115
+ result.deleted.push(file);
116
+ }
117
+ }
102
118
  }
103
119
  function upgradeProject(root) {
104
- const templateDir = path.join(templatesRoot, "minimal");
105
- const cliVersion = getCliVersion();
106
- const result = { updated: [], deleted: [], skipped: [] };
107
- const skeletonFiles = [
108
- "app/entry.client.tsx",
109
- "app/entry.server.tsx",
110
- "app/root.tsx",
111
- "tsconfig.json"
112
- ];
113
- for (const file of skeletonFiles) {
114
- const src = path.join(templateDir, file);
115
- const dest = path.join(root, file);
116
- if (fs.existsSync(src)) {
117
- fs.mkdirSync(path.dirname(dest), { recursive: true });
118
- fs.copyFileSync(src, dest);
119
- result.updated.push(file);
120
- } else {
121
- result.skipped.push(file);
122
- }
123
- }
124
- const userPkgPath = path.join(root, "package.json");
125
- const templatePkgPath = path.join(templateDir, "package.json");
126
- const userPkg = JSON.parse(fs.readFileSync(userPkgPath, "utf-8"));
127
- const templatePkg = JSON.parse(fs.readFileSync(templatePkgPath, "utf-8"));
128
- if (userPkg.dependencies) {
129
- userPkg.dependencies.ardo = `^${cliVersion}`;
130
- }
131
- for (const [dep, version] of Object.entries(
132
- templatePkg.dependencies || {}
133
- )) {
134
- if (dep === "ardo") continue;
135
- if (!userPkg.dependencies?.[dep]) {
136
- userPkg.dependencies = userPkg.dependencies || {};
137
- userPkg.dependencies[dep] = version;
138
- }
139
- }
140
- for (const [dep, version] of Object.entries(
141
- templatePkg.devDependencies || {}
142
- )) {
143
- userPkg.devDependencies = userPkg.devDependencies || {};
144
- userPkg.devDependencies[dep] = version;
145
- }
146
- fs.writeFileSync(userPkgPath, JSON.stringify(userPkg, null, 2) + "\n");
147
- result.updated.push("package.json");
148
- const obsoleteFiles = ["app/vite-env.d.ts"];
149
- for (const file of obsoleteFiles) {
150
- const filePath = path.join(root, file);
151
- if (fs.existsSync(filePath)) {
152
- fs.unlinkSync(filePath);
153
- result.deleted.push(file);
154
- }
155
- }
156
- return result;
120
+ const templateDir = path.join(templatesRoot, "minimal");
121
+ const result = {
122
+ updated: [],
123
+ deleted: [],
124
+ skipped: []
125
+ };
126
+ copySkeletonFiles(root, templateDir, result);
127
+ mergePackageJson(root, templateDir, getCliVersion());
128
+ result.updated.push("package.json");
129
+ deleteObsoleteFiles(root, result);
130
+ return result;
131
+ }
132
+ function ensureStringRecord(object, key) {
133
+ const existing = toStringRecord(object[key]);
134
+ if (existing !== void 0) {
135
+ object[key] = existing;
136
+ return existing;
137
+ }
138
+ const created = {};
139
+ object[key] = created;
140
+ return created;
141
+ }
142
+ function readJsonObject(filePath) {
143
+ try {
144
+ const raw = fs.readFileSync(filePath, "utf8");
145
+ const parsed = JSON.parse(raw);
146
+ return isJsonObject(parsed) ? parsed : void 0;
147
+ } catch {
148
+ return;
149
+ }
150
+ }
151
+ function isJsonObject(value) {
152
+ return typeof value === "object" && value !== null;
157
153
  }
158
-
159
- // src/index.ts
160
- var defaultTargetDir = "my-docs";
161
- var onCancel = () => {
162
- throw new Error(red("\u2716") + " Operation cancelled");
154
+ function toStringRecord(value) {
155
+ if (!isJsonObject(value)) return;
156
+ const entries = Object.entries(value);
157
+ const result = {};
158
+ for (const [key, entryValue] of entries) if (typeof entryValue === "string") result[key] = entryValue;
159
+ return result;
160
+ }
161
+ //#endregion
162
+ //#region src/index.ts
163
+ const defaultTargetDir = "my-docs";
164
+ const onCancel = () => {
165
+ throw new Error(`${red("✖")} Operation cancelled`);
163
166
  };
167
+ async function promptProjectName() {
168
+ const projectName = (await prompts({
169
+ type: "text",
170
+ name: "projectName",
171
+ message: reset("Project name:"),
172
+ initial: defaultTargetDir,
173
+ validate: (value) => {
174
+ const name = value.trim();
175
+ if (!name) return "Project name is required";
176
+ if (/^[.-]/.test(name)) return "Project name cannot start with a dot or hyphen";
177
+ if (!/^[\da-z-]+$/.test(name)) return "Project name may only contain lowercase letters, digits, and hyphens";
178
+ return true;
179
+ }
180
+ }, { onCancel })).projectName;
181
+ return formatTargetDir(typeof projectName === "string" ? projectName : defaultTargetDir) ?? defaultTargetDir;
182
+ }
183
+ async function runUpgradeFlow(root) {
184
+ if ((await prompts({
185
+ type: "select",
186
+ name: "action",
187
+ message: `Existing Ardo project detected. Upgrade to v${getCliVersion()}?`,
188
+ choices: [{
189
+ title: "Upgrade framework files",
190
+ value: "upgrade"
191
+ }, {
192
+ title: "Cancel",
193
+ value: "cancel"
194
+ }]
195
+ }, { onCancel })).action === "cancel") throw new Error(`${red("✖")} Operation cancelled`);
196
+ console.log(`\n ${cyan("Upgrading project in")} ${root}...\n`);
197
+ const result = upgradeProject(root);
198
+ for (const file of result.updated) console.log(` ${green("●")} ${file}`);
199
+ for (const file of result.deleted) console.log(` ${yellow("●")} ${file} ${dim("(removed)")}`);
200
+ for (const file of result.skipped) console.log(` ${dim("○")} ${file} ${dim("(not found, skipped)")}`);
201
+ console.log(`\n ${green("Done!")} Now run:\n`);
202
+ console.log(` ${blue("pnpm install")}\n`);
203
+ }
204
+ function getNewProjectPrompts(targetDir, root, argTemplate) {
205
+ return [
206
+ {
207
+ type: () => !fs.existsSync(root) || isEmpty(root) ? null : "select",
208
+ name: "overwrite",
209
+ message: () => `${targetDir === "." ? "Current directory" : `Target directory "${targetDir}"`} is not empty. How would you like to proceed?`,
210
+ choices: [
211
+ {
212
+ title: "Remove existing files and continue",
213
+ value: "yes"
214
+ },
215
+ {
216
+ title: "Cancel operation",
217
+ value: "no"
218
+ },
219
+ {
220
+ title: "Ignore files and continue",
221
+ value: "ignore"
222
+ }
223
+ ]
224
+ },
225
+ {
226
+ type: (_, { overwrite }) => {
227
+ if (overwrite === "no") throw new Error(`${red("✖")} Operation cancelled`);
228
+ return null;
229
+ },
230
+ name: "overwriteChecker"
231
+ },
232
+ {
233
+ type: argTemplate !== void 0 && isValidTemplate(argTemplate) ? null : "select",
234
+ name: "template",
235
+ message: reset("Select a template:"),
236
+ choices: templates.map((t) => ({
237
+ title: `${t.display} ${yellow(`- ${t.description}`)}`,
238
+ value: t.name
239
+ }))
240
+ },
241
+ {
242
+ type: "text",
243
+ name: "siteTitle",
244
+ message: reset("Site title:"),
245
+ initial: "My Documentation"
246
+ },
247
+ {
248
+ type: "select",
249
+ name: "docType",
250
+ message: reset("What are you documenting?"),
251
+ choices: [{
252
+ title: `Code library ${dim("- includes TypeDoc API generation")}`,
253
+ value: "library"
254
+ }, {
255
+ title: `General documentation ${dim("- guides, manuals, etc.")}`,
256
+ value: "general"
257
+ }]
258
+ },
259
+ {
260
+ type: "select",
261
+ name: "githubPages",
262
+ message: reset("Deploy to GitHub Pages?"),
263
+ choices: [{
264
+ title: `Yes ${dim("- auto-detects base path from git remote")}`,
265
+ value: true
266
+ }, {
267
+ title: `No ${dim("- deploy to other platforms (Netlify, Vercel, etc.)")}`,
268
+ value: false
269
+ }]
270
+ }
271
+ ];
272
+ }
273
+ async function runNewProjectFlow(targetDir, root, argTemplate) {
274
+ const response = await prompts(getNewProjectPrompts(targetDir, root, argTemplate), { onCancel });
275
+ const template = response.template ?? argTemplate ?? "minimal";
276
+ if (response.overwrite === "yes") emptyDir(root);
277
+ else if (!fs.existsSync(root)) fs.mkdirSync(root, { recursive: true });
278
+ console.log(`\n ${cyan("Scaffolding project in")} ${root}...\n`);
279
+ createProjectStructure(root, template, {
280
+ siteTitle: response.siteTitle,
281
+ projectName: targetDir,
282
+ typedoc: response.docType === "library",
283
+ githubPages: response.githubPages ?? true,
284
+ description: detectProjectDescription(root) ?? "Built with Ardo"
285
+ });
286
+ console.log(` ${green("Done!")} Now run:\n`);
287
+ if (root !== process.cwd()) console.log(` ${blue("cd")} ${targetDir}`);
288
+ console.log(` ${blue("pnpm install")}`);
289
+ console.log(` ${blue("pnpm dev")}\n`);
290
+ }
164
291
  async function main() {
165
- console.log();
166
- console.log(` ${cyan("\u25C6")} ${green("create-ardo")}`);
167
- console.log();
168
- const argTargetDir = process.argv[2];
169
- const argTemplate = process.argv[3];
170
- let targetDir = argTargetDir || defaultTargetDir;
171
- if (!argTargetDir) {
172
- const { projectName } = await prompts(
173
- {
174
- type: "text",
175
- name: "projectName",
176
- message: reset("Project name:"),
177
- initial: defaultTargetDir,
178
- validate: (value) => {
179
- const name = value.trim();
180
- if (!name) return "Project name is required";
181
- if (/^[.-]/.test(name)) return "Project name cannot start with a dot or hyphen";
182
- if (!/^[a-z0-9-]+$/.test(name))
183
- return "Project name may only contain lowercase letters, digits, and hyphens";
184
- return true;
185
- }
186
- },
187
- { onCancel }
188
- );
189
- targetDir = formatTargetDir(projectName) || defaultTargetDir;
190
- }
191
- const root = path2.join(process.cwd(), targetDir);
192
- if (fs2.existsSync(root) && !isEmpty(root) && isArdoProject(root)) {
193
- const cliVersion = getCliVersion();
194
- const { action } = await prompts(
195
- {
196
- type: "select",
197
- name: "action",
198
- message: `Existing Ardo project detected. Upgrade to v${cliVersion}?`,
199
- choices: [
200
- { title: "Upgrade framework files", value: "upgrade" },
201
- { title: "Cancel", value: "cancel" }
202
- ]
203
- },
204
- { onCancel }
205
- );
206
- if (action === "cancel") {
207
- throw new Error(red("\u2716") + " Operation cancelled");
208
- }
209
- console.log();
210
- console.log(` ${cyan("Upgrading project in")} ${root}...`);
211
- console.log();
212
- const result = upgradeProject(root);
213
- for (const file of result.updated) {
214
- console.log(` ${green("\u25CF")} ${file}`);
215
- }
216
- for (const file of result.deleted) {
217
- console.log(` ${yellow("\u25CF")} ${file} ${dim("(removed)")}`);
218
- }
219
- for (const file of result.skipped) {
220
- console.log(` ${dim("\u25CB")} ${file} ${dim("(not found, skipped)")}`);
221
- }
222
- console.log();
223
- console.log(` ${green("Done!")} Now run:`);
224
- console.log();
225
- console.log(` ${blue("pnpm install")}`);
226
- console.log();
227
- return;
228
- }
229
- let template = argTemplate;
230
- const response = await prompts(
231
- [
232
- {
233
- type: () => !fs2.existsSync(root) || isEmpty(root) ? null : "select",
234
- name: "overwrite",
235
- message: () => `${targetDir === "." ? "Current directory" : `Target directory "${targetDir}"`} is not empty. How would you like to proceed?`,
236
- choices: [
237
- { title: "Remove existing files and continue", value: "yes" },
238
- { title: "Cancel operation", value: "no" },
239
- { title: "Ignore files and continue", value: "ignore" }
240
- ]
241
- },
242
- {
243
- type: (_, { overwrite: overwrite2 }) => {
244
- if (overwrite2 === "no") {
245
- throw new Error(red("\u2716") + " Operation cancelled");
246
- }
247
- return null;
248
- },
249
- name: "overwriteChecker"
250
- },
251
- {
252
- type: argTemplate && isValidTemplate(argTemplate) ? null : "select",
253
- name: "template",
254
- message: reset("Select a template:"),
255
- choices: templates.map((t) => ({
256
- title: `${t.display} ${yellow(`- ${t.description}`)}`,
257
- value: t.name
258
- }))
259
- },
260
- {
261
- type: "text",
262
- name: "siteTitle",
263
- message: reset("Site title:"),
264
- initial: "My Documentation"
265
- },
266
- {
267
- type: "select",
268
- name: "docType",
269
- message: reset("What are you documenting?"),
270
- choices: [
271
- {
272
- title: `Code library ${dim("- includes TypeDoc API generation")}`,
273
- value: "library"
274
- },
275
- {
276
- title: `General documentation ${dim("- guides, manuals, etc.")}`,
277
- value: "general"
278
- }
279
- ]
280
- },
281
- {
282
- type: "select",
283
- name: "githubPages",
284
- message: reset("Deploy to GitHub Pages?"),
285
- choices: [
286
- {
287
- title: `Yes ${dim("- auto-detects base path from git remote")}`,
288
- value: true
289
- },
290
- {
291
- title: `No ${dim("- deploy to other platforms (Netlify, Vercel, etc.)")}`,
292
- value: false
293
- }
294
- ]
295
- }
296
- ],
297
- { onCancel }
298
- );
299
- const { overwrite, template: templateChoice, siteTitle, docType, githubPages } = response;
300
- template = templateChoice || template || "minimal";
301
- if (overwrite === "yes") {
302
- emptyDir(root);
303
- } else if (!fs2.existsSync(root)) {
304
- fs2.mkdirSync(root, { recursive: true });
305
- }
306
- console.log();
307
- console.log(` ${cyan("Scaffolding project in")} ${root}...`);
308
- console.log();
309
- const description = detectProjectDescription(root) || "Built with Ardo";
310
- createProjectStructure(root, template, {
311
- siteTitle,
312
- projectName: targetDir,
313
- typedoc: docType === "library",
314
- githubPages: githubPages ?? true,
315
- description
316
- });
317
- console.log(` ${green("Done!")} Now run:`);
318
- console.log();
319
- if (root !== process.cwd()) {
320
- console.log(` ${blue("cd")} ${targetDir}`);
321
- }
322
- console.log(` ${blue("pnpm install")}`);
323
- console.log(` ${blue("pnpm dev")}`);
324
- console.log();
292
+ console.log(`\n ${cyan("◆")} ${green("create-ardo")}\n`);
293
+ const argTargetDir = process.argv.length > 2 ? process.argv[2] : void 0;
294
+ const argTemplate = process.argv.length > 3 ? process.argv[3] : void 0;
295
+ const targetDir = argTargetDir ?? await promptProjectName();
296
+ const root = path.join(process.cwd(), targetDir);
297
+ if (fs.existsSync(root) && !isEmpty(root) && isArdoProject(root)) {
298
+ await runUpgradeFlow(root);
299
+ return;
300
+ }
301
+ await runNewProjectFlow(targetDir, root, argTemplate);
302
+ }
303
+ try {
304
+ await main();
305
+ } catch (error) {
306
+ const errorMessage = error instanceof Error ? error.message : String(error);
307
+ console.error(errorMessage);
308
+ process.exit(1);
325
309
  }
326
- main().catch((e) => {
327
- console.error(e.message);
328
- process.exit(1);
329
- });
310
+ //#endregion
311
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-ardo",
3
- "version": "3.1.0",
3
+ "version": "3.2.0",
4
4
  "description": "Scaffolding tool for Ardo documentation projects",
5
5
  "type": "module",
6
6
  "bin": {
@@ -33,17 +33,17 @@
33
33
  "kolorist": "^1.8.0"
34
34
  },
35
35
  "devDependencies": {
36
- "@types/node": "^25.3.3",
36
+ "@types/node": "^25.5.0",
37
37
  "@types/prompts": "^2.4.9",
38
- "tsup": "^8.5.1",
39
- "typescript": "^5.9.3"
38
+ "tsdown": "^0.21.7",
39
+ "typescript": "^6.0.2"
40
40
  },
41
41
  "engines": {
42
42
  "node": ">=18.0.0"
43
43
  },
44
44
  "scripts": {
45
- "build": "tsup",
46
- "dev": "tsup --watch",
45
+ "build": "tsdown",
46
+ "dev": "tsdown --watch",
47
47
  "typecheck": "tsc --noEmit"
48
48
  }
49
49
  }
@@ -0,0 +1,3 @@
1
+ @layer theme, base, components, utilities;
2
+ @import "tailwindcss/theme.css" layer(theme);
3
+ @import "tailwindcss/utilities.css" layer(utilities);
@@ -1,13 +1,14 @@
1
- import { RootLayout, ArdoRoot } from "ardo/ui"
1
+ import { ArdoRootLayout, ArdoRoot } from "ardo/ui"
2
2
  import config from "virtual:ardo/config"
3
3
  import sidebar from "virtual:ardo/sidebar"
4
4
  import type { MetaFunction } from "react-router"
5
+ import "./app.css"
5
6
  import "ardo/ui/styles.css"
6
7
 
7
8
  export const meta: MetaFunction = () => [{ title: config.title }]
8
9
 
9
10
  export function Layout({ children }: { children: React.ReactNode }) {
10
- return <RootLayout>{children}</RootLayout>
11
+ return <ArdoRootLayout>{children}</ArdoRootLayout>
11
12
  }
12
13
 
13
14
  export default function Root() {
@@ -1,4 +1,4 @@
1
- import { Hero, Features } from "ardo/ui"
1
+ import { ArdoHero, ArdoFeatures, ArdoFeatureCard } from "ardo/ui"
2
2
  import { Zap, Sparkles, Palette, ArrowRight, Github } from "ardo/icons"
3
3
  import type { MetaFunction } from "react-router"
4
4
 
@@ -9,7 +9,7 @@ export const meta: MetaFunction = () => [
9
9
  export default function HomePage() {
10
10
  return (
11
11
  <>
12
- <Hero
12
+ <ArdoHero
13
13
  name="{{SITE_TITLE}}"
14
14
  text="Documentation Made Simple"
15
15
  tagline="Focus on your content, not configuration"
@@ -28,25 +28,17 @@ export default function HomePage() {
28
28
  },
29
29
  ]}
30
30
  />
31
- <Features
32
- items={[
33
- {
34
- title: "Fast",
35
- icon: <Zap size={28} strokeWidth={1.5} />,
36
- details: "Lightning fast builds with Vite",
37
- },
38
- {
39
- title: "Simple",
40
- icon: <Sparkles size={28} strokeWidth={1.5} />,
41
- details: "Easy to set up and use",
42
- },
43
- {
44
- title: "Flexible",
45
- icon: <Palette size={28} strokeWidth={1.5} />,
46
- details: "Fully customizable theme",
47
- },
48
- ]}
49
- />
31
+ <ArdoFeatures>
32
+ <ArdoFeatureCard title="Fast" icon={<Zap size={28} strokeWidth={1.5} />}>
33
+ Lightning fast builds with Vite
34
+ </ArdoFeatureCard>
35
+ <ArdoFeatureCard title="Simple" icon={<Sparkles size={28} strokeWidth={1.5} />}>
36
+ Easy to set up and use
37
+ </ArdoFeatureCard>
38
+ <ArdoFeatureCard title="Flexible" icon={<Palette size={28} strokeWidth={1.5} />}>
39
+ Fully customizable theme
40
+ </ArdoFeatureCard>
41
+ </ArdoFeatures>
50
42
  </>
51
43
  )
52
44
  }
@@ -10,17 +10,19 @@
10
10
  },
11
11
  "dependencies": {
12
12
  "ardo": "^{{ARDO_VERSION}}",
13
- "isbot": "*",
14
- "lucide-react": "*",
15
- "react": "*",
16
- "react-dom": "*",
17
- "react-router": "*"
13
+ "isbot": "^5.1.36",
14
+ "lucide-react": "^0.577.0",
15
+ "react": "^19.2.4",
16
+ "react-dom": "^19.2.4",
17
+ "react-router": "^7.13.2"
18
18
  },
19
19
  "devDependencies": {
20
- "@react-router/dev": "*",
20
+ "@react-router/dev": "^7.13.2",
21
+ "@tailwindcss/vite": "^4.2.2",
21
22
  "@types/react": "^19.2.14",
22
23
  "@types/react-dom": "^19.2.3",
23
- "typescript": "^5.9.3",
24
- "vite": "^8.0.0-beta.0"
24
+ "tailwindcss": "^4.2.2",
25
+ "typescript": "^6.0.2",
26
+ "vite": "^8.0.3"
25
27
  }
26
28
  }
@@ -1,8 +1,10 @@
1
1
  import { defineConfig } from 'vite'
2
+ import tailwindcss from '@tailwindcss/vite'
2
3
  import { ardo } from 'ardo/vite'
3
4
 
4
5
  export default defineConfig({
5
6
  plugins: [
7
+ tailwindcss(),
6
8
  ardo({
7
9
  title: '{{SITE_TITLE}}',
8
10
  description: '{{DESCRIPTION}}',
@@ -10,31 +12,6 @@ export default defineConfig({
10
12
  {{TYPEDOC_CONFIG}}
11
13
 
12
14
  {{GITHUB_PAGES_CONFIG}}
13
-
14
- themeConfig: {
15
- siteTitle: '{{SITE_TITLE}}',
16
-
17
- nav: [
18
- { text: 'Guide', link: '/guide/getting-started' },
19
- {{TYPEDOC_NAV}}
20
- ],
21
-
22
- sidebar: [
23
- {
24
- text: 'Guide',
25
- items: [{ text: 'Getting Started', link: '/guide/getting-started' }],
26
- },
27
- {{TYPEDOC_SIDEBAR}}
28
- ],
29
-
30
- footer: {
31
- message: 'Released under the MIT License.',
32
- },
33
-
34
- search: {
35
- enabled: true,
36
- },
37
- },
38
15
  }),
39
16
  ],
40
17
  })