@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 CHANGED
@@ -25,7 +25,7 @@ yoamigo dev
25
25
  yoamigo preview
26
26
 
27
27
  # 5. Submit for review
28
- yoamigo deploy --submit
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://dev.yoamigo.com",
24
- staging: "https://dev-staging.yoamigo.com"
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 Command4("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) => {
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(chalk4.dim(`API URL: ${API_BASE_URL2}`));
266
- console.log(chalk4.dim(`App URL: ${APP_BASE_URL}`));
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(chalk4.red("Error: Not logged in. Run `yoamigo login` first."));
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(chalk4.red("Error: Version must be in semver format (e.g., 1.0.0)"));
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 = path4.join(process.cwd(), "package.json");
280
- if (!await fs4.pathExists(packageJsonPath)) {
281
- console.error(chalk4.red("Error: No package.json found. Are you in a template project?"));
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 fs4.readJson(packageJsonPath);
152
+ const packageJson = await fs2.readJson(packageJsonPath);
285
153
  const templateName = packageJson.name || "unnamed-template";
286
- const spinner = ora3("Deploying template...").start();
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(chalk4.dim(`POST ${templateUrl}`));
303
- console.log(chalk4.dim(`Body: ${JSON.stringify(templateBody)}`));
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(chalk4.red(`
187
+ console.error(chalk2.red(`
320
188
  Fetch error for ${templateUrl}:`));
321
- console.error(chalk4.red(fetchError instanceof Error ? fetchError.stack || fetchError.message : String(fetchError)));
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(chalk4.dim(`Response status: ${templateResponse.status}`));
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(chalk4.red(`
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 = path4.relative(process.cwd(), file);
354
- const content = await fs4.readFile(file, "utf-8");
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(chalk4.dim(`Uploading: ${relativePath}`));
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(chalk4.yellow(`Failed to upload ${relativePath}: ${uploadResponse.status}`));
378
- console.error(chalk4.yellow(`Response: ${errorText}`));
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(chalk4.yellow(`Warning: Failed to upload ${relativePath}`));
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(chalk4.yellow(`Network error uploading ${relativePath}:`));
388
- console.error(chalk4.yellow(uploadError instanceof Error ? uploadError.message : String(uploadError)));
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(chalk4.yellow(`Warning: Failed to upload ${relativePath}`));
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(chalk4.green("Preview URL:"));
398
- console.log(chalk4.cyan(` ${APP_BASE_URL}/preview/${templateId}`));
273
+ console.log(chalk2.green("Preview URL:"));
274
+ console.log(chalk2.cyan(` ${APP_BASE_URL}/preview/${templateId}`));
399
275
  console.log();
400
- console.log(chalk4.dim("To submit for review, visit your dashboard:"));
401
- console.log(chalk4.dim(` ${APP_BASE_URL}/dashboard`));
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(chalk4.red(error.stack));
281
+ console.error(chalk2.red(error.stack));
406
282
  } else {
407
- console.error(chalk4.red(error instanceof Error ? error.message : "Unknown 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 = path4.join(dir, "src");
415
- const publicDir = path4.join(dir, "public");
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 = path4.join(dir, rootFile);
418
- if (await fs4.pathExists(filePath)) {
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 fs4.pathExists(srcDir)) {
298
+ if (await fs2.pathExists(srcDir)) {
423
299
  const srcFiles = await collectFilesRecursive(srcDir);
424
300
  files.push(...srcFiles);
425
301
  }
426
- if (await fs4.pathExists(publicDir)) {
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 fs4.readdir(dir, { withFileTypes: true });
310
+ const entries = await fs2.readdir(dir, { withFileTypes: true });
435
311
  for (const entry of entries) {
436
- const fullPath = path4.join(dir, entry.name);
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.19",
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 { Router, Route } from '@yoamigo.com/core/router'
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 { HomePage } from './pages/HomePage'
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
- <Router>
23
- <Layout>
24
- <Route path="/" component={HomePage} />
25
- <Route path="/about" component={AboutPage} />
26
- <Route path="/contact" component={ContactPage} />
27
- </Layout>
28
- </Router>
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
+ }
@@ -1,6 +1,6 @@
1
1
  import { YaText } from '@yoamigo.com/core'
2
2
 
3
- export function AboutPage() {
3
+ export default function AboutPage() {
4
4
  return (
5
5
  <div>
6
6
  {/* Hero Section */}
@@ -1,6 +1,6 @@
1
1
  import { YaText } from '@yoamigo.com/core'
2
2
 
3
- export function ContactPage() {
3
+ export default function ContactPage() {
4
4
  return (
5
5
  <div>
6
6
  {/* Hero Section */}
@@ -1,6 +1,6 @@
1
1
  import { YaText, YaLink } from '@yoamigo.com/core'
2
2
 
3
- export function HomePage() {
3
+ export default function HomePage() {
4
4
  return (
5
5
  <div>
6
6
  {/* Hero Section */}
@@ -0,0 +1 @@
1
+ export { AppRoutes, getAvailablePages } from './lib/router/routes'
@@ -9,6 +9,7 @@
9
9
  "esModuleInterop": true,
10
10
  "skipLibCheck": true,
11
11
  "noEmit": true,
12
+ "types": ["node", "vite/client"],
12
13
  "resolveJsonModule": true,
13
14
  "baseUrl": ".",
14
15
  "paths": {