@yoamigo.com/cli 0.1.19 → 0.1.20
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/README.md +26 -1
- package/dist/index.js +190 -180
- package/package.json +1 -9
- package/templates/starter/src/App.tsx +12 -11
- package/templates/starter/src/lib/router/routes.tsx +103 -0
- package/templates/starter/src/pages/{AboutPage.tsx → about.tsx} +1 -1
- package/templates/starter/src/pages/{ContactPage.tsx → contact.tsx} +1 -1
- package/templates/starter/src/pages/{HomePage.tsx → index.tsx} +1 -1
- package/templates/starter/src/routes.ts +1 -0
- package/templates/starter/tsconfig.json +1 -0
package/README.md
CHANGED
|
@@ -25,7 +25,7 @@ yoamigo dev
|
|
|
25
25
|
yoamigo preview
|
|
26
26
|
|
|
27
27
|
# 5. Submit for review
|
|
28
|
-
yoamigo deploy
|
|
28
|
+
yoamigo deploy 1.0.0
|
|
29
29
|
```
|
|
30
30
|
|
|
31
31
|
## Commands
|
|
@@ -76,6 +76,31 @@ yoamigo deploy
|
|
|
76
76
|
yoamigo deploy --submit
|
|
77
77
|
```
|
|
78
78
|
|
|
79
|
+
## Template Structure
|
|
80
|
+
|
|
81
|
+
Templates use file-based routing with auto-discovery. Pages must follow these conventions for the builder's page selector to work:
|
|
82
|
+
|
|
83
|
+
```
|
|
84
|
+
my-template/
|
|
85
|
+
├── src/
|
|
86
|
+
│ ├── App.tsx # Root component with ContentStoreProvider
|
|
87
|
+
│ ├── routes.ts # Re-exports getAvailablePages() and AppRoutes
|
|
88
|
+
│ ├── lib/router/routes.tsx # Auto-discovery with import.meta.glob
|
|
89
|
+
│ ├── pages/ # File-based routing
|
|
90
|
+
│ │ ├── index.tsx # Home page (/)
|
|
91
|
+
│ │ ├── about.tsx # About page (/about)
|
|
92
|
+
│ │ └── contact.tsx # Contact page (/contact)
|
|
93
|
+
│ └── components/
|
|
94
|
+
├── public/
|
|
95
|
+
└── package.json
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Page Requirements
|
|
99
|
+
|
|
100
|
+
- **File naming**: Use `index.tsx`, `about.tsx` (not `HomePage.tsx`, `AboutPage.tsx`)
|
|
101
|
+
- **Default exports**: Pages must use `export default function PageName()`
|
|
102
|
+
- **Auto-discovery**: Pages are discovered via `import.meta.glob` in `routes.tsx`
|
|
103
|
+
|
|
79
104
|
## Documentation
|
|
80
105
|
|
|
81
106
|
For full documentation, visit [yoamigo.com/developers](https://yoamigo.com/developers).
|
package/dist/index.js
CHANGED
|
@@ -5,6 +5,13 @@ import "dotenv/config";
|
|
|
5
5
|
import { Command as Command5 } from "commander";
|
|
6
6
|
import { createRequire } from "module";
|
|
7
7
|
|
|
8
|
+
// src/commands/deploy.ts
|
|
9
|
+
import { Command as Command2 } from "commander";
|
|
10
|
+
import chalk2 from "chalk";
|
|
11
|
+
import ora2 from "ora";
|
|
12
|
+
import fs2 from "fs-extra";
|
|
13
|
+
import path2 from "path";
|
|
14
|
+
|
|
8
15
|
// src/commands/login.ts
|
|
9
16
|
import { Command } from "commander";
|
|
10
17
|
import chalk from "chalk";
|
|
@@ -20,8 +27,8 @@ var API_URLS = {
|
|
|
20
27
|
staging: "https://api-staging.yoamigo.com"
|
|
21
28
|
};
|
|
22
29
|
var APP_URLS = {
|
|
23
|
-
production: "https://
|
|
24
|
-
staging: "https://
|
|
30
|
+
production: "https://www.yoamigo.com/dev",
|
|
31
|
+
staging: "https://staging.yoamigo.com/dev"
|
|
25
32
|
};
|
|
26
33
|
function isStaging() {
|
|
27
34
|
return process.env.YOAMIGO_ENVIRONMENT === "staging";
|
|
@@ -106,146 +113,7 @@ var loginCommand = new Command("login").description("Authenticate with your API
|
|
|
106
113
|
}
|
|
107
114
|
});
|
|
108
115
|
|
|
109
|
-
// src/commands/init.ts
|
|
110
|
-
import { Command as Command2 } from "commander";
|
|
111
|
-
import chalk2 from "chalk";
|
|
112
|
-
import ora2 from "ora";
|
|
113
|
-
import fs2 from "fs-extra";
|
|
114
|
-
import path2 from "path";
|
|
115
|
-
import { fileURLToPath } from "url";
|
|
116
|
-
import { input as input2 } from "@inquirer/prompts";
|
|
117
|
-
import { spawn } from "child_process";
|
|
118
|
-
var __dirname = path2.dirname(fileURLToPath(import.meta.url));
|
|
119
|
-
var TEMPLATES_DIR = path2.resolve(__dirname, "../templates");
|
|
120
|
-
var initCommand = new Command2("init").description("Create a new template project").argument("[name]", "Project name").option("-t, --template <template>", "Template to use", "starter").action(async (name, options) => {
|
|
121
|
-
let projectName = name;
|
|
122
|
-
if (!projectName) {
|
|
123
|
-
try {
|
|
124
|
-
projectName = await input2({
|
|
125
|
-
message: "Project name:",
|
|
126
|
-
default: "my-template",
|
|
127
|
-
validate: (value) => {
|
|
128
|
-
if (!value || value.trim().length === 0) {
|
|
129
|
-
return "Project name is required";
|
|
130
|
-
}
|
|
131
|
-
if (!/^[a-z0-9-]+$/.test(value)) {
|
|
132
|
-
return "Project name must be lowercase letters, numbers, and hyphens only";
|
|
133
|
-
}
|
|
134
|
-
return true;
|
|
135
|
-
}
|
|
136
|
-
});
|
|
137
|
-
} catch {
|
|
138
|
-
console.log();
|
|
139
|
-
process.exit(0);
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
const targetDir = path2.resolve(process.cwd(), projectName);
|
|
143
|
-
const templateDir = path2.join(TEMPLATES_DIR, options.template);
|
|
144
|
-
if (await fs2.pathExists(targetDir)) {
|
|
145
|
-
console.error(chalk2.red(`Error: Directory ${projectName} already exists`));
|
|
146
|
-
process.exit(1);
|
|
147
|
-
}
|
|
148
|
-
if (!await fs2.pathExists(templateDir)) {
|
|
149
|
-
console.error(chalk2.red(`Error: Template "${options.template}" not found`));
|
|
150
|
-
process.exit(1);
|
|
151
|
-
}
|
|
152
|
-
const spinner = ora2(`Creating ${projectName}...`).start();
|
|
153
|
-
try {
|
|
154
|
-
await fs2.copy(templateDir, targetDir);
|
|
155
|
-
const packageJsonPath = path2.join(targetDir, "package.json");
|
|
156
|
-
if (await fs2.pathExists(packageJsonPath)) {
|
|
157
|
-
const packageJson = await fs2.readJson(packageJsonPath);
|
|
158
|
-
packageJson.name = projectName;
|
|
159
|
-
await fs2.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
|
160
|
-
}
|
|
161
|
-
spinner.succeed(`Created ${projectName}`);
|
|
162
|
-
console.log();
|
|
163
|
-
const installSpinner = ora2("Installing dependencies...").start();
|
|
164
|
-
await new Promise((resolve, reject) => {
|
|
165
|
-
const child = spawn("npm", ["install"], {
|
|
166
|
-
cwd: targetDir,
|
|
167
|
-
stdio: "pipe"
|
|
168
|
-
});
|
|
169
|
-
child.on("error", reject);
|
|
170
|
-
child.on("close", (code) => {
|
|
171
|
-
if (code === 0) {
|
|
172
|
-
resolve();
|
|
173
|
-
} else {
|
|
174
|
-
reject(new Error(`npm install failed with code ${code}`));
|
|
175
|
-
}
|
|
176
|
-
});
|
|
177
|
-
});
|
|
178
|
-
installSpinner.succeed("Installed dependencies");
|
|
179
|
-
console.log();
|
|
180
|
-
console.log(chalk2.green("Success!") + ` Created ${projectName} at ${targetDir}`);
|
|
181
|
-
console.log();
|
|
182
|
-
console.log("Next steps:");
|
|
183
|
-
console.log(chalk2.cyan(` cd ${projectName}`));
|
|
184
|
-
console.log(chalk2.cyan(" yoamigo dev # Start development server"));
|
|
185
|
-
console.log(chalk2.cyan(" yoamigo preview # Preview in builder"));
|
|
186
|
-
console.log();
|
|
187
|
-
} catch (error) {
|
|
188
|
-
spinner.fail("Failed to create project");
|
|
189
|
-
console.error(chalk2.red(error instanceof Error ? error.message : "Unknown error"));
|
|
190
|
-
process.exit(1);
|
|
191
|
-
}
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
// src/commands/dev.ts
|
|
195
|
-
import { Command as Command3 } from "commander";
|
|
196
|
-
import chalk3 from "chalk";
|
|
197
|
-
import { spawn as spawn2 } from "child_process";
|
|
198
|
-
import fs3 from "fs-extra";
|
|
199
|
-
import path3 from "path";
|
|
200
|
-
var devCommand = new Command3("dev").description("Start development server").option("-p, --port <port>", "Port to run on", "5173").option("--edit", "Enable edit mode").action(async (options) => {
|
|
201
|
-
const packageJsonPath = path3.join(process.cwd(), "package.json");
|
|
202
|
-
if (!await fs3.pathExists(packageJsonPath)) {
|
|
203
|
-
console.error(chalk3.red("Error: No package.json found. Are you in a template project?"));
|
|
204
|
-
console.log(chalk3.dim("Run `yoamigo init` to create a new template project."));
|
|
205
|
-
process.exit(1);
|
|
206
|
-
}
|
|
207
|
-
const viteConfigPath = path3.join(process.cwd(), "vite.config.ts");
|
|
208
|
-
if (!await fs3.pathExists(viteConfigPath)) {
|
|
209
|
-
console.error(chalk3.red("Error: No vite.config.ts found. Are you in a template project?"));
|
|
210
|
-
process.exit(1);
|
|
211
|
-
}
|
|
212
|
-
console.log(chalk3.cyan("Starting development server..."));
|
|
213
|
-
console.log();
|
|
214
|
-
const packageManager = await detectPackageManager();
|
|
215
|
-
const env = {
|
|
216
|
-
...process.env,
|
|
217
|
-
...options.edit ? { YA_EDIT_MODE: "true" } : {}
|
|
218
|
-
};
|
|
219
|
-
const args = packageManager === "npm" ? ["run", "dev"] : ["dev"];
|
|
220
|
-
const child = spawn2(packageManager, args, {
|
|
221
|
-
cwd: process.cwd(),
|
|
222
|
-
stdio: "inherit",
|
|
223
|
-
env
|
|
224
|
-
});
|
|
225
|
-
child.on("error", (error) => {
|
|
226
|
-
console.error(chalk3.red(`Failed to start dev server: ${error.message}`));
|
|
227
|
-
process.exit(1);
|
|
228
|
-
});
|
|
229
|
-
child.on("close", (code) => {
|
|
230
|
-
process.exit(code ?? 0);
|
|
231
|
-
});
|
|
232
|
-
});
|
|
233
|
-
async function detectPackageManager() {
|
|
234
|
-
if (await fs3.pathExists(path3.join(process.cwd(), "pnpm-lock.yaml"))) {
|
|
235
|
-
return "pnpm";
|
|
236
|
-
}
|
|
237
|
-
if (await fs3.pathExists(path3.join(process.cwd(), "yarn.lock"))) {
|
|
238
|
-
return "yarn";
|
|
239
|
-
}
|
|
240
|
-
return "npm";
|
|
241
|
-
}
|
|
242
|
-
|
|
243
116
|
// src/commands/deploy.ts
|
|
244
|
-
import { Command as Command4 } from "commander";
|
|
245
|
-
import chalk4 from "chalk";
|
|
246
|
-
import ora3 from "ora";
|
|
247
|
-
import fs4 from "fs-extra";
|
|
248
|
-
import path4 from "path";
|
|
249
117
|
var API_BASE_URL2 = getApiBaseUrl();
|
|
250
118
|
var APP_BASE_URL = getAppBaseUrl();
|
|
251
119
|
var EXCLUDE_PATTERNS = ["node_modules", "dist", ".git", "*.log"];
|
|
@@ -259,31 +127,31 @@ var ROOT_FILES = [
|
|
|
259
127
|
"postcss.config.cjs",
|
|
260
128
|
"tailwind.config.ts"
|
|
261
129
|
];
|
|
262
|
-
var deployCommand = new
|
|
130
|
+
var deployCommand = new Command2("deploy").description("Upload template to YoAmigo").argument("<version>", "Template version (e.g., 1.0.0)").option("-v, --verbose", "Show detailed logging for debugging").action(async (version, options) => {
|
|
263
131
|
const verbose = options.verbose;
|
|
264
132
|
if (verbose) {
|
|
265
|
-
console.log(
|
|
266
|
-
console.log(
|
|
133
|
+
console.log(chalk2.dim(`API URL: ${API_BASE_URL2}`));
|
|
134
|
+
console.log(chalk2.dim(`App URL: ${APP_BASE_URL}`));
|
|
267
135
|
console.log();
|
|
268
136
|
}
|
|
269
137
|
const credentials = await getCredentials();
|
|
270
138
|
if (!credentials) {
|
|
271
|
-
console.error(
|
|
139
|
+
console.error(chalk2.red("Error: Not logged in. Run `yoamigo login` first."));
|
|
272
140
|
process.exit(1);
|
|
273
141
|
}
|
|
274
142
|
const versionRegex = /^\d+\.\d+\.\d+$/;
|
|
275
143
|
if (!versionRegex.test(version)) {
|
|
276
|
-
console.error(
|
|
144
|
+
console.error(chalk2.red("Error: Version must be in semver format (e.g., 1.0.0)"));
|
|
277
145
|
process.exit(1);
|
|
278
146
|
}
|
|
279
|
-
const packageJsonPath =
|
|
280
|
-
if (!await
|
|
281
|
-
console.error(
|
|
147
|
+
const packageJsonPath = path2.join(process.cwd(), "package.json");
|
|
148
|
+
if (!await fs2.pathExists(packageJsonPath)) {
|
|
149
|
+
console.error(chalk2.red("Error: No package.json found. Are you in a template project?"));
|
|
282
150
|
process.exit(1);
|
|
283
151
|
}
|
|
284
|
-
const packageJson = await
|
|
152
|
+
const packageJson = await fs2.readJson(packageJsonPath);
|
|
285
153
|
const templateName = packageJson.name || "unnamed-template";
|
|
286
|
-
const spinner =
|
|
154
|
+
const spinner = ora2("Deploying template...").start();
|
|
287
155
|
try {
|
|
288
156
|
spinner.text = "Collecting files...";
|
|
289
157
|
const files = await collectFiles(process.cwd());
|
|
@@ -299,8 +167,8 @@ var deployCommand = new Command4("deploy").description("Upload template to YoAmi
|
|
|
299
167
|
};
|
|
300
168
|
if (verbose) {
|
|
301
169
|
spinner.stop();
|
|
302
|
-
console.log(
|
|
303
|
-
console.log(
|
|
170
|
+
console.log(chalk2.dim(`POST ${templateUrl}`));
|
|
171
|
+
console.log(chalk2.dim(`Body: ${JSON.stringify(templateBody)}`));
|
|
304
172
|
spinner.start();
|
|
305
173
|
}
|
|
306
174
|
let templateResponse;
|
|
@@ -316,22 +184,22 @@ var deployCommand = new Command4("deploy").description("Upload template to YoAmi
|
|
|
316
184
|
} catch (fetchError) {
|
|
317
185
|
if (verbose) {
|
|
318
186
|
spinner.stop();
|
|
319
|
-
console.error(
|
|
187
|
+
console.error(chalk2.red(`
|
|
320
188
|
Fetch error for ${templateUrl}:`));
|
|
321
|
-
console.error(
|
|
189
|
+
console.error(chalk2.red(fetchError instanceof Error ? fetchError.stack || fetchError.message : String(fetchError)));
|
|
322
190
|
}
|
|
323
191
|
throw new Error(`Network error: ${fetchError instanceof Error ? fetchError.message : "fetch failed"}`);
|
|
324
192
|
}
|
|
325
193
|
if (verbose) {
|
|
326
194
|
spinner.stop();
|
|
327
|
-
console.log(
|
|
195
|
+
console.log(chalk2.dim(`Response status: ${templateResponse.status}`));
|
|
328
196
|
spinner.start();
|
|
329
197
|
}
|
|
330
198
|
if (!templateResponse.ok) {
|
|
331
199
|
const errorText = await templateResponse.text();
|
|
332
200
|
if (verbose) {
|
|
333
201
|
spinner.stop();
|
|
334
|
-
console.error(
|
|
202
|
+
console.error(chalk2.red(`
|
|
335
203
|
Response body: ${errorText}`));
|
|
336
204
|
}
|
|
337
205
|
let errorMessage = "Failed to create template";
|
|
@@ -345,16 +213,22 @@ Response body: ${errorText}`));
|
|
|
345
213
|
}
|
|
346
214
|
const templateData = await templateResponse.json();
|
|
347
215
|
const templateId = templateData.result?.data?.id;
|
|
216
|
+
const versionId = templateData.result?.data?.versionId;
|
|
348
217
|
if (!templateId) {
|
|
349
218
|
throw new Error("Failed to get template ID");
|
|
350
219
|
}
|
|
220
|
+
if (verbose && versionId) {
|
|
221
|
+
spinner.stop();
|
|
222
|
+
console.log(chalk2.dim(`Version ID: ${versionId}`));
|
|
223
|
+
spinner.start();
|
|
224
|
+
}
|
|
351
225
|
const uploadUrl = `${API_BASE_URL2}/api/trpc/developer.uploadTemplateFile`;
|
|
352
226
|
for (const file of files) {
|
|
353
|
-
const relativePath =
|
|
354
|
-
const content = await
|
|
227
|
+
const relativePath = path2.relative(process.cwd(), file);
|
|
228
|
+
const content = await fs2.readFile(file, "utf-8");
|
|
355
229
|
if (verbose) {
|
|
356
230
|
spinner.stop();
|
|
357
|
-
console.log(
|
|
231
|
+
console.log(chalk2.dim(`Uploading: ${relativePath}`));
|
|
358
232
|
spinner.start();
|
|
359
233
|
}
|
|
360
234
|
try {
|
|
@@ -366,6 +240,8 @@ Response body: ${errorText}`));
|
|
|
366
240
|
},
|
|
367
241
|
body: JSON.stringify({
|
|
368
242
|
templateId,
|
|
243
|
+
versionId,
|
|
244
|
+
// Include versionId for version-specific storage path
|
|
369
245
|
path: relativePath,
|
|
370
246
|
content
|
|
371
247
|
})
|
|
@@ -374,56 +250,56 @@ Response body: ${errorText}`));
|
|
|
374
250
|
const errorText = await uploadResponse.text();
|
|
375
251
|
if (verbose) {
|
|
376
252
|
spinner.stop();
|
|
377
|
-
console.error(
|
|
378
|
-
console.error(
|
|
253
|
+
console.error(chalk2.yellow(`Failed to upload ${relativePath}: ${uploadResponse.status}`));
|
|
254
|
+
console.error(chalk2.yellow(`Response: ${errorText}`));
|
|
379
255
|
spinner.start();
|
|
380
256
|
} else {
|
|
381
|
-
console.warn(
|
|
257
|
+
console.warn(chalk2.yellow(`Warning: Failed to upload ${relativePath}`));
|
|
382
258
|
}
|
|
383
259
|
}
|
|
384
260
|
} catch (uploadError) {
|
|
385
261
|
if (verbose) {
|
|
386
262
|
spinner.stop();
|
|
387
|
-
console.error(
|
|
388
|
-
console.error(
|
|
263
|
+
console.error(chalk2.yellow(`Network error uploading ${relativePath}:`));
|
|
264
|
+
console.error(chalk2.yellow(uploadError instanceof Error ? uploadError.message : String(uploadError)));
|
|
389
265
|
spinner.start();
|
|
390
266
|
} else {
|
|
391
|
-
console.warn(
|
|
267
|
+
console.warn(chalk2.yellow(`Warning: Failed to upload ${relativePath}`));
|
|
392
268
|
}
|
|
393
269
|
}
|
|
394
270
|
}
|
|
395
271
|
spinner.succeed(`Template v${version} deployed successfully!`);
|
|
396
272
|
console.log();
|
|
397
|
-
console.log(
|
|
398
|
-
console.log(
|
|
273
|
+
console.log(chalk2.green("Preview URL:"));
|
|
274
|
+
console.log(chalk2.cyan(` ${APP_BASE_URL}/preview/${templateId}`));
|
|
399
275
|
console.log();
|
|
400
|
-
console.log(
|
|
401
|
-
console.log(
|
|
276
|
+
console.log(chalk2.dim("To submit for review, visit your dashboard:"));
|
|
277
|
+
console.log(chalk2.dim(` ${APP_BASE_URL}/dashboard`));
|
|
402
278
|
} catch (error) {
|
|
403
279
|
spinner.fail("Failed to deploy template");
|
|
404
280
|
if (verbose && error instanceof Error && error.stack) {
|
|
405
|
-
console.error(
|
|
281
|
+
console.error(chalk2.red(error.stack));
|
|
406
282
|
} else {
|
|
407
|
-
console.error(
|
|
283
|
+
console.error(chalk2.red(error instanceof Error ? error.message : "Unknown error"));
|
|
408
284
|
}
|
|
409
285
|
process.exit(1);
|
|
410
286
|
}
|
|
411
287
|
});
|
|
412
288
|
async function collectFiles(dir) {
|
|
413
289
|
const files = [];
|
|
414
|
-
const srcDir =
|
|
415
|
-
const publicDir =
|
|
290
|
+
const srcDir = path2.join(dir, "src");
|
|
291
|
+
const publicDir = path2.join(dir, "public");
|
|
416
292
|
for (const rootFile of ROOT_FILES) {
|
|
417
|
-
const filePath =
|
|
418
|
-
if (await
|
|
293
|
+
const filePath = path2.join(dir, rootFile);
|
|
294
|
+
if (await fs2.pathExists(filePath)) {
|
|
419
295
|
files.push(filePath);
|
|
420
296
|
}
|
|
421
297
|
}
|
|
422
|
-
if (await
|
|
298
|
+
if (await fs2.pathExists(srcDir)) {
|
|
423
299
|
const srcFiles = await collectFilesRecursive(srcDir);
|
|
424
300
|
files.push(...srcFiles);
|
|
425
301
|
}
|
|
426
|
-
if (await
|
|
302
|
+
if (await fs2.pathExists(publicDir)) {
|
|
427
303
|
const publicFiles = await collectFilesRecursive(publicDir);
|
|
428
304
|
files.push(...publicFiles);
|
|
429
305
|
}
|
|
@@ -431,9 +307,9 @@ async function collectFiles(dir) {
|
|
|
431
307
|
}
|
|
432
308
|
async function collectFilesRecursive(dir) {
|
|
433
309
|
const files = [];
|
|
434
|
-
const entries = await
|
|
310
|
+
const entries = await fs2.readdir(dir, { withFileTypes: true });
|
|
435
311
|
for (const entry of entries) {
|
|
436
|
-
const fullPath =
|
|
312
|
+
const fullPath = path2.join(dir, entry.name);
|
|
437
313
|
if (EXCLUDE_PATTERNS.some((pattern) => entry.name.includes(pattern))) {
|
|
438
314
|
continue;
|
|
439
315
|
}
|
|
@@ -447,6 +323,140 @@ async function collectFilesRecursive(dir) {
|
|
|
447
323
|
return files;
|
|
448
324
|
}
|
|
449
325
|
|
|
326
|
+
// src/commands/dev.ts
|
|
327
|
+
import { Command as Command3 } from "commander";
|
|
328
|
+
import chalk3 from "chalk";
|
|
329
|
+
import { spawn } from "child_process";
|
|
330
|
+
import fs3 from "fs-extra";
|
|
331
|
+
import path3 from "path";
|
|
332
|
+
var devCommand = new Command3("dev").description("Start development server").option("-p, --port <port>", "Port to run on", "5173").option("--edit", "Enable edit mode").action(async (options) => {
|
|
333
|
+
const packageJsonPath = path3.join(process.cwd(), "package.json");
|
|
334
|
+
if (!await fs3.pathExists(packageJsonPath)) {
|
|
335
|
+
console.error(chalk3.red("Error: No package.json found. Are you in a template project?"));
|
|
336
|
+
console.log(chalk3.dim("Run `yoamigo init` to create a new template project."));
|
|
337
|
+
process.exit(1);
|
|
338
|
+
}
|
|
339
|
+
const viteConfigPath = path3.join(process.cwd(), "vite.config.ts");
|
|
340
|
+
if (!await fs3.pathExists(viteConfigPath)) {
|
|
341
|
+
console.error(chalk3.red("Error: No vite.config.ts found. Are you in a template project?"));
|
|
342
|
+
process.exit(1);
|
|
343
|
+
}
|
|
344
|
+
console.log(chalk3.cyan("Starting development server..."));
|
|
345
|
+
console.log();
|
|
346
|
+
const packageManager = await detectPackageManager();
|
|
347
|
+
const env = {
|
|
348
|
+
...process.env,
|
|
349
|
+
...options.edit ? { YA_EDIT_MODE: "true" } : {}
|
|
350
|
+
};
|
|
351
|
+
const args = packageManager === "npm" ? ["run", "dev"] : ["dev"];
|
|
352
|
+
const child = spawn(packageManager, args, {
|
|
353
|
+
cwd: process.cwd(),
|
|
354
|
+
stdio: "inherit",
|
|
355
|
+
env
|
|
356
|
+
});
|
|
357
|
+
child.on("error", (error) => {
|
|
358
|
+
console.error(chalk3.red(`Failed to start dev server: ${error.message}`));
|
|
359
|
+
process.exit(1);
|
|
360
|
+
});
|
|
361
|
+
child.on("close", (code) => {
|
|
362
|
+
process.exit(code ?? 0);
|
|
363
|
+
});
|
|
364
|
+
});
|
|
365
|
+
async function detectPackageManager() {
|
|
366
|
+
if (await fs3.pathExists(path3.join(process.cwd(), "pnpm-lock.yaml"))) {
|
|
367
|
+
return "pnpm";
|
|
368
|
+
}
|
|
369
|
+
if (await fs3.pathExists(path3.join(process.cwd(), "yarn.lock"))) {
|
|
370
|
+
return "yarn";
|
|
371
|
+
}
|
|
372
|
+
return "npm";
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// src/commands/init.ts
|
|
376
|
+
import { Command as Command4 } from "commander";
|
|
377
|
+
import chalk4 from "chalk";
|
|
378
|
+
import ora3 from "ora";
|
|
379
|
+
import fs4 from "fs-extra";
|
|
380
|
+
import path4 from "path";
|
|
381
|
+
import { fileURLToPath } from "url";
|
|
382
|
+
import { input as input2 } from "@inquirer/prompts";
|
|
383
|
+
import { spawn as spawn2 } from "child_process";
|
|
384
|
+
var __dirname = path4.dirname(fileURLToPath(import.meta.url));
|
|
385
|
+
var TEMPLATES_DIR = path4.resolve(__dirname, "../templates");
|
|
386
|
+
var initCommand = new Command4("init").description("Create a new template project").argument("[name]", "Project name").option("-t, --template <template>", "Template to use", "starter").action(async (name, options) => {
|
|
387
|
+
let projectName = name;
|
|
388
|
+
if (!projectName) {
|
|
389
|
+
try {
|
|
390
|
+
projectName = await input2({
|
|
391
|
+
message: "Project name:",
|
|
392
|
+
default: "my-template",
|
|
393
|
+
validate: (value) => {
|
|
394
|
+
if (!value || value.trim().length === 0) {
|
|
395
|
+
return "Project name is required";
|
|
396
|
+
}
|
|
397
|
+
if (!/^[a-z0-9-]+$/.test(value)) {
|
|
398
|
+
return "Project name must be lowercase letters, numbers, and hyphens only";
|
|
399
|
+
}
|
|
400
|
+
return true;
|
|
401
|
+
}
|
|
402
|
+
});
|
|
403
|
+
} catch {
|
|
404
|
+
console.log();
|
|
405
|
+
process.exit(0);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
const targetDir = path4.resolve(process.cwd(), projectName);
|
|
409
|
+
const templateDir = path4.join(TEMPLATES_DIR, options.template);
|
|
410
|
+
if (await fs4.pathExists(targetDir)) {
|
|
411
|
+
console.error(chalk4.red(`Error: Directory ${projectName} already exists`));
|
|
412
|
+
process.exit(1);
|
|
413
|
+
}
|
|
414
|
+
if (!await fs4.pathExists(templateDir)) {
|
|
415
|
+
console.error(chalk4.red(`Error: Template "${options.template}" not found`));
|
|
416
|
+
process.exit(1);
|
|
417
|
+
}
|
|
418
|
+
const spinner = ora3(`Creating ${projectName}...`).start();
|
|
419
|
+
try {
|
|
420
|
+
await fs4.copy(templateDir, targetDir);
|
|
421
|
+
const packageJsonPath = path4.join(targetDir, "package.json");
|
|
422
|
+
if (await fs4.pathExists(packageJsonPath)) {
|
|
423
|
+
const packageJson = await fs4.readJson(packageJsonPath);
|
|
424
|
+
packageJson.name = projectName;
|
|
425
|
+
await fs4.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
|
426
|
+
}
|
|
427
|
+
spinner.succeed(`Created ${projectName}`);
|
|
428
|
+
console.log();
|
|
429
|
+
const installSpinner = ora3("Installing dependencies...").start();
|
|
430
|
+
await new Promise((resolve, reject) => {
|
|
431
|
+
const child = spawn2("npm", ["install"], {
|
|
432
|
+
cwd: targetDir,
|
|
433
|
+
stdio: "pipe"
|
|
434
|
+
});
|
|
435
|
+
child.on("error", reject);
|
|
436
|
+
child.on("close", (code) => {
|
|
437
|
+
if (code === 0) {
|
|
438
|
+
resolve();
|
|
439
|
+
} else {
|
|
440
|
+
reject(new Error(`npm install failed with code ${code}`));
|
|
441
|
+
}
|
|
442
|
+
});
|
|
443
|
+
});
|
|
444
|
+
installSpinner.succeed("Installed dependencies");
|
|
445
|
+
console.log();
|
|
446
|
+
console.log(chalk4.green("Success!") + ` Created ${projectName} at ${targetDir}`);
|
|
447
|
+
console.log();
|
|
448
|
+
console.log("Next steps:");
|
|
449
|
+
console.log(chalk4.cyan(` cd ${projectName}`));
|
|
450
|
+
console.log(chalk4.cyan(" yoamigo dev # Start development server"));
|
|
451
|
+
console.log(chalk4.cyan(" yoamigo preview # Preview in builder"));
|
|
452
|
+
console.log();
|
|
453
|
+
} catch (error) {
|
|
454
|
+
spinner.fail("Failed to create project");
|
|
455
|
+
console.error(chalk4.red(error instanceof Error ? error.message : "Unknown error"));
|
|
456
|
+
process.exit(1);
|
|
457
|
+
}
|
|
458
|
+
});
|
|
459
|
+
|
|
450
460
|
// src/index.ts
|
|
451
461
|
var require2 = createRequire(import.meta.url);
|
|
452
462
|
var pkg = require2("../package.json");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yoamigo.com/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.20",
|
|
4
4
|
"description": "CLI for creating and managing YoAmigo templates",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "SEE LICENSE IN LICENSE",
|
|
@@ -21,15 +21,7 @@
|
|
|
21
21
|
"publishConfig": {
|
|
22
22
|
"access": "public"
|
|
23
23
|
},
|
|
24
|
-
"repository": {
|
|
25
|
-
"type": "git",
|
|
26
|
-
"url": "https://github.com/yoamigo/sdk.git",
|
|
27
|
-
"directory": "packages/yoamigo-cli"
|
|
28
|
-
},
|
|
29
24
|
"homepage": "https://yoamigo.com/developers",
|
|
30
|
-
"bugs": {
|
|
31
|
-
"url": "https://github.com/yoamigo/sdk/issues"
|
|
32
|
-
},
|
|
33
25
|
"keywords": [
|
|
34
26
|
"yoamigo",
|
|
35
27
|
"cli",
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ContentStoreProvider } from '@yoamigo.com/core'
|
|
2
|
+
import { Router } from '@yoamigo.com/core/router'
|
|
2
3
|
import { Header } from './components/Header'
|
|
3
4
|
import { Footer } from './components/Footer'
|
|
4
|
-
import {
|
|
5
|
-
import { AboutPage } from './pages/AboutPage'
|
|
6
|
-
import { ContactPage } from './pages/ContactPage'
|
|
5
|
+
import { AppRoutes, getAvailablePages } from './routes'
|
|
7
6
|
|
|
8
7
|
function Layout({ children }: { children: React.ReactNode }) {
|
|
9
8
|
return (
|
|
@@ -18,13 +17,15 @@ function Layout({ children }: { children: React.ReactNode }) {
|
|
|
18
17
|
}
|
|
19
18
|
|
|
20
19
|
export default function App() {
|
|
20
|
+
const pages = getAvailablePages()
|
|
21
|
+
|
|
21
22
|
return (
|
|
22
|
-
<
|
|
23
|
-
<
|
|
24
|
-
<
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
</
|
|
28
|
-
</
|
|
23
|
+
<ContentStoreProvider pages={pages}>
|
|
24
|
+
<Router>
|
|
25
|
+
<Layout>
|
|
26
|
+
<AppRoutes />
|
|
27
|
+
</Layout>
|
|
28
|
+
</Router>
|
|
29
|
+
</ContentStoreProvider>
|
|
29
30
|
)
|
|
30
31
|
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Routes - Auto-discovery with wouter
|
|
3
|
+
*
|
|
4
|
+
* Auto-discovers pages from src/pages/ using import.meta.glob
|
|
5
|
+
* and generates wouter Route components.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { lazy, Suspense, ComponentType } from 'react'
|
|
9
|
+
import { Route, Switch } from 'wouter'
|
|
10
|
+
|
|
11
|
+
// Import all page modules
|
|
12
|
+
const pageModules = import.meta.glob<{ default: ComponentType }>('/src/pages/**/*.tsx')
|
|
13
|
+
|
|
14
|
+
interface RouteDefinition {
|
|
15
|
+
path: string
|
|
16
|
+
component: ComponentType
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function generateRoutes(): RouteDefinition[] {
|
|
20
|
+
const routes: RouteDefinition[] = []
|
|
21
|
+
|
|
22
|
+
for (const filePath in pageModules) {
|
|
23
|
+
// Skip component files that might be in pages
|
|
24
|
+
if (filePath.includes('/components/')) continue
|
|
25
|
+
if (filePath.includes('/_')) continue // Skip files starting with _
|
|
26
|
+
|
|
27
|
+
// Convert filesystem path to URL path
|
|
28
|
+
// /src/pages/index.tsx -> /
|
|
29
|
+
// /src/pages/contact.tsx -> /contact
|
|
30
|
+
// /src/pages/thank-you.tsx -> /thank-you
|
|
31
|
+
let routePath = filePath
|
|
32
|
+
.replace('/src/pages', '')
|
|
33
|
+
.replace(/\.tsx$/, '')
|
|
34
|
+
.replace(/\/index$/, '') || '/'
|
|
35
|
+
|
|
36
|
+
// Create lazy component
|
|
37
|
+
const Component = lazy(pageModules[filePath])
|
|
38
|
+
|
|
39
|
+
routes.push({ path: routePath, component: Component })
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Sort routes: exact paths before wildcards, longer paths first
|
|
43
|
+
routes.sort((a, b) => {
|
|
44
|
+
if (a.path === '/') return 1
|
|
45
|
+
if (b.path === '/') return -1
|
|
46
|
+
return b.path.length - a.path.length
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
return routes
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const routes = generateRoutes()
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Available routes for page selection
|
|
56
|
+
* Returns paths only (not lazy components) for use in UI
|
|
57
|
+
*/
|
|
58
|
+
export function getAvailablePages(): Array<{ path: string; label: string }> {
|
|
59
|
+
return routes.map(route => ({
|
|
60
|
+
path: route.path,
|
|
61
|
+
label: route.path === '/'
|
|
62
|
+
? 'Home'
|
|
63
|
+
: route.path
|
|
64
|
+
.slice(1) // Remove leading /
|
|
65
|
+
.split('-')
|
|
66
|
+
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
|
|
67
|
+
.join(' '),
|
|
68
|
+
}))
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* PageLoader - Shown while lazy-loading page components
|
|
73
|
+
*/
|
|
74
|
+
function PageLoader() {
|
|
75
|
+
return (
|
|
76
|
+
<div className="min-h-screen flex items-center justify-center bg-white">
|
|
77
|
+
<div className="animate-pulse text-gray-600">Loading...</div>
|
|
78
|
+
</div>
|
|
79
|
+
)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* AppRoutes - Renders auto-discovered routes with Suspense
|
|
84
|
+
*/
|
|
85
|
+
export function AppRoutes() {
|
|
86
|
+
return (
|
|
87
|
+
<Suspense fallback={<PageLoader />}>
|
|
88
|
+
<Switch>
|
|
89
|
+
{routes.map(({ path, component: Component }) => (
|
|
90
|
+
<Route key={path} path={path}>
|
|
91
|
+
<Component />
|
|
92
|
+
</Route>
|
|
93
|
+
))}
|
|
94
|
+
{/* 404 fallback */}
|
|
95
|
+
<Route>
|
|
96
|
+
<div className="min-h-screen flex items-center justify-center bg-white">
|
|
97
|
+
<h1 className="text-2xl text-gray-900">Page Not Found</h1>
|
|
98
|
+
</div>
|
|
99
|
+
</Route>
|
|
100
|
+
</Switch>
|
|
101
|
+
</Suspense>
|
|
102
|
+
)
|
|
103
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { AppRoutes, getAvailablePages } from './lib/router/routes'
|