create-nyoworks 2.7.2 → 3.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.
- package/dist/config-loader.d.ts +45 -0
- package/dist/config-loader.js +68 -0
- package/dist/init.js +378 -111
- package/package.json +3 -2
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export interface AppDefinition {
|
|
2
|
+
id: string;
|
|
3
|
+
name: string;
|
|
4
|
+
name_tr: string;
|
|
5
|
+
description: string;
|
|
6
|
+
description_tr: string;
|
|
7
|
+
platforms: ("web" | "mobile" | "desktop")[];
|
|
8
|
+
features: string[];
|
|
9
|
+
integrations: string[];
|
|
10
|
+
}
|
|
11
|
+
export interface IntegrationDefinition {
|
|
12
|
+
name: string;
|
|
13
|
+
providers?: string[];
|
|
14
|
+
default?: string;
|
|
15
|
+
services?: string[];
|
|
16
|
+
free_tier?: Record<string, string>;
|
|
17
|
+
note?: string;
|
|
18
|
+
}
|
|
19
|
+
export interface PlatformDefinition {
|
|
20
|
+
name: string;
|
|
21
|
+
framework: string;
|
|
22
|
+
version: string;
|
|
23
|
+
}
|
|
24
|
+
export interface AppsConfig {
|
|
25
|
+
apps: AppDefinition[];
|
|
26
|
+
integrations: Record<string, IntegrationDefinition>;
|
|
27
|
+
platforms: Record<string, PlatformDefinition>;
|
|
28
|
+
}
|
|
29
|
+
export declare function loadAppsConfig(repoDir: string): Promise<AppsConfig>;
|
|
30
|
+
export declare function appToPromptChoice(app: AppDefinition, lang?: string): {
|
|
31
|
+
title: string;
|
|
32
|
+
value: string;
|
|
33
|
+
description: string;
|
|
34
|
+
};
|
|
35
|
+
export declare function integrationToPromptChoices(integration: IntegrationDefinition, integrationId: string): {
|
|
36
|
+
choices: {
|
|
37
|
+
title: string;
|
|
38
|
+
value: string;
|
|
39
|
+
}[];
|
|
40
|
+
initial: number;
|
|
41
|
+
message: string;
|
|
42
|
+
} | null;
|
|
43
|
+
export declare function getAppFeatures(app: AppDefinition): string[];
|
|
44
|
+
export declare function getAppIntegrations(app: AppDefinition): string[];
|
|
45
|
+
export declare function getAppPlatforms(app: AppDefinition): string[];
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
2
|
+
// Config Loader - Load app definitions from YAML
|
|
3
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
4
|
+
import fs from "fs-extra";
|
|
5
|
+
import path from "path";
|
|
6
|
+
import { parse } from "yaml";
|
|
7
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
8
|
+
// Loader Functions
|
|
9
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
10
|
+
export async function loadAppsConfig(repoDir) {
|
|
11
|
+
const configPath = path.join(repoDir, "config", "apps.yaml");
|
|
12
|
+
if (!await fs.pathExists(configPath)) {
|
|
13
|
+
throw new Error("config/apps.yaml not found in repository");
|
|
14
|
+
}
|
|
15
|
+
const content = await fs.readFile(configPath, "utf8");
|
|
16
|
+
return parse(content);
|
|
17
|
+
}
|
|
18
|
+
export function appToPromptChoice(app, lang = "en") {
|
|
19
|
+
const name = lang === "tr" ? app.name_tr : app.name;
|
|
20
|
+
const desc = lang === "tr" ? app.description_tr : app.description;
|
|
21
|
+
return {
|
|
22
|
+
title: name,
|
|
23
|
+
value: app.id,
|
|
24
|
+
description: desc,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
export function integrationToPromptChoices(integration, integrationId) {
|
|
28
|
+
if (!integration.providers || integration.providers.length <= 1) {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
const defaultIndex = integration.providers.indexOf(integration.default || integration.providers[0]);
|
|
32
|
+
return {
|
|
33
|
+
choices: integration.providers.map((provider) => ({
|
|
34
|
+
title: formatProviderName(provider),
|
|
35
|
+
value: provider,
|
|
36
|
+
})),
|
|
37
|
+
initial: defaultIndex >= 0 ? defaultIndex : 0,
|
|
38
|
+
message: `${integration.name} provider:`,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
function formatProviderName(provider) {
|
|
42
|
+
const names = {
|
|
43
|
+
mollie: "Mollie (iDEAL, Bancontact)",
|
|
44
|
+
adyen: "Adyen",
|
|
45
|
+
stripe: "Stripe",
|
|
46
|
+
resend: "Resend (3000/ay free)",
|
|
47
|
+
brevo: "Brevo (300/gün free)",
|
|
48
|
+
twilio: "Twilio",
|
|
49
|
+
plivo: "Plivo",
|
|
50
|
+
postnl: "PostNL",
|
|
51
|
+
sendcloud: "Sendcloud",
|
|
52
|
+
dhl: "DHL",
|
|
53
|
+
storecove: "Storecove",
|
|
54
|
+
};
|
|
55
|
+
return names[provider] || provider.charAt(0).toUpperCase() + provider.slice(1);
|
|
56
|
+
}
|
|
57
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
58
|
+
// Feature Helpers
|
|
59
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
60
|
+
export function getAppFeatures(app) {
|
|
61
|
+
return app.features || [];
|
|
62
|
+
}
|
|
63
|
+
export function getAppIntegrations(app) {
|
|
64
|
+
return app.integrations || [];
|
|
65
|
+
}
|
|
66
|
+
export function getAppPlatforms(app) {
|
|
67
|
+
return app.platforms || ["web"];
|
|
68
|
+
}
|
package/dist/init.js
CHANGED
|
@@ -1,47 +1,23 @@
|
|
|
1
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
2
|
+
// Create NYOWORKS - Project Initialization (Multi-Product Support)
|
|
3
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
1
4
|
import prompts from "prompts";
|
|
2
5
|
import pc from "picocolors";
|
|
3
6
|
import fs from "fs-extra";
|
|
4
7
|
import path from "path";
|
|
5
8
|
import os from "os";
|
|
9
|
+
import { fileURLToPath } from "url";
|
|
6
10
|
import { execa } from "execa";
|
|
7
11
|
import { replacePlaceholders } from "./replace.js";
|
|
8
12
|
import { checkDependencies, showClaudeMaxWarning, getDockerComposeCommand } from "./checks.js";
|
|
13
|
+
import { loadAppsConfig, appToPromptChoice, integrationToPromptChoices, getAppFeatures, getAppPlatforms, } from "./config-loader.js";
|
|
14
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
15
|
+
const __dirname = path.dirname(__filename);
|
|
16
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
17
|
+
// Constants
|
|
18
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
9
19
|
const REPO = "naimozcan/nyoworks-framework";
|
|
10
20
|
const BRANCH = "main";
|
|
11
|
-
const PRODUCT_TYPES = [
|
|
12
|
-
{ title: "E-Commerce", value: "ecommerce", description: "Online store, product sales" },
|
|
13
|
-
{ title: "Booking", value: "booking", description: "Appointment/reservation system" },
|
|
14
|
-
{ title: "SaaS Platform", value: "saas", description: "Subscription-based software" },
|
|
15
|
-
{ title: "Marketplace", value: "marketplace", description: "Multi-vendor platform" },
|
|
16
|
-
{ title: "Content/Blog", value: "content", description: "Blog, news, CMS" },
|
|
17
|
-
{ title: "CRM", value: "crm", description: "Customer relationship management" },
|
|
18
|
-
{ title: "Custom", value: "custom", description: "Manual feature selection" },
|
|
19
|
-
];
|
|
20
|
-
const REQUIRED_FEATURES = {
|
|
21
|
-
ecommerce: ["payments", "crm", "notifications", "search", "storage"],
|
|
22
|
-
booking: ["appointments", "payments", "crm", "notifications"],
|
|
23
|
-
saas: ["payments", "subscriptions", "analytics", "notifications", "audit", "multitenant"],
|
|
24
|
-
marketplace: ["payments", "crm", "analytics", "notifications", "audit", "search", "storage", "multitenant"],
|
|
25
|
-
content: ["analytics", "search", "storage"],
|
|
26
|
-
crm: ["crm", "analytics", "notifications", "audit", "export"],
|
|
27
|
-
custom: [],
|
|
28
|
-
};
|
|
29
|
-
const AVAILABLE_FEATURES = [
|
|
30
|
-
{ title: "Analytics", value: "analytics", description: "User behavior tracking" },
|
|
31
|
-
{ title: "Appointments", value: "appointments", description: "Booking system" },
|
|
32
|
-
{ title: "Audit", value: "audit", description: "Activity logging" },
|
|
33
|
-
{ title: "Auth Social", value: "auth-social", description: "Google, Apple, GitHub OAuth" },
|
|
34
|
-
{ title: "CRM", value: "crm", description: "Customer relationship management" },
|
|
35
|
-
{ title: "Export", value: "export", description: "PDF/CSV export" },
|
|
36
|
-
{ title: "i18n", value: "i18n", description: "Multi-language support" },
|
|
37
|
-
{ title: "Multitenant", value: "multitenant", description: "Multi-organization support" },
|
|
38
|
-
{ title: "Notifications", value: "notifications", description: "Email, SMS, Push" },
|
|
39
|
-
{ title: "Payments", value: "payments", description: "Stripe integration" },
|
|
40
|
-
{ title: "Realtime", value: "realtime", description: "WebSocket support" },
|
|
41
|
-
{ title: "Search", value: "search", description: "Full-text search" },
|
|
42
|
-
{ title: "Storage", value: "storage", description: "File uploads (S3/R2)" },
|
|
43
|
-
{ title: "Subscriptions", value: "subscriptions", description: "Plans & usage limits" },
|
|
44
|
-
];
|
|
45
21
|
const AVAILABLE_PLATFORMS = [
|
|
46
22
|
{ title: "Web", value: "web", description: "Next.js 16" },
|
|
47
23
|
{ title: "Mobile", value: "mobile", description: "Expo SDK 54" },
|
|
@@ -57,6 +33,16 @@ const LANGUAGE_RESPONSES = {
|
|
|
57
33
|
en: "English",
|
|
58
34
|
nl: "Dutch",
|
|
59
35
|
};
|
|
36
|
+
const ADDITIONAL_FEATURES = [
|
|
37
|
+
{ title: "Realtime", value: "realtime", description: "WebSocket support" },
|
|
38
|
+
{ title: "i18n", value: "i18n", description: "Multi-language support" },
|
|
39
|
+
{ title: "Auth Social", value: "auth-social", description: "Google, Apple, GitHub OAuth" },
|
|
40
|
+
{ title: "Multitenant", value: "multitenant", description: "Multi-organization support" },
|
|
41
|
+
{ title: "Subscriptions", value: "subscriptions", description: "Plans & usage limits" },
|
|
42
|
+
];
|
|
43
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
44
|
+
// Helper Functions
|
|
45
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
60
46
|
function generateCode(name) {
|
|
61
47
|
return name
|
|
62
48
|
.toUpperCase()
|
|
@@ -84,13 +70,44 @@ async function downloadRepo(repo, branch) {
|
|
|
84
70
|
await execa("tar", ["-xzf", tarFile, "-C", tempDir]);
|
|
85
71
|
return path.join(tempDir, `nyoworks-framework-${branch}`);
|
|
86
72
|
}
|
|
73
|
+
function getLocalRepoPath() {
|
|
74
|
+
const localPath = path.resolve(__dirname, "..", "..", "..");
|
|
75
|
+
const configPath = path.join(localPath, "config", "apps.yaml");
|
|
76
|
+
if (fs.existsSync(configPath)) {
|
|
77
|
+
return localPath;
|
|
78
|
+
}
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
async function replacePackageNames(targetDir, slug) {
|
|
82
|
+
const pkgFiles = await findPackageJsonFiles(targetDir);
|
|
83
|
+
for (const pkgFile of pkgFiles) {
|
|
84
|
+
let content = await fs.readFile(pkgFile, "utf8");
|
|
85
|
+
content = content.replace(/@nyoworks\//g, `@${slug}/`);
|
|
86
|
+
await fs.writeFile(pkgFile, content);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
async function findPackageJsonFiles(dir) {
|
|
90
|
+
const results = [];
|
|
91
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
92
|
+
for (const entry of entries) {
|
|
93
|
+
const fullPath = path.join(dir, entry.name);
|
|
94
|
+
if (entry.isDirectory() && entry.name !== "node_modules" && entry.name !== ".git") {
|
|
95
|
+
results.push(...await findPackageJsonFiles(fullPath));
|
|
96
|
+
}
|
|
97
|
+
else if (entry.isFile() && entry.name === "package.json") {
|
|
98
|
+
results.push(fullPath);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return results;
|
|
102
|
+
}
|
|
103
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
104
|
+
// Main Function
|
|
105
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
87
106
|
export async function createProject(projectName) {
|
|
88
107
|
console.log();
|
|
89
108
|
console.log(pc.cyan(pc.bold(" NYOWORKS Framework")));
|
|
90
|
-
console.log(pc.dim(" Create a new project"));
|
|
109
|
+
console.log(pc.dim(" Create a new multi-product project"));
|
|
91
110
|
console.log();
|
|
92
|
-
let productType = "custom";
|
|
93
|
-
let requiredFeatures = [];
|
|
94
111
|
const nameResponse = await prompts({
|
|
95
112
|
type: projectName ? null : "text",
|
|
96
113
|
name: "name",
|
|
@@ -102,79 +119,175 @@ export async function createProject(projectName) {
|
|
|
102
119
|
console.log(pc.red("Aborted."));
|
|
103
120
|
process.exit(1);
|
|
104
121
|
}
|
|
105
|
-
const productResponse = await prompts({
|
|
106
|
-
type: "select",
|
|
107
|
-
name: "productType",
|
|
108
|
-
message: "Product type:",
|
|
109
|
-
choices: PRODUCT_TYPES,
|
|
110
|
-
initial: 0,
|
|
111
|
-
});
|
|
112
|
-
if (productResponse.productType) {
|
|
113
|
-
productType = productResponse.productType;
|
|
114
|
-
requiredFeatures = REQUIRED_FEATURES[productType] || [];
|
|
115
|
-
}
|
|
116
|
-
const featureChoices = AVAILABLE_FEATURES.map((f) => {
|
|
117
|
-
const isRequired = requiredFeatures.includes(f.value);
|
|
118
|
-
return {
|
|
119
|
-
title: isRequired ? `${f.title} [required]` : f.title,
|
|
120
|
-
value: f.value,
|
|
121
|
-
description: f.description,
|
|
122
|
-
selected: isRequired,
|
|
123
|
-
};
|
|
124
|
-
});
|
|
125
|
-
const response = await prompts([
|
|
126
|
-
{
|
|
127
|
-
type: "multiselect",
|
|
128
|
-
name: "platforms",
|
|
129
|
-
message: "Select platforms:",
|
|
130
|
-
choices: AVAILABLE_PLATFORMS,
|
|
131
|
-
min: 1,
|
|
132
|
-
hint: "- Space to select. Return to submit",
|
|
133
|
-
instructions: false,
|
|
134
|
-
},
|
|
135
|
-
{
|
|
136
|
-
type: "multiselect",
|
|
137
|
-
name: "features",
|
|
138
|
-
message: "Select features:",
|
|
139
|
-
choices: featureChoices,
|
|
140
|
-
hint: "- Space to select. Return to submit",
|
|
141
|
-
instructions: false,
|
|
142
|
-
},
|
|
143
|
-
{
|
|
144
|
-
type: "select",
|
|
145
|
-
name: "language",
|
|
146
|
-
message: "Agent response language:",
|
|
147
|
-
choices: AVAILABLE_LANGUAGES,
|
|
148
|
-
initial: 0,
|
|
149
|
-
},
|
|
150
|
-
]);
|
|
151
122
|
const name = (nameResponse.name || projectName);
|
|
152
123
|
const code = generateCode(name);
|
|
153
124
|
const slug = generateSlug(name);
|
|
154
125
|
const databaseName = generateDatabaseName(name);
|
|
155
|
-
const platforms = response.platforms || ["web"];
|
|
156
|
-
const selectedFeatures = response.features || [];
|
|
157
|
-
const features = [...new Set([...requiredFeatures, ...selectedFeatures])];
|
|
158
|
-
const language = response.language || "tr";
|
|
159
126
|
const targetDir = path.resolve(process.cwd(), slug);
|
|
160
127
|
if (fs.existsSync(targetDir)) {
|
|
161
128
|
console.log(pc.red(`Directory ${slug} already exists.`));
|
|
162
129
|
process.exit(1);
|
|
163
130
|
}
|
|
164
131
|
console.log();
|
|
165
|
-
process.stdout.write(pc.cyan("Downloading from GitHub..."));
|
|
166
132
|
let repoDir;
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
133
|
+
let config;
|
|
134
|
+
let isLocalMode = false;
|
|
135
|
+
const localRepo = getLocalRepoPath();
|
|
136
|
+
if (localRepo) {
|
|
137
|
+
process.stdout.write(pc.cyan("Using local framework..."));
|
|
138
|
+
repoDir = localRepo;
|
|
139
|
+
isLocalMode = true;
|
|
140
|
+
try {
|
|
141
|
+
config = await loadAppsConfig(repoDir);
|
|
142
|
+
console.log(pc.green(" done"));
|
|
143
|
+
}
|
|
144
|
+
catch (error) {
|
|
145
|
+
console.log(pc.red(" failed"));
|
|
146
|
+
console.error(pc.red("Failed to load local config."));
|
|
147
|
+
process.exit(1);
|
|
148
|
+
}
|
|
170
149
|
}
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
150
|
+
else {
|
|
151
|
+
process.stdout.write(pc.cyan("Downloading from GitHub..."));
|
|
152
|
+
try {
|
|
153
|
+
repoDir = await downloadRepo(REPO, BRANCH);
|
|
154
|
+
config = await loadAppsConfig(repoDir);
|
|
155
|
+
console.log(pc.green(" done"));
|
|
156
|
+
}
|
|
157
|
+
catch (error) {
|
|
158
|
+
console.log(pc.red(" failed"));
|
|
159
|
+
console.error(pc.red("Failed to download from GitHub. Check your internet connection."));
|
|
160
|
+
process.exit(1);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
164
|
+
// Multi-Product Selection
|
|
165
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
166
|
+
const appChoices = config.apps.map((app) => ({
|
|
167
|
+
...appToPromptChoice(app, "tr"),
|
|
168
|
+
selected: false,
|
|
169
|
+
}));
|
|
170
|
+
const productsResponse = await prompts({
|
|
171
|
+
type: "multiselect",
|
|
172
|
+
name: "appIds",
|
|
173
|
+
message: "Products (birden fazla seçebilirsiniz):",
|
|
174
|
+
choices: appChoices,
|
|
175
|
+
min: 1,
|
|
176
|
+
hint: "- Space to select. Return to submit",
|
|
177
|
+
instructions: false,
|
|
178
|
+
});
|
|
179
|
+
if (!productsResponse.appIds || productsResponse.appIds.length === 0) {
|
|
180
|
+
console.log(pc.red("Aborted."));
|
|
174
181
|
process.exit(1);
|
|
175
182
|
}
|
|
183
|
+
const selectedAppIds = productsResponse.appIds;
|
|
184
|
+
const selectedApps = config.apps.filter((a) => selectedAppIds.includes(a.id));
|
|
185
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
186
|
+
// Per-Product Platform Selection
|
|
187
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
188
|
+
const productSelections = [];
|
|
189
|
+
for (const app of selectedApps) {
|
|
190
|
+
console.log();
|
|
191
|
+
console.log(pc.cyan(` ${app.name_tr || app.name} platformları:`));
|
|
192
|
+
const appPlatforms = getAppPlatforms(app);
|
|
193
|
+
const platformChoices = AVAILABLE_PLATFORMS
|
|
194
|
+
.filter((p) => appPlatforms.includes(p.value))
|
|
195
|
+
.map((p) => ({ ...p, selected: p.value === "web" }));
|
|
196
|
+
const platformResponse = await prompts({
|
|
197
|
+
type: "multiselect",
|
|
198
|
+
name: "platforms",
|
|
199
|
+
message: `${app.id} platforms:`,
|
|
200
|
+
choices: platformChoices,
|
|
201
|
+
min: 1,
|
|
202
|
+
hint: "- Space to select. Return to submit",
|
|
203
|
+
instructions: false,
|
|
204
|
+
});
|
|
205
|
+
const platforms = (platformResponse.platforms || ["web"]);
|
|
206
|
+
productSelections.push({ app, platforms });
|
|
207
|
+
}
|
|
208
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
209
|
+
// Collect All Features from All Products
|
|
210
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
211
|
+
const allAppFeatures = new Set();
|
|
212
|
+
const allIntegrations = new Set();
|
|
213
|
+
for (const selection of productSelections) {
|
|
214
|
+
const features = getAppFeatures(selection.app);
|
|
215
|
+
features.forEach((f) => allAppFeatures.add(f));
|
|
216
|
+
for (const integration of selection.app.integrations || []) {
|
|
217
|
+
allIntegrations.add(integration);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
221
|
+
// Integration Provider Selection (Once for All Products)
|
|
222
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
223
|
+
const selectedProviders = {};
|
|
224
|
+
for (const integrationId of allIntegrations) {
|
|
225
|
+
const integration = config.integrations?.[integrationId];
|
|
226
|
+
if (!integration)
|
|
227
|
+
continue;
|
|
228
|
+
const promptConfig = integrationToPromptChoices(integration, integrationId);
|
|
229
|
+
if (!promptConfig) {
|
|
230
|
+
selectedProviders[integrationId] = integration.default || integration.providers?.[0] || integrationId;
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
const providerResponse = await prompts({
|
|
234
|
+
type: "select",
|
|
235
|
+
name: "provider",
|
|
236
|
+
message: promptConfig.message,
|
|
237
|
+
choices: promptConfig.choices,
|
|
238
|
+
initial: promptConfig.initial,
|
|
239
|
+
});
|
|
240
|
+
if (providerResponse.provider) {
|
|
241
|
+
selectedProviders[integrationId] = providerResponse.provider;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
245
|
+
// Additional Features
|
|
246
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
247
|
+
const additionalFeaturesNotInApps = ADDITIONAL_FEATURES.filter((f) => !allAppFeatures.has(f.value));
|
|
248
|
+
let additionalFeatures = [];
|
|
249
|
+
if (additionalFeaturesNotInApps.length > 0) {
|
|
250
|
+
const additionalResponse = await prompts({
|
|
251
|
+
type: "multiselect",
|
|
252
|
+
name: "features",
|
|
253
|
+
message: "Ek feature'lar (isteğe bağlı):",
|
|
254
|
+
choices: additionalFeaturesNotInApps,
|
|
255
|
+
hint: "- Space to select. Return to submit",
|
|
256
|
+
instructions: false,
|
|
257
|
+
});
|
|
258
|
+
additionalFeatures = additionalResponse.features || [];
|
|
259
|
+
}
|
|
260
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
261
|
+
// Language Selection
|
|
262
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
263
|
+
const languageResponse = await prompts({
|
|
264
|
+
type: "select",
|
|
265
|
+
name: "language",
|
|
266
|
+
message: "Agent language:",
|
|
267
|
+
choices: AVAILABLE_LANGUAGES,
|
|
268
|
+
initial: 0,
|
|
269
|
+
});
|
|
270
|
+
const language = languageResponse.language || "tr";
|
|
271
|
+
const features = [...new Set([...allAppFeatures, ...additionalFeatures])];
|
|
272
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
273
|
+
// Build Project Config
|
|
274
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
275
|
+
const projectConfig = {
|
|
276
|
+
name,
|
|
277
|
+
code,
|
|
278
|
+
slug,
|
|
279
|
+
databaseName,
|
|
280
|
+
products: productSelections,
|
|
281
|
+
features,
|
|
282
|
+
providers: selectedProviders,
|
|
283
|
+
language,
|
|
284
|
+
};
|
|
285
|
+
console.log();
|
|
176
286
|
process.stdout.write(pc.dim(" Copying files..."));
|
|
177
287
|
await fs.ensureDir(targetDir);
|
|
288
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
289
|
+
// Copy Core Packages (Shared)
|
|
290
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
178
291
|
const corePaths = [
|
|
179
292
|
"packages/api",
|
|
180
293
|
"packages/api-client",
|
|
@@ -187,6 +300,7 @@ export async function createProject(projectName) {
|
|
|
187
300
|
"docs/bible",
|
|
188
301
|
"mcp-server",
|
|
189
302
|
".claude",
|
|
303
|
+
"config",
|
|
190
304
|
];
|
|
191
305
|
for (const p of corePaths) {
|
|
192
306
|
const src = path.join(repoDir, p);
|
|
@@ -195,13 +309,32 @@ export async function createProject(projectName) {
|
|
|
195
309
|
await fs.copy(src, dest);
|
|
196
310
|
}
|
|
197
311
|
}
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
312
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
313
|
+
// Copy Apps (Per Product + Platform)
|
|
314
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
315
|
+
for (const selection of productSelections) {
|
|
316
|
+
const appId = selection.app.id;
|
|
317
|
+
for (const platform of selection.platforms) {
|
|
318
|
+
const templateSrc = path.join(repoDir, `apps/_templates/${platform}`);
|
|
319
|
+
const legacySrc = path.join(repoDir, `apps/${platform}`);
|
|
320
|
+
const dest = path.join(targetDir, `apps/${appId}/${platform}`);
|
|
321
|
+
if (await fs.pathExists(templateSrc)) {
|
|
322
|
+
await fs.copy(templateSrc, dest);
|
|
323
|
+
}
|
|
324
|
+
else if (await fs.pathExists(legacySrc)) {
|
|
325
|
+
await fs.copy(legacySrc, dest);
|
|
326
|
+
}
|
|
327
|
+
const pkgPath = path.join(dest, "package.json");
|
|
328
|
+
if (await fs.pathExists(pkgPath)) {
|
|
329
|
+
const pkg = await fs.readJson(pkgPath);
|
|
330
|
+
pkg.name = `@${slug}/${appId}-${platform}`;
|
|
331
|
+
await fs.writeJson(pkgPath, pkg, { spaces: 2 });
|
|
332
|
+
}
|
|
203
333
|
}
|
|
204
334
|
}
|
|
335
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
336
|
+
// Copy Feature Packages
|
|
337
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
205
338
|
for (const feature of features) {
|
|
206
339
|
const src = path.join(repoDir, `packages/features/${feature}`);
|
|
207
340
|
const dest = path.join(targetDir, `packages/features/${feature}`);
|
|
@@ -209,6 +342,29 @@ export async function createProject(projectName) {
|
|
|
209
342
|
await fs.copy(src, dest);
|
|
210
343
|
}
|
|
211
344
|
}
|
|
345
|
+
const integrationFeatureMap = {
|
|
346
|
+
payments: ["payments"],
|
|
347
|
+
shipping: ["shipping"],
|
|
348
|
+
invoicing: ["invoicing"],
|
|
349
|
+
whatsapp: ["whatsapp"],
|
|
350
|
+
google: ["google"],
|
|
351
|
+
ai: ["ai"],
|
|
352
|
+
email: [],
|
|
353
|
+
sms: [],
|
|
354
|
+
};
|
|
355
|
+
for (const integrationId of allIntegrations) {
|
|
356
|
+
const featuresToCopy = integrationFeatureMap[integrationId] || [];
|
|
357
|
+
for (const featureName of featuresToCopy) {
|
|
358
|
+
const src = path.join(repoDir, `packages/features/${featureName}`);
|
|
359
|
+
const dest = path.join(targetDir, `packages/features/${featureName}`);
|
|
360
|
+
if (await fs.pathExists(src) && !await fs.pathExists(dest)) {
|
|
361
|
+
await fs.copy(src, dest);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
366
|
+
// Copy Root Files
|
|
367
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
212
368
|
const rootFiles = [
|
|
213
369
|
"package.json",
|
|
214
370
|
"pnpm-workspace.yaml",
|
|
@@ -227,22 +383,41 @@ export async function createProject(projectName) {
|
|
|
227
383
|
await fs.copy(src, dest);
|
|
228
384
|
}
|
|
229
385
|
}
|
|
230
|
-
|
|
386
|
+
if (!isLocalMode) {
|
|
387
|
+
await fs.remove(path.dirname(repoDir));
|
|
388
|
+
}
|
|
231
389
|
console.log(pc.green(" done"));
|
|
390
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
391
|
+
// Update Package Names
|
|
392
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
393
|
+
process.stdout.write(pc.dim(" Updating package names..."));
|
|
394
|
+
await replacePackageNames(targetDir, slug);
|
|
395
|
+
console.log(pc.green(" done"));
|
|
396
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
397
|
+
// Replace Placeholders
|
|
398
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
399
|
+
const primaryApp = productSelections[0].app;
|
|
400
|
+
const allPlatforms = [...new Set(productSelections.flatMap((s) => s.platforms))];
|
|
232
401
|
const placeholders = {
|
|
233
402
|
"${PROJECT_NAME}": name,
|
|
234
403
|
"${PROJECT_CODE}": code,
|
|
235
404
|
"${PROJECT_SLUG}": slug,
|
|
236
405
|
"${DATABASE_NAME}": databaseName,
|
|
237
|
-
"${
|
|
406
|
+
"${APP_ID}": primaryApp.id,
|
|
407
|
+
"${APP_NAME}": primaryApp.name,
|
|
408
|
+
"${PRODUCT_TYPE}": primaryApp.id,
|
|
238
409
|
"${RESPONSE_LANGUAGE}": LANGUAGE_RESPONSES[language] || "Turkish",
|
|
239
410
|
};
|
|
240
411
|
process.stdout.write(pc.dim(" Replacing placeholders..."));
|
|
241
412
|
await replacePlaceholders(targetDir, placeholders);
|
|
242
413
|
console.log(pc.green(" done"));
|
|
414
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
415
|
+
// Create Feature Docs
|
|
416
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
243
417
|
for (const feature of features) {
|
|
244
418
|
const featureDoc = path.join(targetDir, "docs", "bible", "features", `${feature}.md`);
|
|
245
|
-
|
|
419
|
+
if (!await fs.pathExists(featureDoc)) {
|
|
420
|
+
const content = `# Feature: ${feature.charAt(0).toUpperCase() + feature.slice(1)}
|
|
246
421
|
|
|
247
422
|
## Overview
|
|
248
423
|
|
|
@@ -275,20 +450,48 @@ See \`docs/bible/data/schema.md\`
|
|
|
275
450
|
|----|----------|-----------|
|
|
276
451
|
| T-xxx | [Decision] | [Why] |
|
|
277
452
|
`;
|
|
278
|
-
|
|
279
|
-
|
|
453
|
+
await fs.outputFile(featureDoc, content);
|
|
454
|
+
console.log(pc.dim(` Created docs/bible/features/${feature}.md`));
|
|
455
|
+
}
|
|
280
456
|
}
|
|
457
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
458
|
+
// Generate Multi-Product Config (apps.config.yaml)
|
|
459
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
460
|
+
const appsConfigContent = generateAppsConfigYaml(projectConfig);
|
|
461
|
+
await fs.outputFile(path.join(targetDir, "config", "apps.config.yaml"), appsConfigContent);
|
|
462
|
+
console.log(pc.dim(" Created config/apps.config.yaml (multi-product config)"));
|
|
463
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
464
|
+
// Update nyoworks.config.yaml
|
|
465
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
281
466
|
const configPath = path.join(targetDir, "nyoworks.config.yaml");
|
|
282
467
|
if (await fs.pathExists(configPath)) {
|
|
283
|
-
let
|
|
468
|
+
let configContent = await fs.readFile(configPath, "utf8");
|
|
284
469
|
if (features.length > 0) {
|
|
285
|
-
|
|
470
|
+
configContent = configContent.replace(/enabled: \[\]/, `enabled:\n${features.map((f) => ` - ${f}`).join("\n")}`);
|
|
286
471
|
}
|
|
287
|
-
|
|
288
|
-
await fs.writeFile(configPath,
|
|
472
|
+
configContent = configContent.replace(/targets:\n - web/, `targets:\n${allPlatforms.map((p) => ` - ${p}`).join("\n")}`);
|
|
473
|
+
await fs.writeFile(configPath, configContent);
|
|
474
|
+
}
|
|
475
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
476
|
+
// Update pnpm-workspace.yaml
|
|
477
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
478
|
+
const workspacePath = path.join(targetDir, "pnpm-workspace.yaml");
|
|
479
|
+
if (await fs.pathExists(workspacePath)) {
|
|
480
|
+
const workspaceContent = `packages:
|
|
481
|
+
- "apps/server"
|
|
482
|
+
- "apps/*/*"
|
|
483
|
+
- "packages/*"
|
|
484
|
+
- "packages/features/*"
|
|
485
|
+
- "packages/platforms/*"
|
|
486
|
+
- "mcp-server"
|
|
487
|
+
`;
|
|
488
|
+
await fs.writeFile(workspacePath, workspaceContent);
|
|
289
489
|
}
|
|
490
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
491
|
+
// Success Message
|
|
492
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
290
493
|
console.log();
|
|
291
|
-
console.log(pc.green(pc.bold("
|
|
494
|
+
console.log(pc.green(pc.bold("Multi-product project created successfully!")));
|
|
292
495
|
await checkDependencies();
|
|
293
496
|
const dockerCmd = await getDockerComposeCommand();
|
|
294
497
|
showClaudeMaxWarning();
|
|
@@ -307,11 +510,19 @@ See \`docs/bible/data/schema.md\`
|
|
|
307
510
|
console.log(pc.dim(" Configuration:"));
|
|
308
511
|
console.log(pc.dim(` Name: ${name}`));
|
|
309
512
|
console.log(pc.dim(` Code: ${code}`));
|
|
310
|
-
console.log(pc.dim(
|
|
311
|
-
|
|
513
|
+
console.log(pc.dim(" Products:"));
|
|
514
|
+
for (const selection of productSelections) {
|
|
515
|
+
console.log(pc.dim(` - ${selection.app.name} (${selection.app.id}): ${selection.platforms.join(", ")}`));
|
|
516
|
+
}
|
|
312
517
|
console.log(pc.dim(` Features: ${features.join(", ") || "none"}`));
|
|
518
|
+
if (Object.keys(selectedProviders).length > 0) {
|
|
519
|
+
console.log(pc.dim(` Providers: ${Object.entries(selectedProviders).map(([k, v]) => `${k}:${v}`).join(", ")}`));
|
|
520
|
+
}
|
|
313
521
|
console.log(pc.dim(` Language: ${LANGUAGE_RESPONSES[language] || "Turkish"}`));
|
|
314
522
|
console.log();
|
|
523
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
524
|
+
// Build MCP Server
|
|
525
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
315
526
|
process.stdout.write(pc.dim(" Building MCP server..."));
|
|
316
527
|
const mcpPath = path.join(targetDir, "mcp-server");
|
|
317
528
|
try {
|
|
@@ -341,3 +552,59 @@ See \`docs/bible/data/schema.md\`
|
|
|
341
552
|
await fs.outputJson(mcpConfigPath, mcpConfig, { spaces: 2 });
|
|
342
553
|
console.log(pc.dim(" Created .claude/settings.local.json"));
|
|
343
554
|
}
|
|
555
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
556
|
+
// Generate Multi-Product Config YAML
|
|
557
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
558
|
+
function generateAppsConfigYaml(config) {
|
|
559
|
+
const productsYaml = config.products.map((p) => ` ${p.app.id}:
|
|
560
|
+
name: "${p.app.name}"
|
|
561
|
+
name_tr: "${p.app.name_tr || p.app.name}"
|
|
562
|
+
platforms:
|
|
563
|
+
${p.platforms.map((pl) => ` - ${pl}`).join("\n")}
|
|
564
|
+
features:
|
|
565
|
+
${getAppFeatures(p.app).map((f) => ` - ${f}`).join("\n")}`).join("\n\n");
|
|
566
|
+
const providersYaml = Object.entries(config.providers)
|
|
567
|
+
.map(([k, v]) => ` ${k}: "${v}"`)
|
|
568
|
+
.join("\n");
|
|
569
|
+
return `# ═══════════════════════════════════════════════════════════════════════════════
|
|
570
|
+
# ${config.name} - Multi-Product Configuration
|
|
571
|
+
# ═══════════════════════════════════════════════════════════════════════════════
|
|
572
|
+
|
|
573
|
+
project:
|
|
574
|
+
name: "${config.name}"
|
|
575
|
+
code: "${config.code}"
|
|
576
|
+
slug: "${config.slug}"
|
|
577
|
+
database: "${config.databaseName}"
|
|
578
|
+
language: "${config.language}"
|
|
579
|
+
|
|
580
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
581
|
+
# Products (Apps)
|
|
582
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
583
|
+
|
|
584
|
+
products:
|
|
585
|
+
${productsYaml}
|
|
586
|
+
|
|
587
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
588
|
+
# Shared Features (across all products)
|
|
589
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
590
|
+
|
|
591
|
+
shared_features:
|
|
592
|
+
${config.features.map((f) => ` - ${f}`).join("\n")}
|
|
593
|
+
|
|
594
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
595
|
+
# Integration Providers
|
|
596
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
597
|
+
|
|
598
|
+
providers:
|
|
599
|
+
${providersYaml}
|
|
600
|
+
|
|
601
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
602
|
+
# Security Configuration
|
|
603
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
604
|
+
|
|
605
|
+
security:
|
|
606
|
+
rls_enabled: true
|
|
607
|
+
app_scoped_routes: true
|
|
608
|
+
branded_validators: true
|
|
609
|
+
`;
|
|
610
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-nyoworks",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.1.0",
|
|
4
4
|
"description": "Create a new NYOWORKS project",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -30,7 +30,8 @@
|
|
|
30
30
|
"execa": "^9.6.1",
|
|
31
31
|
"fs-extra": "^11.3.3",
|
|
32
32
|
"picocolors": "^1.1.1",
|
|
33
|
-
"prompts": "^2.4.2"
|
|
33
|
+
"prompts": "^2.4.2",
|
|
34
|
+
"yaml": "^2.8.2"
|
|
34
35
|
},
|
|
35
36
|
"devDependencies": {
|
|
36
37
|
"@types/fs-extra": "^11.0.4",
|