@doswiftly/cli 0.1.4 → 0.1.6
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/commands/deploy.d.ts +9 -0
- package/dist/commands/deploy.d.ts.map +1 -1
- package/dist/commands/deploy.js +40 -1
- package/dist/commands/deploy.js.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +7 -1
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/template.d.ts.map +1 -1
- package/dist/commands/template.js +388 -345
- package/dist/commands/template.js.map +1 -1
- package/dist/index.js +5 -1
- package/dist/index.js.map +1 -1
- package/dist/lib/config.d.ts +63 -1
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +25 -2
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/errors.d.ts.map +1 -1
- package/dist/lib/errors.js +4 -0
- package/dist/lib/errors.js.map +1 -1
- package/dist/lib/logger.d.ts +6 -0
- package/dist/lib/logger.d.ts.map +1 -1
- package/dist/lib/logger.js +11 -0
- package/dist/lib/logger.js.map +1 -1
- package/dist/lib/shared-api-client.d.ts.map +1 -1
- package/dist/lib/shared-api-client.js +14 -0
- package/dist/lib/shared-api-client.js.map +1 -1
- package/package.json +1 -1
- package/templates/storefront-nextjs/app/auth/register/page.tsx +0 -16
- package/templates/storefront-nextjs-shadcn/app/account/settings/page.tsx +1 -25
- package/templates/storefront-nextjs-shadcn/components/auth/register-form.tsx +0 -21
- package/dist/lib/api.d.ts +0 -67
- package/dist/lib/api.d.ts.map +0 -1
- package/dist/lib/api.js +0 -36
- package/dist/lib/api.js.map +0 -1
- package/templates/storefront-nextjs/.github/workflows/deploy.yml +0 -45
- package/templates/storefront-nextjs/.github/workflows/preview.yml +0 -45
- package/templates/storefront-nextjs-shadcn/.github/workflows/deploy.yml +0 -45
- package/templates/storefront-nextjs-shadcn/.github/workflows/preview.yml +0 -45
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import chalk from
|
|
2
|
-
import ora from
|
|
3
|
-
import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync, statSync } from
|
|
4
|
-
import { execSync, execFileSync } from
|
|
5
|
-
import { join, dirname } from
|
|
6
|
-
import { fileURLToPath } from
|
|
7
|
-
import * as p from
|
|
8
|
-
import { createSharedApiClient } from
|
|
9
|
-
import { logger } from
|
|
10
|
-
import { getStoredRole } from
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import ora from "ora";
|
|
3
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync, statSync, } from "fs";
|
|
4
|
+
import { execSync, execFileSync } from "child_process";
|
|
5
|
+
import { join, dirname } from "path";
|
|
6
|
+
import { fileURLToPath } from "url";
|
|
7
|
+
import * as p from "@clack/prompts";
|
|
8
|
+
import { createSharedApiClient } from "../lib/shared-api-client.js";
|
|
9
|
+
import { logger } from "../lib/logger.js";
|
|
10
|
+
import { getStoredRole } from "./auth.js";
|
|
11
11
|
const __filename = fileURLToPath(import.meta.url);
|
|
12
12
|
const __dirname = dirname(__filename);
|
|
13
13
|
/**
|
|
@@ -17,9 +17,9 @@ const __dirname = dirname(__filename);
|
|
|
17
17
|
*/
|
|
18
18
|
function requireSaasDeveloperRole() {
|
|
19
19
|
const role = getStoredRole()?.toLowerCase();
|
|
20
|
-
if (role && role !==
|
|
21
|
-
console.log(chalk.red(
|
|
22
|
-
console.log(chalk.gray(
|
|
20
|
+
if (role && role !== "admin" && role !== "saas_developer") {
|
|
21
|
+
console.log(chalk.red("\n Error: This command requires SaaS developer role."));
|
|
22
|
+
console.log(chalk.gray(" Your current role: " + role + "\n"));
|
|
23
23
|
process.exit(1);
|
|
24
24
|
}
|
|
25
25
|
}
|
|
@@ -27,18 +27,18 @@ async function apiRequest(endpoint, options = {}) {
|
|
|
27
27
|
// Template operations are platform-level, not shop-level
|
|
28
28
|
// They don't require doswiftly.config.ts with shop.slug
|
|
29
29
|
const client = createSharedApiClient({ requireShopSlug: false });
|
|
30
|
-
logger.debug(`API Request: ${options.method ||
|
|
30
|
+
logger.debug(`API Request: ${options.method || "GET"} ${endpoint}`);
|
|
31
31
|
try {
|
|
32
32
|
const response = await client.request({
|
|
33
33
|
url: endpoint,
|
|
34
|
-
method: (options.method ||
|
|
34
|
+
method: (options.method || "GET"),
|
|
35
35
|
data: options.body ? JSON.parse(options.body) : undefined,
|
|
36
36
|
});
|
|
37
37
|
return response.data;
|
|
38
38
|
}
|
|
39
39
|
catch (error) {
|
|
40
40
|
// Extract meaningful error message from axios error response
|
|
41
|
-
if (error && typeof error ===
|
|
41
|
+
if (error && typeof error === "object" && "response" in error) {
|
|
42
42
|
const axiosError = error;
|
|
43
43
|
const serverMessage = axiosError.response?.data?.message;
|
|
44
44
|
if (serverMessage) {
|
|
@@ -52,35 +52,35 @@ async function apiRequest(endpoint, options = {}) {
|
|
|
52
52
|
* List all available templates from the registry.
|
|
53
53
|
*/
|
|
54
54
|
export async function templateListCommand() {
|
|
55
|
-
console.log(chalk.bold.blue(
|
|
56
|
-
const spinner = ora(
|
|
55
|
+
console.log(chalk.bold.blue("\nDoSwiftly CLI - Template Registry\n"));
|
|
56
|
+
const spinner = ora("Fetching templates...").start();
|
|
57
57
|
try {
|
|
58
|
-
const templates = await apiRequest(
|
|
58
|
+
const templates = await apiRequest("/cli/templates");
|
|
59
59
|
spinner.stop();
|
|
60
60
|
if (!templates || templates.length === 0) {
|
|
61
|
-
console.log(chalk.yellow(
|
|
61
|
+
console.log(chalk.yellow(" No templates available.\n"));
|
|
62
62
|
return;
|
|
63
63
|
}
|
|
64
|
-
console.log(chalk.gray(
|
|
65
|
-
console.log(chalk.bold(
|
|
66
|
-
chalk.bold(
|
|
67
|
-
chalk.bold(
|
|
68
|
-
chalk.bold(
|
|
69
|
-
chalk.bold(
|
|
70
|
-
console.log(chalk.gray(
|
|
64
|
+
console.log(chalk.gray("─".repeat(80)));
|
|
65
|
+
console.log(chalk.bold(" Name".padEnd(25)) +
|
|
66
|
+
chalk.bold("Version".padEnd(12)) +
|
|
67
|
+
chalk.bold("UI Library".padEnd(15)) +
|
|
68
|
+
chalk.bold("Status".padEnd(12)) +
|
|
69
|
+
chalk.bold("Description"));
|
|
70
|
+
console.log(chalk.gray("─".repeat(80)));
|
|
71
71
|
for (const t of templates) {
|
|
72
|
-
const statusColor = t.status ===
|
|
72
|
+
const statusColor = t.status === "PUBLISHED" ? chalk.green : chalk.yellow;
|
|
73
73
|
console.log(` ${chalk.cyan(t.name.padEnd(25))}` +
|
|
74
74
|
`${chalk.gray(t.version.padEnd(12))}` +
|
|
75
|
-
`${chalk.gray((t.uiLibrary ||
|
|
75
|
+
`${chalk.gray((t.uiLibrary || "-").padEnd(15))}` +
|
|
76
76
|
`${statusColor(t.status.padEnd(12))}` +
|
|
77
|
-
`${chalk.gray((t.description ||
|
|
77
|
+
`${chalk.gray((t.description || "").slice(0, 30))}`);
|
|
78
78
|
}
|
|
79
|
-
console.log(chalk.gray(
|
|
80
|
-
console.log(
|
|
79
|
+
console.log(chalk.gray("─".repeat(80)));
|
|
80
|
+
console.log("");
|
|
81
81
|
}
|
|
82
82
|
catch (error) {
|
|
83
|
-
spinner.fail(
|
|
83
|
+
spinner.fail("Failed to fetch templates");
|
|
84
84
|
console.log(chalk.red(`\n${error.message}\n`));
|
|
85
85
|
process.exit(1);
|
|
86
86
|
}
|
|
@@ -89,38 +89,38 @@ export async function templateListCommand() {
|
|
|
89
89
|
* Show template details.
|
|
90
90
|
*/
|
|
91
91
|
export async function templateInfoCommand(name) {
|
|
92
|
-
console.log(chalk.bold.blue(
|
|
92
|
+
console.log(chalk.bold.blue("\nDoSwiftly CLI - Template Info\n"));
|
|
93
93
|
if (!name) {
|
|
94
|
-
console.log(chalk.red(
|
|
94
|
+
console.log(chalk.red(" Error: Template name required\n"));
|
|
95
95
|
process.exit(1);
|
|
96
96
|
}
|
|
97
97
|
const spinner = ora(`Fetching template '${name}'...`).start();
|
|
98
98
|
try {
|
|
99
99
|
const t = await apiRequest(`/cli/templates/${encodeURIComponent(name)}`);
|
|
100
100
|
spinner.stop();
|
|
101
|
-
console.log(` ${chalk.bold(
|
|
102
|
-
console.log(` ${chalk.bold(
|
|
103
|
-
console.log(` ${chalk.bold(
|
|
104
|
-
console.log(` ${chalk.bold(
|
|
101
|
+
console.log(` ${chalk.bold("Name:")} ${chalk.cyan(t.name)}`);
|
|
102
|
+
console.log(` ${chalk.bold("Version:")} ${t.version}`);
|
|
103
|
+
console.log(` ${chalk.bold("Status:")} ${t.status}`);
|
|
104
|
+
console.log(` ${chalk.bold("Visibility:")} ${t.visibility}`);
|
|
105
105
|
if (t.description) {
|
|
106
|
-
console.log(` ${chalk.bold(
|
|
106
|
+
console.log(` ${chalk.bold("Description:")} ${t.description}`);
|
|
107
107
|
}
|
|
108
108
|
if (t.uiLibrary) {
|
|
109
|
-
console.log(` ${chalk.bold(
|
|
109
|
+
console.log(` ${chalk.bold("UI Library:")} ${t.uiLibrary}`);
|
|
110
110
|
}
|
|
111
111
|
if (t.features.length > 0) {
|
|
112
|
-
console.log(` ${chalk.bold(
|
|
112
|
+
console.log(` ${chalk.bold("Features:")} ${t.features.join(", ")}`);
|
|
113
113
|
}
|
|
114
114
|
if (t.repoUrl) {
|
|
115
|
-
console.log(` ${chalk.bold(
|
|
115
|
+
console.log(` ${chalk.bold("Repository:")} ${chalk.cyan(t.repoUrl)}`);
|
|
116
116
|
}
|
|
117
117
|
if (t.previewUrl) {
|
|
118
|
-
console.log(` ${chalk.bold(
|
|
118
|
+
console.log(` ${chalk.bold("Preview:")} ${chalk.cyan(t.previewUrl)}`);
|
|
119
119
|
}
|
|
120
|
-
console.log(
|
|
120
|
+
console.log("");
|
|
121
121
|
}
|
|
122
122
|
catch (error) {
|
|
123
|
-
spinner.fail(
|
|
123
|
+
spinner.fail("Failed to fetch template");
|
|
124
124
|
console.log(chalk.red(`\n${error.message}\n`));
|
|
125
125
|
process.exit(1);
|
|
126
126
|
}
|
|
@@ -129,10 +129,10 @@ export async function templateInfoCommand(name) {
|
|
|
129
129
|
* Read doswiftly-template.json manifest from current directory.
|
|
130
130
|
*/
|
|
131
131
|
function readTemplateManifest() {
|
|
132
|
-
if (!existsSync(
|
|
132
|
+
if (!existsSync("doswiftly-template.json"))
|
|
133
133
|
return null;
|
|
134
134
|
try {
|
|
135
|
-
return JSON.parse(readFileSync(
|
|
135
|
+
return JSON.parse(readFileSync("doswiftly-template.json", "utf-8"));
|
|
136
136
|
}
|
|
137
137
|
catch {
|
|
138
138
|
return null;
|
|
@@ -144,15 +144,20 @@ function readTemplateManifest() {
|
|
|
144
144
|
*/
|
|
145
145
|
function detectGitRemoteUrl() {
|
|
146
146
|
try {
|
|
147
|
-
execSync(
|
|
148
|
-
const url = execSync(
|
|
147
|
+
execSync("git rev-parse --git-dir", { stdio: "pipe" });
|
|
148
|
+
const url = execSync("git remote get-url origin", {
|
|
149
|
+
encoding: "utf-8",
|
|
150
|
+
stdio: "pipe",
|
|
151
|
+
}).trim();
|
|
149
152
|
if (!url)
|
|
150
153
|
return null;
|
|
151
154
|
// Convert SSH to HTTPS
|
|
152
|
-
if (url.startsWith(
|
|
153
|
-
return url
|
|
155
|
+
if (url.startsWith("git@github.com:")) {
|
|
156
|
+
return url
|
|
157
|
+
.replace("git@github.com:", "https://github.com/")
|
|
158
|
+
.replace(/\.git$/, "");
|
|
154
159
|
}
|
|
155
|
-
return url.replace(/\.git$/,
|
|
160
|
+
return url.replace(/\.git$/, "");
|
|
156
161
|
}
|
|
157
162
|
catch {
|
|
158
163
|
return null;
|
|
@@ -163,7 +168,7 @@ function detectGitRemoteUrl() {
|
|
|
163
168
|
*/
|
|
164
169
|
function isGitInitialized() {
|
|
165
170
|
try {
|
|
166
|
-
execSync(
|
|
171
|
+
execSync("git rev-parse --git-dir", { stdio: "pipe" });
|
|
167
172
|
return true;
|
|
168
173
|
}
|
|
169
174
|
catch {
|
|
@@ -180,28 +185,28 @@ function isGitInitialized() {
|
|
|
180
185
|
*/
|
|
181
186
|
export async function templateRegisterCommand(options = {}) {
|
|
182
187
|
requireSaasDeveloperRole();
|
|
183
|
-
console.log(chalk.bold.blue(
|
|
188
|
+
console.log(chalk.bold.blue("\nDoSwiftly CLI - Register Template\n"));
|
|
184
189
|
// Validate project structure
|
|
185
|
-
if (!existsSync(
|
|
186
|
-
console.log(chalk.red(
|
|
190
|
+
if (!existsSync("package.json")) {
|
|
191
|
+
console.log(chalk.red(" Error: No package.json found. Run this from a template project root.\n"));
|
|
187
192
|
process.exit(1);
|
|
188
193
|
}
|
|
189
194
|
// Check git is initialized
|
|
190
195
|
if (!isGitInitialized()) {
|
|
191
|
-
console.log(chalk.red(
|
|
196
|
+
console.log(chalk.red(" Error: Git is not initialized in this directory."));
|
|
192
197
|
console.log(chalk.gray(' Run "git init" or "doswiftly init --create-template" first.\n'));
|
|
193
198
|
process.exit(1);
|
|
194
199
|
}
|
|
195
200
|
// Auto-detect from manifest
|
|
196
201
|
const manifest = readTemplateManifest();
|
|
197
202
|
if (manifest) {
|
|
198
|
-
console.log(chalk.gray(
|
|
203
|
+
console.log(chalk.gray(" Auto-detected doswiftly-template.json"));
|
|
199
204
|
}
|
|
200
205
|
const name = options.name || manifest?.name || null;
|
|
201
206
|
const description = options.description || manifest?.description || undefined;
|
|
202
207
|
const uiLibrary = options.uiLibrary || manifest?.uiLibrary || undefined;
|
|
203
208
|
if (!name) {
|
|
204
|
-
console.log(chalk.red(
|
|
209
|
+
console.log(chalk.red(" Error: Template name required."));
|
|
205
210
|
console.log(chalk.gray(' Use --name or add "name" to doswiftly-template.json\n'));
|
|
206
211
|
process.exit(1);
|
|
207
212
|
}
|
|
@@ -211,10 +216,10 @@ export async function templateRegisterCommand(options = {}) {
|
|
|
211
216
|
console.log(chalk.gray(` Git remote: ${repoUrl}`));
|
|
212
217
|
}
|
|
213
218
|
else {
|
|
214
|
-
console.log(chalk.yellow(
|
|
219
|
+
console.log(chalk.yellow(" Warning: No git remote detected. Use --repo-url to specify."));
|
|
215
220
|
}
|
|
216
221
|
// Show summary
|
|
217
|
-
console.log(chalk.gray(
|
|
222
|
+
console.log(chalk.gray("\n Registration summary:"));
|
|
218
223
|
console.log(chalk.gray(` Name: ${chalk.cyan(name)}`));
|
|
219
224
|
if (description)
|
|
220
225
|
console.log(chalk.gray(` Description: ${description}`));
|
|
@@ -222,11 +227,11 @@ export async function templateRegisterCommand(options = {}) {
|
|
|
222
227
|
console.log(chalk.gray(` Repo URL: ${repoUrl}`));
|
|
223
228
|
if (uiLibrary)
|
|
224
229
|
console.log(chalk.gray(` UI Library: ${uiLibrary}`));
|
|
225
|
-
console.log(
|
|
230
|
+
console.log("");
|
|
226
231
|
const spinner = ora(`Registering template '${name}'...`).start();
|
|
227
232
|
try {
|
|
228
|
-
const template = await apiRequest(
|
|
229
|
-
method:
|
|
233
|
+
const template = await apiRequest("/cli/templates", {
|
|
234
|
+
method: "POST",
|
|
230
235
|
body: JSON.stringify({
|
|
231
236
|
name,
|
|
232
237
|
description,
|
|
@@ -237,13 +242,15 @@ export async function templateRegisterCommand(options = {}) {
|
|
|
237
242
|
spinner.succeed(`Template '${template.name}' registered`);
|
|
238
243
|
console.log(chalk.gray(`\n ID: ${template.id}`));
|
|
239
244
|
console.log(chalk.gray(` Status: ${template.status}`));
|
|
240
|
-
console.log(chalk.gray(
|
|
245
|
+
console.log(chalk.gray("\n Next steps:"));
|
|
241
246
|
console.log(chalk.gray(' 1. Set version in package.json (e.g., "version": "1.0.0")'));
|
|
242
|
-
console.log(chalk.gray(
|
|
243
|
-
console.log(chalk.gray(
|
|
247
|
+
console.log(chalk.gray(" 2. Publish: doswiftly template publish"));
|
|
248
|
+
console.log(chalk.gray(" 3. Set visibility: doswiftly template visibility " +
|
|
249
|
+
template.name +
|
|
250
|
+
" PUBLIC\n"));
|
|
244
251
|
}
|
|
245
252
|
catch (error) {
|
|
246
|
-
spinner.fail(
|
|
253
|
+
spinner.fail("Failed to register template");
|
|
247
254
|
console.log(chalk.red(`\n${error.message}\n`));
|
|
248
255
|
process.exit(1);
|
|
249
256
|
}
|
|
@@ -253,9 +260,9 @@ export async function templateRegisterCommand(options = {}) {
|
|
|
253
260
|
*/
|
|
254
261
|
export async function templateUpdateCommand(nameOrId, options = {}) {
|
|
255
262
|
requireSaasDeveloperRole();
|
|
256
|
-
console.log(chalk.bold.blue(
|
|
263
|
+
console.log(chalk.bold.blue("\nDoSwiftly CLI - Update Template\n"));
|
|
257
264
|
if (!nameOrId) {
|
|
258
|
-
console.log(chalk.red(
|
|
265
|
+
console.log(chalk.red(" Error: Template name or ID required\n"));
|
|
259
266
|
process.exit(1);
|
|
260
267
|
}
|
|
261
268
|
const body = {};
|
|
@@ -270,13 +277,13 @@ export async function templateUpdateCommand(nameOrId, options = {}) {
|
|
|
270
277
|
if (options.visibility !== undefined)
|
|
271
278
|
body.visibility = options.visibility;
|
|
272
279
|
if (Object.keys(body).length === 0) {
|
|
273
|
-
console.log(chalk.yellow(
|
|
280
|
+
console.log(chalk.yellow(" No fields to update. Use --description, --repo-url, --ui-library, --preview-url, or --visibility.\n"));
|
|
274
281
|
process.exit(1);
|
|
275
282
|
}
|
|
276
283
|
const spinner = ora(`Updating template '${nameOrId}'...`).start();
|
|
277
284
|
try {
|
|
278
285
|
const template = await apiRequest(`/cli/templates/${encodeURIComponent(nameOrId)}`, {
|
|
279
|
-
method:
|
|
286
|
+
method: "PUT",
|
|
280
287
|
body: JSON.stringify(body),
|
|
281
288
|
});
|
|
282
289
|
spinner.succeed(`Template '${template.name}' updated`);
|
|
@@ -286,10 +293,10 @@ export async function templateUpdateCommand(nameOrId, options = {}) {
|
|
|
286
293
|
if (template.repoUrl)
|
|
287
294
|
console.log(chalk.gray(` Repository: ${template.repoUrl}`));
|
|
288
295
|
console.log(chalk.gray(` Visibility: ${template.visibility}`));
|
|
289
|
-
console.log(
|
|
296
|
+
console.log("");
|
|
290
297
|
}
|
|
291
298
|
catch (error) {
|
|
292
|
-
spinner.fail(
|
|
299
|
+
spinner.fail("Failed to update template");
|
|
293
300
|
console.log(chalk.red(`\n${error.message}\n`));
|
|
294
301
|
process.exit(1);
|
|
295
302
|
}
|
|
@@ -298,10 +305,10 @@ export async function templateUpdateCommand(nameOrId, options = {}) {
|
|
|
298
305
|
* Read version from package.json in current directory.
|
|
299
306
|
*/
|
|
300
307
|
function readPackageVersion() {
|
|
301
|
-
if (!existsSync(
|
|
308
|
+
if (!existsSync("package.json"))
|
|
302
309
|
return null;
|
|
303
310
|
try {
|
|
304
|
-
const pkg = JSON.parse(readFileSync(
|
|
311
|
+
const pkg = JSON.parse(readFileSync("package.json", "utf-8"));
|
|
305
312
|
return pkg.version || null;
|
|
306
313
|
}
|
|
307
314
|
catch {
|
|
@@ -319,13 +326,13 @@ function readPackageVersion() {
|
|
|
319
326
|
*/
|
|
320
327
|
export async function templatePublishCommand(nameOrId) {
|
|
321
328
|
requireSaasDeveloperRole();
|
|
322
|
-
console.log(chalk.bold.blue(
|
|
329
|
+
console.log(chalk.bold.blue("\nDoSwiftly CLI - Publish Template\n"));
|
|
323
330
|
// Auto-detect from manifest if nameOrId not provided
|
|
324
331
|
const manifest = readTemplateManifest();
|
|
325
332
|
const resolvedName = nameOrId || manifest?.name || null;
|
|
326
333
|
if (!resolvedName) {
|
|
327
|
-
console.log(chalk.red(
|
|
328
|
-
console.log(chalk.gray(
|
|
334
|
+
console.log(chalk.red(" Error: Template name required."));
|
|
335
|
+
console.log(chalk.gray(" Provide name as argument or run from a directory with doswiftly-template.json\n"));
|
|
329
336
|
process.exit(1);
|
|
330
337
|
}
|
|
331
338
|
// Read version from package.json (primary) or doswiftly-template.json (fallback)
|
|
@@ -333,31 +340,33 @@ export async function templatePublishCommand(nameOrId) {
|
|
|
333
340
|
const manifestVersion = manifest?.version;
|
|
334
341
|
const version = packageVersion || manifestVersion;
|
|
335
342
|
if (!version) {
|
|
336
|
-
console.log(chalk.red(
|
|
343
|
+
console.log(chalk.red(" Error: No version found."));
|
|
337
344
|
console.log(chalk.gray(' Set "version" in package.json (recommended) or doswiftly-template.json\n'));
|
|
338
345
|
process.exit(1);
|
|
339
346
|
}
|
|
340
347
|
// Show version source for transparency
|
|
341
|
-
const versionSource = packageVersion
|
|
348
|
+
const versionSource = packageVersion
|
|
349
|
+
? "package.json"
|
|
350
|
+
: "doswiftly-template.json";
|
|
342
351
|
console.log(chalk.gray(` Version: ${chalk.cyan(version)} (from ${versionSource})`));
|
|
343
|
-
console.log(
|
|
352
|
+
console.log("");
|
|
344
353
|
const spinner = ora(`Publishing template '${resolvedName}' v${version}...`).start();
|
|
345
354
|
try {
|
|
346
355
|
const template = await apiRequest(`/cli/templates/${encodeURIComponent(resolvedName)}/version`, {
|
|
347
|
-
method:
|
|
356
|
+
method: "PUT",
|
|
348
357
|
body: JSON.stringify({ version }),
|
|
349
358
|
});
|
|
350
359
|
spinner.succeed(`Template '${template.name}' v${template.version} published`);
|
|
351
360
|
console.log(chalk.gray(`\n Status: ${template.status}`));
|
|
352
361
|
console.log(chalk.gray(` Visibility: ${template.visibility}`));
|
|
353
|
-
if (template.visibility ===
|
|
354
|
-
console.log(chalk.yellow(
|
|
362
|
+
if (template.visibility === "PRIVATE") {
|
|
363
|
+
console.log(chalk.yellow("\n Note: Template is PRIVATE. Set visibility to PUBLIC for users to see it:"));
|
|
355
364
|
console.log(chalk.cyan(` doswiftly template visibility ${template.name} PUBLIC`));
|
|
356
365
|
}
|
|
357
|
-
console.log(
|
|
366
|
+
console.log("");
|
|
358
367
|
}
|
|
359
368
|
catch (error) {
|
|
360
|
-
spinner.fail(
|
|
369
|
+
spinner.fail("Failed to publish template");
|
|
361
370
|
console.log(chalk.red(`\n${error.message}\n`));
|
|
362
371
|
process.exit(1);
|
|
363
372
|
}
|
|
@@ -368,21 +377,21 @@ export async function templatePublishCommand(nameOrId) {
|
|
|
368
377
|
*/
|
|
369
378
|
export async function templateVersionCommand(nameOrId, version) {
|
|
370
379
|
console.log(chalk.yellow('\n ⚠️ "template version" is deprecated. Use "template publish" instead.'));
|
|
371
|
-
console.log(chalk.gray(
|
|
380
|
+
console.log(chalk.gray(" The version is now read automatically from package.json.\n"));
|
|
372
381
|
// For backward compatibility, still allow manual version if provided
|
|
373
382
|
requireSaasDeveloperRole();
|
|
374
383
|
const spinner = ora(`Publishing template '${nameOrId}' v${version}...`).start();
|
|
375
384
|
try {
|
|
376
385
|
const template = await apiRequest(`/cli/templates/${encodeURIComponent(nameOrId)}/version`, {
|
|
377
|
-
method:
|
|
386
|
+
method: "PUT",
|
|
378
387
|
body: JSON.stringify({ version }),
|
|
379
388
|
});
|
|
380
389
|
spinner.succeed(`Template '${template.name}' v${template.version} published`);
|
|
381
390
|
console.log(chalk.gray(`\n Status: ${template.status}`));
|
|
382
|
-
console.log(
|
|
391
|
+
console.log("");
|
|
383
392
|
}
|
|
384
393
|
catch (error) {
|
|
385
|
-
spinner.fail(
|
|
394
|
+
spinner.fail("Failed to publish template");
|
|
386
395
|
console.log(chalk.red(`\n${error.message}\n`));
|
|
387
396
|
process.exit(1);
|
|
388
397
|
}
|
|
@@ -392,21 +401,21 @@ export async function templateVersionCommand(nameOrId, version) {
|
|
|
392
401
|
*/
|
|
393
402
|
export async function templateUnpublishCommand(name) {
|
|
394
403
|
requireSaasDeveloperRole();
|
|
395
|
-
console.log(chalk.bold.blue(
|
|
404
|
+
console.log(chalk.bold.blue("\nDoSwiftly CLI - Unpublish Template\n"));
|
|
396
405
|
if (!name) {
|
|
397
|
-
console.log(chalk.red(
|
|
406
|
+
console.log(chalk.red(" Error: Template name required\n"));
|
|
398
407
|
process.exit(1);
|
|
399
408
|
}
|
|
400
409
|
const spinner = ora(`Unpublishing template '${name}'...`).start();
|
|
401
410
|
try {
|
|
402
411
|
await apiRequest(`/cli/templates/${encodeURIComponent(name)}/unpublish`, {
|
|
403
|
-
method:
|
|
412
|
+
method: "POST",
|
|
404
413
|
});
|
|
405
414
|
spinner.succeed(`Template '${name}' unpublished`);
|
|
406
|
-
console.log(chalk.yellow(
|
|
415
|
+
console.log(chalk.yellow("\n Template has been deprecated and will no longer appear in listings.\n"));
|
|
407
416
|
}
|
|
408
417
|
catch (error) {
|
|
409
|
-
spinner.fail(
|
|
418
|
+
spinner.fail("Failed to unpublish template");
|
|
410
419
|
console.log(chalk.red(`\n${error.message}\n`));
|
|
411
420
|
process.exit(1);
|
|
412
421
|
}
|
|
@@ -417,12 +426,12 @@ export async function templateUnpublishCommand(name) {
|
|
|
417
426
|
*/
|
|
418
427
|
export async function templateVisibilityCommand(nameOrId, visibility) {
|
|
419
428
|
if (!nameOrId) {
|
|
420
|
-
console.log(chalk.red(
|
|
429
|
+
console.log(chalk.red("\n Error: Template name or ID required\n"));
|
|
421
430
|
process.exit(1);
|
|
422
431
|
}
|
|
423
432
|
const normalizedVis = visibility?.toUpperCase();
|
|
424
|
-
if (!normalizedVis || ![
|
|
425
|
-
console.log(chalk.red(
|
|
433
|
+
if (!normalizedVis || !["PUBLIC", "PRIVATE"].includes(normalizedVis)) {
|
|
434
|
+
console.log(chalk.red("\n Error: Visibility must be PUBLIC or PRIVATE\n"));
|
|
426
435
|
process.exit(1);
|
|
427
436
|
}
|
|
428
437
|
return templateUpdateCommand(nameOrId, { visibility: normalizedVis });
|
|
@@ -441,9 +450,9 @@ function copyTemplateDir(src, dest, replacements) {
|
|
|
441
450
|
copyTemplateDir(srcPath, destPath, replacements);
|
|
442
451
|
}
|
|
443
452
|
else {
|
|
444
|
-
let content = readFileSync(srcPath,
|
|
453
|
+
let content = readFileSync(srcPath, "utf-8");
|
|
445
454
|
for (const [placeholder, value] of Object.entries(replacements)) {
|
|
446
|
-
content = content.replace(new RegExp(placeholder,
|
|
455
|
+
content = content.replace(new RegExp(placeholder, "g"), value);
|
|
447
456
|
}
|
|
448
457
|
writeFileSync(destPath, content);
|
|
449
458
|
}
|
|
@@ -458,19 +467,21 @@ function readAllFiles(dir, baseDir = dir) {
|
|
|
458
467
|
const entries = readdirSync(dir);
|
|
459
468
|
for (const entry of entries) {
|
|
460
469
|
const fullPath = join(dir, entry);
|
|
461
|
-
const relativePath = fullPath
|
|
470
|
+
const relativePath = fullPath
|
|
471
|
+
.replace(baseDir + "/", "")
|
|
472
|
+
.replace(baseDir + "\\", "");
|
|
462
473
|
const stat = statSync(fullPath);
|
|
463
474
|
if (stat.isDirectory()) {
|
|
464
475
|
// Skip .git directory
|
|
465
|
-
if (entry ===
|
|
476
|
+
if (entry === ".git")
|
|
466
477
|
continue;
|
|
467
478
|
files.push(...readAllFiles(fullPath, baseDir));
|
|
468
479
|
}
|
|
469
480
|
else {
|
|
470
481
|
try {
|
|
471
|
-
const content = readFileSync(fullPath,
|
|
482
|
+
const content = readFileSync(fullPath, "utf-8");
|
|
472
483
|
// Use forward slashes for GitHub API
|
|
473
|
-
files.push({ path: relativePath.replace(/\\/g,
|
|
484
|
+
files.push({ path: relativePath.replace(/\\/g, "/"), content });
|
|
474
485
|
}
|
|
475
486
|
catch {
|
|
476
487
|
// Skip binary files or unreadable files
|
|
@@ -485,108 +496,110 @@ function readAllFiles(dir, baseDir = dir) {
|
|
|
485
496
|
*/
|
|
486
497
|
export async function templateCreateCommand(options = {}) {
|
|
487
498
|
requireSaasDeveloperRole();
|
|
488
|
-
console.log(chalk.bold.blue(
|
|
499
|
+
console.log(chalk.bold.blue("\nDoSwiftly CLI - Create Template\n"));
|
|
489
500
|
// Step 1: Template name
|
|
490
501
|
let templateName = options.name;
|
|
491
502
|
if (!templateName) {
|
|
492
503
|
const nameResult = await p.text({
|
|
493
|
-
message:
|
|
494
|
-
placeholder:
|
|
504
|
+
message: "Template name:",
|
|
505
|
+
placeholder: "my-awesome-template",
|
|
495
506
|
validate: (v) => {
|
|
496
507
|
if (!v)
|
|
497
|
-
return
|
|
508
|
+
return "Name is required";
|
|
498
509
|
if (!/^[a-z0-9-]+$/.test(v))
|
|
499
|
-
return
|
|
510
|
+
return "Use lowercase letters, numbers, and hyphens only";
|
|
500
511
|
if (v.length < 3)
|
|
501
|
-
return
|
|
512
|
+
return "At least 3 characters";
|
|
502
513
|
return undefined;
|
|
503
514
|
},
|
|
504
515
|
});
|
|
505
516
|
if (p.isCancel(nameResult)) {
|
|
506
|
-
console.log(chalk.yellow(
|
|
517
|
+
console.log(chalk.yellow("\n Operation cancelled.\n"));
|
|
507
518
|
process.exit(0);
|
|
508
519
|
}
|
|
509
520
|
templateName = nameResult;
|
|
510
521
|
}
|
|
511
522
|
// Validate name format
|
|
512
523
|
if (!/^[a-z0-9-]+$/.test(templateName)) {
|
|
513
|
-
console.log(chalk.red(
|
|
524
|
+
console.log(chalk.red("\n Error: Template name must be lowercase, alphanumeric, and may contain hyphens.\n"));
|
|
514
525
|
process.exit(1);
|
|
515
526
|
}
|
|
516
527
|
// Step 2: Description
|
|
517
528
|
let description = options.description;
|
|
518
529
|
if (!description) {
|
|
519
530
|
const descResult = await p.text({
|
|
520
|
-
message:
|
|
531
|
+
message: "Template description (optional):",
|
|
521
532
|
placeholder: `DoSwiftly storefront template: ${templateName}`,
|
|
522
533
|
});
|
|
523
534
|
if (p.isCancel(descResult)) {
|
|
524
|
-
console.log(chalk.yellow(
|
|
535
|
+
console.log(chalk.yellow("\n Operation cancelled.\n"));
|
|
525
536
|
process.exit(0);
|
|
526
537
|
}
|
|
527
|
-
description =
|
|
538
|
+
description =
|
|
539
|
+
descResult ||
|
|
540
|
+
`DoSwiftly storefront template: ${templateName}`;
|
|
528
541
|
}
|
|
529
542
|
// Step 3: Base template selection
|
|
530
543
|
let baseTemplate = options.base;
|
|
531
544
|
if (!baseTemplate) {
|
|
532
545
|
const baseResult = await p.select({
|
|
533
|
-
message:
|
|
546
|
+
message: "Base template to start from:",
|
|
534
547
|
options: [
|
|
535
548
|
{
|
|
536
|
-
value:
|
|
537
|
-
label:
|
|
538
|
-
hint:
|
|
549
|
+
value: "storefront-nextjs-shadcn",
|
|
550
|
+
label: "shadcn/ui (Recommended)",
|
|
551
|
+
hint: "Next.js + shadcn/ui + Tailwind CSS",
|
|
539
552
|
},
|
|
540
553
|
{
|
|
541
|
-
value:
|
|
542
|
-
label:
|
|
543
|
-
hint:
|
|
554
|
+
value: "storefront-minimal",
|
|
555
|
+
label: "Minimal (Next.js only)",
|
|
556
|
+
hint: "Minimal Next.js starter",
|
|
544
557
|
},
|
|
545
558
|
],
|
|
546
559
|
});
|
|
547
560
|
if (p.isCancel(baseResult)) {
|
|
548
|
-
console.log(chalk.yellow(
|
|
561
|
+
console.log(chalk.yellow("\n Operation cancelled.\n"));
|
|
549
562
|
process.exit(0);
|
|
550
563
|
}
|
|
551
564
|
baseTemplate = baseResult;
|
|
552
565
|
}
|
|
553
566
|
// UI Library mapping
|
|
554
567
|
const uiLibraryMap = {
|
|
555
|
-
|
|
556
|
-
|
|
568
|
+
"storefront-nextjs-shadcn": "shadcn/ui",
|
|
569
|
+
"storefront-minimal": "none",
|
|
557
570
|
};
|
|
558
|
-
const uiLibrary = uiLibraryMap[baseTemplate] ||
|
|
571
|
+
const uiLibrary = uiLibraryMap[baseTemplate] || "none";
|
|
559
572
|
// Step 4: GitHub repository creation
|
|
560
573
|
let createGitHubRepo = false;
|
|
561
574
|
let repoInfo = null;
|
|
562
575
|
let githubUsername;
|
|
563
576
|
if (!options.skipGithub) {
|
|
564
577
|
const githubResult = await p.confirm({
|
|
565
|
-
message:
|
|
578
|
+
message: "Create GitHub repository in DoSwiftly organization?",
|
|
566
579
|
initialValue: true,
|
|
567
580
|
});
|
|
568
581
|
if (p.isCancel(githubResult)) {
|
|
569
|
-
console.log(chalk.yellow(
|
|
582
|
+
console.log(chalk.yellow("\n Operation cancelled.\n"));
|
|
570
583
|
process.exit(0);
|
|
571
584
|
}
|
|
572
585
|
createGitHubRepo = githubResult;
|
|
573
586
|
// Ask for GitHub username to add as collaborator (optional - not needed for org owners)
|
|
574
587
|
if (createGitHubRepo) {
|
|
575
588
|
const usernameResult = await p.text({
|
|
576
|
-
message:
|
|
577
|
-
placeholder:
|
|
578
|
-
defaultValue:
|
|
589
|
+
message: "GitHub username to add as collaborator (Enter to skip):",
|
|
590
|
+
placeholder: " ",
|
|
591
|
+
defaultValue: "",
|
|
579
592
|
validate: (v) => {
|
|
580
|
-
if (!v || v.trim() ===
|
|
593
|
+
if (!v || v.trim() === "")
|
|
581
594
|
return undefined; // Optional
|
|
582
595
|
if (!/^[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?$/.test(v.trim())) {
|
|
583
|
-
return
|
|
596
|
+
return "Invalid GitHub username format";
|
|
584
597
|
}
|
|
585
598
|
return undefined;
|
|
586
599
|
},
|
|
587
600
|
});
|
|
588
601
|
if (p.isCancel(usernameResult)) {
|
|
589
|
-
console.log(chalk.yellow(
|
|
602
|
+
console.log(chalk.yellow("\n Operation cancelled.\n"));
|
|
590
603
|
process.exit(0);
|
|
591
604
|
}
|
|
592
605
|
const trimmed = usernameResult?.trim();
|
|
@@ -601,11 +614,15 @@ export async function templateCreateCommand(options = {}) {
|
|
|
601
614
|
}
|
|
602
615
|
// Step 5: Create GitHub repo if requested
|
|
603
616
|
if (createGitHubRepo) {
|
|
604
|
-
const repoSpinner = ora(
|
|
617
|
+
const repoSpinner = ora("Creating GitHub repository...").start();
|
|
605
618
|
try {
|
|
606
|
-
repoInfo = await apiRequest(
|
|
607
|
-
method:
|
|
608
|
-
body: JSON.stringify({
|
|
619
|
+
repoInfo = await apiRequest("/cli/templates/create-repo", {
|
|
620
|
+
method: "POST",
|
|
621
|
+
body: JSON.stringify({
|
|
622
|
+
name: templateName,
|
|
623
|
+
description,
|
|
624
|
+
githubUsername,
|
|
625
|
+
}),
|
|
609
626
|
});
|
|
610
627
|
if (repoInfo.collaboratorAdded) {
|
|
611
628
|
repoSpinner.succeed(`GitHub repository created: ${chalk.cyan(repoInfo.fullName)} ` +
|
|
@@ -616,36 +633,37 @@ export async function templateCreateCommand(options = {}) {
|
|
|
616
633
|
}
|
|
617
634
|
}
|
|
618
635
|
catch (error) {
|
|
619
|
-
repoSpinner.fail(
|
|
636
|
+
repoSpinner.fail("Failed to create GitHub repository");
|
|
620
637
|
const errorMsg = error.message;
|
|
621
638
|
// Check if it's a configuration/installation issue
|
|
622
|
-
if (errorMsg.includes(
|
|
623
|
-
|
|
624
|
-
console.log(chalk.
|
|
639
|
+
if (errorMsg.includes("not configured") ||
|
|
640
|
+
errorMsg.includes("not installed")) {
|
|
641
|
+
console.log(chalk.yellow("\n GitHub integration is not available."));
|
|
642
|
+
console.log(chalk.gray(" You can continue without GitHub integration.\n"));
|
|
625
643
|
const continueResult = await p.confirm({
|
|
626
|
-
message:
|
|
644
|
+
message: "Continue without GitHub integration?",
|
|
627
645
|
initialValue: true,
|
|
628
646
|
});
|
|
629
647
|
if (p.isCancel(continueResult) || !continueResult) {
|
|
630
|
-
console.log(chalk.yellow(
|
|
648
|
+
console.log(chalk.yellow("\n Operation cancelled.\n"));
|
|
631
649
|
process.exit(0);
|
|
632
650
|
}
|
|
633
651
|
createGitHubRepo = false;
|
|
634
652
|
}
|
|
635
|
-
else if (errorMsg.includes(
|
|
653
|
+
else if (errorMsg.includes("already exists")) {
|
|
636
654
|
console.log(chalk.red(`\n ${errorMsg}\n`));
|
|
637
655
|
process.exit(1);
|
|
638
656
|
}
|
|
639
|
-
else if (errorMsg.includes(
|
|
657
|
+
else if (errorMsg.includes("permission") || errorMsg.includes("403")) {
|
|
640
658
|
// Permission-related error - offer to continue without GitHub
|
|
641
|
-
console.log(chalk.yellow(
|
|
659
|
+
console.log(chalk.yellow("\n GitHub App does not have required permissions."));
|
|
642
660
|
console.log(chalk.gray(` ${errorMsg}\n`));
|
|
643
661
|
const continueResult = await p.confirm({
|
|
644
|
-
message:
|
|
662
|
+
message: "Continue without GitHub integration?",
|
|
645
663
|
initialValue: true,
|
|
646
664
|
});
|
|
647
665
|
if (p.isCancel(continueResult) || !continueResult) {
|
|
648
|
-
console.log(chalk.yellow(
|
|
666
|
+
console.log(chalk.yellow("\n Operation cancelled.\n"));
|
|
649
667
|
process.exit(0);
|
|
650
668
|
}
|
|
651
669
|
createGitHubRepo = false;
|
|
@@ -657,20 +675,20 @@ export async function templateCreateCommand(options = {}) {
|
|
|
657
675
|
}
|
|
658
676
|
}
|
|
659
677
|
// Step 6: Scaffold the template locally
|
|
660
|
-
const scaffoldSpinner = ora(
|
|
678
|
+
const scaffoldSpinner = ora("Creating template project...").start();
|
|
661
679
|
try {
|
|
662
|
-
const templateDir = join(__dirname,
|
|
680
|
+
const templateDir = join(__dirname, "..", "..", "templates", baseTemplate);
|
|
663
681
|
if (!existsSync(templateDir)) {
|
|
664
682
|
throw new Error(`Base template not found: ${baseTemplate}`);
|
|
665
683
|
}
|
|
666
684
|
// Copy template files with generic placeholders
|
|
667
685
|
const replacements = {
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
686
|
+
"\\{\\{PROJECT_NAME\\}\\}": templateName,
|
|
687
|
+
"\\{\\{SHOP_SLUG\\}\\}": "{{SHOP_SLUG}}",
|
|
688
|
+
"\\{\\{API_URL\\}\\}": "{{API_URL}}",
|
|
689
|
+
"\\{\\{SDK_VERSION\\}\\}": "^1.0.0",
|
|
690
|
+
"\\{\\{COMMERCE_SDK_VERSION\\}\\}": "^1.0.0",
|
|
691
|
+
"\\{\\{STOREFRONT_OPS_VERSION\\}\\}": "^1.0.0",
|
|
674
692
|
};
|
|
675
693
|
copyTemplateDir(templateDir, targetDir, replacements);
|
|
676
694
|
// Create doswiftly-template.json manifest
|
|
@@ -679,68 +697,85 @@ export async function templateCreateCommand(options = {}) {
|
|
|
679
697
|
description,
|
|
680
698
|
uiLibrary,
|
|
681
699
|
features: [],
|
|
682
|
-
version:
|
|
700
|
+
version: "0.1.0",
|
|
683
701
|
};
|
|
684
|
-
writeFileSync(join(targetDir,
|
|
702
|
+
writeFileSync(join(targetDir, "doswiftly-template.json"), JSON.stringify(manifest, null, 2) + "\n");
|
|
685
703
|
// Initialize git
|
|
686
|
-
scaffoldSpinner.text =
|
|
704
|
+
scaffoldSpinner.text = "Initializing git repository...";
|
|
687
705
|
let gitInitialized = false;
|
|
688
|
-
let localBranch =
|
|
706
|
+
let localBranch = "main";
|
|
689
707
|
try {
|
|
690
|
-
execFileSync(
|
|
691
|
-
execFileSync(
|
|
708
|
+
execFileSync("git", ["init"], { stdio: "pipe", cwd: targetDir });
|
|
709
|
+
execFileSync("git", ["add", "-A"], { stdio: "pipe", cwd: targetDir });
|
|
692
710
|
// Check if git user is configured, if not set temporary values
|
|
693
711
|
try {
|
|
694
|
-
execFileSync(
|
|
712
|
+
execFileSync("git", ["config", "user.name"], {
|
|
713
|
+
stdio: "pipe",
|
|
714
|
+
cwd: targetDir,
|
|
715
|
+
});
|
|
695
716
|
}
|
|
696
717
|
catch {
|
|
697
|
-
execFileSync(
|
|
718
|
+
execFileSync("git", ["config", "user.name", "DoSwiftly CLI"], {
|
|
719
|
+
stdio: "pipe",
|
|
720
|
+
cwd: targetDir,
|
|
721
|
+
});
|
|
698
722
|
}
|
|
699
723
|
try {
|
|
700
|
-
execFileSync(
|
|
724
|
+
execFileSync("git", ["config", "user.email"], {
|
|
725
|
+
stdio: "pipe",
|
|
726
|
+
cwd: targetDir,
|
|
727
|
+
});
|
|
701
728
|
}
|
|
702
729
|
catch {
|
|
703
|
-
execFileSync(
|
|
730
|
+
execFileSync("git", ["config", "user.email", "cli@doswiftly.pl"], {
|
|
731
|
+
stdio: "pipe",
|
|
732
|
+
cwd: targetDir,
|
|
733
|
+
});
|
|
704
734
|
}
|
|
705
|
-
execFileSync(
|
|
706
|
-
stdio:
|
|
735
|
+
execFileSync("git", ["commit", "-m", `chore: initialize ${templateName} template`], {
|
|
736
|
+
stdio: "pipe",
|
|
707
737
|
cwd: targetDir,
|
|
708
738
|
});
|
|
709
739
|
// Get the actual branch name (might be 'master' on older git)
|
|
710
740
|
try {
|
|
711
|
-
localBranch =
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
741
|
+
localBranch =
|
|
742
|
+
execSync("git branch --show-current", {
|
|
743
|
+
stdio: "pipe",
|
|
744
|
+
cwd: targetDir,
|
|
745
|
+
encoding: "utf-8",
|
|
746
|
+
}).trim() || "main";
|
|
716
747
|
}
|
|
717
748
|
catch {
|
|
718
|
-
localBranch =
|
|
749
|
+
localBranch = "main";
|
|
719
750
|
}
|
|
720
751
|
gitInitialized = true;
|
|
721
752
|
}
|
|
722
753
|
catch (gitError) {
|
|
723
754
|
const err = gitError;
|
|
724
|
-
const errorMsg = err.stderr || err.message ||
|
|
725
|
-
scaffoldSpinner.fail(
|
|
755
|
+
const errorMsg = err.stderr || err.message || "Unknown git error";
|
|
756
|
+
scaffoldSpinner.fail("Failed to initialize git");
|
|
726
757
|
console.log(chalk.red(`\n Git error: ${errorMsg.trim()}\n`));
|
|
727
758
|
// Continue without git - user can init manually
|
|
728
759
|
}
|
|
729
760
|
// Add remote if GitHub repo was created
|
|
730
761
|
if (repoInfo) {
|
|
731
|
-
scaffoldSpinner.text =
|
|
762
|
+
scaffoldSpinner.text = "Configuring git remote...";
|
|
732
763
|
// Detect if user has SSH configured for GitHub
|
|
733
764
|
let useSSH = false;
|
|
734
765
|
try {
|
|
735
766
|
// Check if SSH key auth works with GitHub
|
|
736
|
-
execSync(
|
|
767
|
+
execSync("ssh -T git@github.com 2>&1 || true", {
|
|
768
|
+
stdio: "pipe",
|
|
769
|
+
encoding: "utf-8",
|
|
770
|
+
});
|
|
737
771
|
// Check if user has SSH in their git config or .ssh folder
|
|
738
|
-
const sshTest = execSync(
|
|
739
|
-
stdio:
|
|
740
|
-
encoding:
|
|
772
|
+
const sshTest = execSync("ssh -o BatchMode=yes -o StrictHostKeyChecking=no git@github.com 2>&1 || true", {
|
|
773
|
+
stdio: "pipe",
|
|
774
|
+
encoding: "utf-8",
|
|
741
775
|
});
|
|
742
776
|
// GitHub returns "successfully authenticated" even with exit code 1
|
|
743
|
-
if (sshTest.includes(
|
|
777
|
+
if (sshTest.includes("successfully authenticated") ||
|
|
778
|
+
sshTest.includes("Hi ")) {
|
|
744
779
|
useSSH = true;
|
|
745
780
|
}
|
|
746
781
|
}
|
|
@@ -750,7 +785,7 @@ export async function templateCreateCommand(options = {}) {
|
|
|
750
785
|
const remoteUrl = useSSH ? repoInfo.sshUrl : repoInfo.cloneUrl;
|
|
751
786
|
try {
|
|
752
787
|
execSync(`git remote add origin ${remoteUrl}`, {
|
|
753
|
-
stdio:
|
|
788
|
+
stdio: "pipe",
|
|
754
789
|
cwd: targetDir,
|
|
755
790
|
});
|
|
756
791
|
}
|
|
@@ -758,46 +793,46 @@ export async function templateCreateCommand(options = {}) {
|
|
|
758
793
|
// Non-fatal
|
|
759
794
|
}
|
|
760
795
|
}
|
|
761
|
-
scaffoldSpinner.succeed(
|
|
796
|
+
scaffoldSpinner.succeed("Template project created");
|
|
762
797
|
// Step 7: Push to GitHub using local git
|
|
763
798
|
if (repoInfo && gitInitialized) {
|
|
764
|
-
const pushSpinner = ora(
|
|
799
|
+
const pushSpinner = ora("Pushing to GitHub...").start();
|
|
765
800
|
try {
|
|
766
801
|
// Try to push using local git credentials
|
|
767
802
|
// Use the local branch name, not the remote default (they might differ)
|
|
768
803
|
execSync(`git push -u origin ${localBranch}`, {
|
|
769
804
|
cwd: targetDir,
|
|
770
|
-
stdio: [
|
|
771
|
-
encoding:
|
|
805
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
806
|
+
encoding: "utf-8",
|
|
772
807
|
});
|
|
773
|
-
pushSpinner.succeed(
|
|
808
|
+
pushSpinner.succeed("Pushed to GitHub");
|
|
774
809
|
}
|
|
775
810
|
catch (pushError) {
|
|
776
|
-
pushSpinner.fail(
|
|
811
|
+
pushSpinner.fail("Failed to push to GitHub");
|
|
777
812
|
// Extract and show the actual git error
|
|
778
813
|
const err = pushError;
|
|
779
|
-
const gitError = err.stderr || err.message ||
|
|
780
|
-
console.log(chalk.red(
|
|
781
|
-
console.log(chalk.gray(` ${gitError.trim().split(
|
|
814
|
+
const gitError = err.stderr || err.message || "Unknown error";
|
|
815
|
+
console.log(chalk.red("\n Git error:"));
|
|
816
|
+
console.log(chalk.gray(` ${gitError.trim().split("\n").join("\n ")}`));
|
|
782
817
|
// Provide helpful suggestions
|
|
783
|
-
console.log(chalk.yellow(
|
|
784
|
-
console.log(chalk.gray(
|
|
785
|
-
console.log(chalk.gray(
|
|
786
|
-
console.log(chalk.gray(
|
|
787
|
-
console.log(chalk.yellow(
|
|
818
|
+
console.log(chalk.yellow("\n Possible solutions:"));
|
|
819
|
+
console.log(chalk.gray(" 1. Run: gh auth login (if you have GitHub CLI)"));
|
|
820
|
+
console.log(chalk.gray(" 2. Configure SSH key for GitHub"));
|
|
821
|
+
console.log(chalk.gray(" 3. Use HTTPS with credential manager"));
|
|
822
|
+
console.log(chalk.yellow("\n Then push manually:"));
|
|
788
823
|
console.log(chalk.cyan(` cd ${templateName} && git push -u origin ${localBranch}\n`));
|
|
789
824
|
}
|
|
790
825
|
}
|
|
791
826
|
else if (repoInfo && !gitInitialized) {
|
|
792
|
-
console.log(chalk.yellow(
|
|
827
|
+
console.log(chalk.yellow("\n Git not initialized. Push manually after fixing git config:"));
|
|
793
828
|
console.log(chalk.cyan(` cd ${templateName} && git init && git add -A && git commit -m "Initial commit" && git push -u origin main\n`));
|
|
794
829
|
}
|
|
795
830
|
// Step 8: Auto-register the template
|
|
796
831
|
if (repoInfo) {
|
|
797
|
-
const registerSpinner = ora(
|
|
832
|
+
const registerSpinner = ora("Registering template in registry...").start();
|
|
798
833
|
try {
|
|
799
|
-
const template = await apiRequest(
|
|
800
|
-
method:
|
|
834
|
+
const template = await apiRequest("/cli/templates", {
|
|
835
|
+
method: "POST",
|
|
801
836
|
body: JSON.stringify({
|
|
802
837
|
name: templateName,
|
|
803
838
|
description,
|
|
@@ -810,57 +845,58 @@ export async function templateCreateCommand(options = {}) {
|
|
|
810
845
|
registerSpinner.succeed(`Template registered: ${chalk.cyan(template.name)}`);
|
|
811
846
|
}
|
|
812
847
|
catch (regError) {
|
|
813
|
-
registerSpinner.fail(
|
|
814
|
-
const errorMsg = regError.message ||
|
|
848
|
+
registerSpinner.fail("Failed to register template");
|
|
849
|
+
const errorMsg = regError.message || "Unknown error";
|
|
815
850
|
console.log(chalk.red(` Error: ${errorMsg}`));
|
|
816
|
-
console.log(chalk.yellow(
|
|
851
|
+
console.log(chalk.yellow(" You can register manually with:"));
|
|
817
852
|
console.log(chalk.gray(` cd ${templateName} && doswiftly template register\n`));
|
|
818
853
|
}
|
|
819
854
|
}
|
|
820
855
|
// Show CF Builds status
|
|
821
856
|
if (repoInfo?.buildsSetup) {
|
|
822
857
|
if (repoInfo.buildsSetup.success) {
|
|
823
|
-
console.log(chalk.green(
|
|
858
|
+
console.log(chalk.green("\n Cloudflare Workers Builds configured!"));
|
|
824
859
|
console.log(chalk.gray(` Worker: storefront-template-${templateName}`));
|
|
825
|
-
console.log(chalk.gray(
|
|
860
|
+
console.log(chalk.gray(" Auto-deploy: pushes to main/master trigger build + deploy"));
|
|
826
861
|
}
|
|
827
862
|
else {
|
|
828
|
-
console.log(chalk.yellow(
|
|
863
|
+
console.log(chalk.yellow("\n CF Builds setup failed: " +
|
|
864
|
+
(repoInfo.buildsSetup.error || "unknown")));
|
|
829
865
|
console.log(chalk.gray(` Retry with: doswiftly template setup-builds ${templateName}`));
|
|
830
866
|
}
|
|
831
867
|
}
|
|
832
868
|
}
|
|
833
869
|
catch (error) {
|
|
834
|
-
scaffoldSpinner.fail(
|
|
870
|
+
scaffoldSpinner.fail("Failed to create template project");
|
|
835
871
|
console.log(chalk.red(`\n ${error.message}\n`));
|
|
836
872
|
process.exit(1);
|
|
837
873
|
}
|
|
838
874
|
// Success summary
|
|
839
|
-
console.log(chalk.green(
|
|
840
|
-
console.log(chalk.gray(
|
|
875
|
+
console.log(chalk.green("\n ✓ Template created successfully!\n"));
|
|
876
|
+
console.log(chalk.gray(" Location: ") + chalk.cyan(`${targetDir}/`));
|
|
841
877
|
if (repoInfo) {
|
|
842
|
-
console.log(chalk.gray(
|
|
878
|
+
console.log(chalk.gray(" Repository: ") + chalk.cyan(repoInfo.repoUrl));
|
|
843
879
|
}
|
|
844
|
-
console.log(chalk.bold(
|
|
880
|
+
console.log(chalk.bold("\n Next steps:\n"));
|
|
845
881
|
console.log(chalk.cyan(` cd ${templateName}`));
|
|
846
|
-
console.log(chalk.gray(
|
|
882
|
+
console.log(chalk.gray(" # Customize your template"));
|
|
847
883
|
if (!repoInfo) {
|
|
848
|
-
console.log(chalk.cyan(
|
|
849
|
-
console.log(chalk.cyan(
|
|
850
|
-
console.log(chalk.cyan(
|
|
851
|
-
console.log(chalk.gray(
|
|
852
|
-
console.log(chalk.cyan(
|
|
884
|
+
console.log(chalk.cyan(" git remote add origin <your-github-repo-url>"));
|
|
885
|
+
console.log(chalk.cyan(" git push -u origin main"));
|
|
886
|
+
console.log(chalk.cyan(" doswiftly template register"));
|
|
887
|
+
console.log(chalk.gray(" # Update version in package.json, then:"));
|
|
888
|
+
console.log(chalk.cyan(" doswiftly template publish"));
|
|
853
889
|
}
|
|
854
890
|
else if (repoInfo.buildsSetup?.success) {
|
|
855
|
-
console.log(chalk.gray(
|
|
891
|
+
console.log(chalk.gray(" # Make changes, commit and push:"));
|
|
856
892
|
console.log(chalk.cyan(' git add -A && git commit -m "feat: ..." && git push'));
|
|
857
|
-
console.log(chalk.gray(
|
|
893
|
+
console.log(chalk.gray(" # CF Builds will auto-build and deploy on push"));
|
|
858
894
|
}
|
|
859
895
|
else {
|
|
860
|
-
console.log(chalk.gray(
|
|
861
|
-
console.log(chalk.cyan(
|
|
896
|
+
console.log(chalk.gray(" # Update version in package.json, then:"));
|
|
897
|
+
console.log(chalk.cyan(" doswiftly template publish"));
|
|
862
898
|
}
|
|
863
|
-
console.log(
|
|
899
|
+
console.log("");
|
|
864
900
|
}
|
|
865
901
|
/**
|
|
866
902
|
* Validate a template project before publishing.
|
|
@@ -873,18 +909,18 @@ export async function templateCreateCommand(options = {}) {
|
|
|
873
909
|
*/
|
|
874
910
|
export async function templateValidateCommand(options = {}) {
|
|
875
911
|
requireSaasDeveloperRole();
|
|
876
|
-
console.log(chalk.bold.blue(
|
|
912
|
+
console.log(chalk.bold.blue("\nDoSwiftly CLI - Validate Template\n"));
|
|
877
913
|
const result = {
|
|
878
914
|
valid: true,
|
|
879
915
|
errors: [],
|
|
880
916
|
warnings: [],
|
|
881
917
|
};
|
|
882
918
|
// Check 1: doswiftly-template.json
|
|
883
|
-
console.log(chalk.gray(
|
|
919
|
+
console.log(chalk.gray(" Checking doswiftly-template.json..."));
|
|
884
920
|
const manifest = readTemplateManifest();
|
|
885
921
|
if (!manifest) {
|
|
886
922
|
result.valid = false;
|
|
887
|
-
result.errors.push(
|
|
923
|
+
result.errors.push("doswiftly-template.json not found. Create one with required fields: name, description, uiLibrary");
|
|
888
924
|
}
|
|
889
925
|
else {
|
|
890
926
|
// Validate required fields
|
|
@@ -905,40 +941,41 @@ export async function templateValidateCommand(options = {}) {
|
|
|
905
941
|
if (!manifest.version) {
|
|
906
942
|
result.warnings.push('doswiftly-template.json: "version" is recommended');
|
|
907
943
|
}
|
|
908
|
-
console.log(chalk.green(
|
|
944
|
+
console.log(chalk.green(" ✓ doswiftly-template.json exists"));
|
|
909
945
|
}
|
|
910
946
|
// Check 2: package.json
|
|
911
|
-
console.log(chalk.gray(
|
|
912
|
-
if (!existsSync(
|
|
947
|
+
console.log(chalk.gray(" Checking package.json..."));
|
|
948
|
+
if (!existsSync("package.json")) {
|
|
913
949
|
result.valid = false;
|
|
914
|
-
result.errors.push(
|
|
950
|
+
result.errors.push("package.json not found");
|
|
915
951
|
}
|
|
916
952
|
else {
|
|
917
953
|
try {
|
|
918
|
-
const pkg = JSON.parse(readFileSync(
|
|
954
|
+
const pkg = JSON.parse(readFileSync("package.json", "utf-8"));
|
|
919
955
|
if (!pkg.name) {
|
|
920
956
|
result.warnings.push('package.json: "name" field is recommended');
|
|
921
957
|
}
|
|
922
958
|
// Check for required dependencies
|
|
923
959
|
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
924
|
-
const requiredDeps = [
|
|
925
|
-
const missingDeps = requiredDeps.filter(dep => !allDeps[dep]);
|
|
960
|
+
const requiredDeps = ["next", "react", "react-dom"];
|
|
961
|
+
const missingDeps = requiredDeps.filter((dep) => !allDeps[dep]);
|
|
926
962
|
if (missingDeps.length > 0) {
|
|
927
|
-
result.warnings.push(`package.json: Missing recommended dependencies: ${missingDeps.join(
|
|
963
|
+
result.warnings.push(`package.json: Missing recommended dependencies: ${missingDeps.join(", ")}`);
|
|
928
964
|
}
|
|
929
965
|
// Check for DoSwiftly SDK
|
|
930
|
-
if (!allDeps[
|
|
931
|
-
|
|
966
|
+
if (!allDeps["@doswiftly/commerce-sdk"] &&
|
|
967
|
+
!allDeps["@doswiftly/storefront-operations"]) {
|
|
968
|
+
result.warnings.push("package.json: Consider adding @doswiftly/commerce-sdk for storefront functionality");
|
|
932
969
|
}
|
|
933
|
-
console.log(chalk.green(
|
|
970
|
+
console.log(chalk.green(" ✓ package.json exists"));
|
|
934
971
|
}
|
|
935
972
|
catch {
|
|
936
973
|
result.valid = false;
|
|
937
|
-
result.errors.push(
|
|
974
|
+
result.errors.push("package.json is not valid JSON");
|
|
938
975
|
}
|
|
939
976
|
}
|
|
940
977
|
// Check 3: Git initialization
|
|
941
|
-
console.log(chalk.gray(
|
|
978
|
+
console.log(chalk.gray(" Checking git configuration..."));
|
|
942
979
|
if (!isGitInitialized()) {
|
|
943
980
|
result.warnings.push('Git is not initialized. Run "git init" before publishing');
|
|
944
981
|
}
|
|
@@ -952,11 +989,11 @@ export async function templateValidateCommand(options = {}) {
|
|
|
952
989
|
}
|
|
953
990
|
}
|
|
954
991
|
// Check 4: Required files
|
|
955
|
-
console.log(chalk.gray(
|
|
992
|
+
console.log(chalk.gray(" Checking required files..."));
|
|
956
993
|
const recommendedFiles = [
|
|
957
|
-
{ path:
|
|
958
|
-
{ path:
|
|
959
|
-
{ path:
|
|
994
|
+
{ path: "README.md", required: false },
|
|
995
|
+
{ path: ".gitignore", required: false },
|
|
996
|
+
{ path: "tsconfig.json", required: false },
|
|
960
997
|
];
|
|
961
998
|
for (const file of recommendedFiles) {
|
|
962
999
|
if (!existsSync(file.path)) {
|
|
@@ -970,55 +1007,56 @@ export async function templateValidateCommand(options = {}) {
|
|
|
970
1007
|
}
|
|
971
1008
|
}
|
|
972
1009
|
// Check 5: Placeholder tokens (should not be resolved yet in templates)
|
|
973
|
-
console.log(chalk.gray(
|
|
974
|
-
const checkFiles = [
|
|
1010
|
+
console.log(chalk.gray(" Checking placeholder tokens..."));
|
|
1011
|
+
const checkFiles = ["package.json", "doswiftly-template.json"];
|
|
975
1012
|
for (const file of checkFiles) {
|
|
976
1013
|
if (existsSync(file)) {
|
|
977
|
-
const content = readFileSync(file,
|
|
1014
|
+
const content = readFileSync(file, "utf-8");
|
|
978
1015
|
// Templates should contain placeholders like {{SHOP_SLUG}}
|
|
979
|
-
if (content.includes(
|
|
1016
|
+
if (content.includes("{{SHOP_SLUG}}") ||
|
|
1017
|
+
content.includes("{{API_URL}}")) {
|
|
980
1018
|
console.log(chalk.green(` ✓ ${file} contains expected placeholders`));
|
|
981
1019
|
}
|
|
982
1020
|
}
|
|
983
1021
|
}
|
|
984
1022
|
// Check 6: Build test (optional)
|
|
985
1023
|
if (options.build) {
|
|
986
|
-
console.log(chalk.gray(
|
|
987
|
-
const buildSpinner = ora(
|
|
1024
|
+
console.log(chalk.gray(" Running build test..."));
|
|
1025
|
+
const buildSpinner = ora("Building project...").start();
|
|
988
1026
|
try {
|
|
989
|
-
execSync(
|
|
990
|
-
buildSpinner.succeed(
|
|
1027
|
+
execSync("npm run build", { stdio: "pipe" });
|
|
1028
|
+
buildSpinner.succeed("Build succeeded");
|
|
991
1029
|
}
|
|
992
1030
|
catch (buildError) {
|
|
993
|
-
buildSpinner.fail(
|
|
1031
|
+
buildSpinner.fail("Build failed");
|
|
994
1032
|
result.valid = false;
|
|
995
|
-
result.errors.push(
|
|
1033
|
+
result.errors.push("Build failed. Fix build errors before publishing.");
|
|
996
1034
|
}
|
|
997
1035
|
}
|
|
998
1036
|
// Summary
|
|
999
|
-
console.log(
|
|
1037
|
+
console.log("");
|
|
1000
1038
|
if (result.errors.length > 0) {
|
|
1001
|
-
console.log(chalk.red.bold(
|
|
1039
|
+
console.log(chalk.red.bold(" Errors:"));
|
|
1002
1040
|
for (const error of result.errors) {
|
|
1003
1041
|
console.log(chalk.red(` ✗ ${error}`));
|
|
1004
1042
|
}
|
|
1005
|
-
console.log(
|
|
1043
|
+
console.log("");
|
|
1006
1044
|
}
|
|
1007
1045
|
if (result.warnings.length > 0) {
|
|
1008
|
-
console.log(chalk.yellow.bold(
|
|
1046
|
+
console.log(chalk.yellow.bold(" Warnings:"));
|
|
1009
1047
|
for (const warning of result.warnings) {
|
|
1010
1048
|
console.log(chalk.yellow(` ⚠ ${warning}`));
|
|
1011
1049
|
}
|
|
1012
|
-
console.log(
|
|
1050
|
+
console.log("");
|
|
1013
1051
|
}
|
|
1014
1052
|
if (result.valid && result.errors.length === 0) {
|
|
1015
|
-
console.log(chalk.green.bold(
|
|
1016
|
-
console.log(chalk.gray(
|
|
1017
|
-
console.log(chalk.cyan(
|
|
1018
|
-
console.log(chalk.cyan(
|
|
1053
|
+
console.log(chalk.green.bold(" ✓ Template is valid and ready for publishing!\n"));
|
|
1054
|
+
console.log(chalk.gray(" Next steps:"));
|
|
1055
|
+
console.log(chalk.cyan(" doswiftly template register"));
|
|
1056
|
+
console.log(chalk.cyan(" doswiftly template version <name> 1.0.0\n"));
|
|
1019
1057
|
}
|
|
1020
1058
|
else {
|
|
1021
|
-
console.log(chalk.red.bold(
|
|
1059
|
+
console.log(chalk.red.bold(" ✗ Template validation failed. Fix errors before publishing.\n"));
|
|
1022
1060
|
process.exit(1);
|
|
1023
1061
|
}
|
|
1024
1062
|
}
|
|
@@ -1031,75 +1069,80 @@ export async function templateValidateCommand(options = {}) {
|
|
|
1031
1069
|
*/
|
|
1032
1070
|
export async function templateBuildCommand(options = {}) {
|
|
1033
1071
|
requireSaasDeveloperRole();
|
|
1034
|
-
console.log(chalk.bold.blue(
|
|
1072
|
+
console.log(chalk.bold.blue("\nDoSwiftly CLI - Build Template for Cloudflare\n"));
|
|
1035
1073
|
// Check for required files
|
|
1036
|
-
if (!existsSync(
|
|
1037
|
-
console.log(chalk.red(
|
|
1074
|
+
if (!existsSync("package.json")) {
|
|
1075
|
+
console.log(chalk.red(" Error: No package.json found. Run this from a template project root.\n"));
|
|
1038
1076
|
process.exit(1);
|
|
1039
1077
|
}
|
|
1040
1078
|
// Read manifest for template name
|
|
1041
1079
|
const manifest = readTemplateManifest();
|
|
1042
|
-
const templateName = manifest?.name ||
|
|
1080
|
+
const templateName = manifest?.name || "template";
|
|
1043
1081
|
console.log(chalk.gray(` Template: ${chalk.cyan(templateName)}`));
|
|
1044
1082
|
// Check for @opennextjs/cloudflare
|
|
1045
1083
|
let hasOpenNext = false;
|
|
1046
1084
|
try {
|
|
1047
|
-
const pkg = JSON.parse(readFileSync(
|
|
1085
|
+
const pkg = JSON.parse(readFileSync("package.json", "utf-8"));
|
|
1048
1086
|
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
1049
|
-
hasOpenNext =
|
|
1087
|
+
hasOpenNext =
|
|
1088
|
+
!!allDeps["@opennextjs/cloudflare"] ||
|
|
1089
|
+
!!allDeps["@opennextjs/cloudflare"];
|
|
1050
1090
|
}
|
|
1051
1091
|
catch {
|
|
1052
1092
|
// Continue without check
|
|
1053
1093
|
}
|
|
1054
1094
|
if (!hasOpenNext) {
|
|
1055
|
-
console.log(chalk.yellow(
|
|
1056
|
-
console.log(chalk.gray(
|
|
1057
|
-
console.log(chalk.gray(
|
|
1095
|
+
console.log(chalk.yellow("\n Warning: @opennextjs/cloudflare not found in dependencies."));
|
|
1096
|
+
console.log(chalk.gray(" Install it with: pnpm add -D @opennextjs/cloudflare"));
|
|
1097
|
+
console.log(chalk.gray(" Or: npx @opennextjs/cloudflare build (will use npx version)\n"));
|
|
1058
1098
|
}
|
|
1059
1099
|
// Step 1: Run Next.js build (unless skipped)
|
|
1060
1100
|
if (!options.skipNextBuild) {
|
|
1061
|
-
const nextSpinner = ora(
|
|
1101
|
+
const nextSpinner = ora("Building Next.js application...").start();
|
|
1062
1102
|
try {
|
|
1063
1103
|
// Try pnpm first, then npm
|
|
1064
1104
|
try {
|
|
1065
|
-
execSync(
|
|
1105
|
+
execSync("pnpm build", { stdio: "pipe", cwd: process.cwd() });
|
|
1066
1106
|
}
|
|
1067
1107
|
catch {
|
|
1068
|
-
execSync(
|
|
1108
|
+
execSync("npm run build", { stdio: "pipe", cwd: process.cwd() });
|
|
1069
1109
|
}
|
|
1070
|
-
nextSpinner.succeed(
|
|
1110
|
+
nextSpinner.succeed("Next.js build completed");
|
|
1071
1111
|
}
|
|
1072
1112
|
catch (error) {
|
|
1073
|
-
nextSpinner.fail(
|
|
1113
|
+
nextSpinner.fail("Next.js build failed");
|
|
1074
1114
|
const err = error;
|
|
1075
|
-
const stderr = err.stderr?.toString() || err.message ||
|
|
1076
|
-
console.log(chalk.red(`\n ${stderr.split(
|
|
1115
|
+
const stderr = err.stderr?.toString() || err.message || "";
|
|
1116
|
+
console.log(chalk.red(`\n ${stderr.split("\n").slice(0, 10).join("\n ")}\n`));
|
|
1077
1117
|
process.exit(1);
|
|
1078
1118
|
}
|
|
1079
1119
|
}
|
|
1080
1120
|
else {
|
|
1081
|
-
console.log(chalk.gray(
|
|
1121
|
+
console.log(chalk.gray(" Skipping Next.js build (--skip-next-build)\n"));
|
|
1082
1122
|
}
|
|
1083
1123
|
// Step 2: Run OpenNext build for Cloudflare
|
|
1084
|
-
const openNextSpinner = ora(
|
|
1124
|
+
const openNextSpinner = ora("Building for Cloudflare Workers (OpenNext)...").start();
|
|
1085
1125
|
try {
|
|
1086
|
-
execSync(
|
|
1087
|
-
|
|
1126
|
+
execSync("npx @opennextjs/cloudflare build", {
|
|
1127
|
+
stdio: "pipe",
|
|
1128
|
+
cwd: process.cwd(),
|
|
1129
|
+
});
|
|
1130
|
+
openNextSpinner.succeed("OpenNext build completed");
|
|
1088
1131
|
}
|
|
1089
1132
|
catch (error) {
|
|
1090
|
-
openNextSpinner.fail(
|
|
1133
|
+
openNextSpinner.fail("OpenNext build failed");
|
|
1091
1134
|
const err = error;
|
|
1092
|
-
const stderr = err.stderr?.toString() || err.message ||
|
|
1093
|
-
console.log(chalk.red(`\n ${stderr.split(
|
|
1094
|
-
console.log(chalk.yellow(
|
|
1095
|
-
console.log(chalk.gray(
|
|
1135
|
+
const stderr = err.stderr?.toString() || err.message || "";
|
|
1136
|
+
console.log(chalk.red(`\n ${stderr.split("\n").slice(0, 10).join("\n ")}\n`));
|
|
1137
|
+
console.log(chalk.yellow(" Make sure @opennextjs/cloudflare is installed:"));
|
|
1138
|
+
console.log(chalk.gray(" pnpm add -D @opennextjs/cloudflare\n"));
|
|
1096
1139
|
process.exit(1);
|
|
1097
1140
|
}
|
|
1098
1141
|
// Check output
|
|
1099
|
-
const workerPath =
|
|
1142
|
+
const workerPath = ".open-next/worker.js";
|
|
1100
1143
|
if (!existsSync(workerPath)) {
|
|
1101
1144
|
console.log(chalk.red(`\n Error: Worker file not found at ${workerPath}`));
|
|
1102
|
-
console.log(chalk.gray(
|
|
1145
|
+
console.log(chalk.gray(" The OpenNext build may have failed silently.\n"));
|
|
1103
1146
|
process.exit(1);
|
|
1104
1147
|
}
|
|
1105
1148
|
const workerStats = statSync(workerPath);
|
|
@@ -1107,7 +1150,7 @@ export async function templateBuildCommand(options = {}) {
|
|
|
1107
1150
|
console.log(chalk.green(`\n ✓ Build successful!`));
|
|
1108
1151
|
console.log(chalk.gray(` Worker: ${workerPath} (${sizeKB} KB)`));
|
|
1109
1152
|
console.log(chalk.gray(`\n Next: Upload to registry with:`));
|
|
1110
|
-
console.log(chalk.cyan(` doswiftly template upload${manifest?.version ?
|
|
1153
|
+
console.log(chalk.cyan(` doswiftly template upload${manifest?.version ? "" : " --version 1.0.0"}\n`));
|
|
1111
1154
|
}
|
|
1112
1155
|
/**
|
|
1113
1156
|
* Upload a built worker bundle to R2 storage.
|
|
@@ -1119,13 +1162,13 @@ export async function templateBuildCommand(options = {}) {
|
|
|
1119
1162
|
*/
|
|
1120
1163
|
export async function templateUploadCommand(options = {}) {
|
|
1121
1164
|
requireSaasDeveloperRole();
|
|
1122
|
-
console.log(chalk.bold.blue(
|
|
1165
|
+
console.log(chalk.bold.blue("\nDoSwiftly CLI - Upload Template Bundle\n"));
|
|
1123
1166
|
// Read manifest for template name
|
|
1124
1167
|
const manifest = readTemplateManifest();
|
|
1125
1168
|
const templateName = manifest?.name;
|
|
1126
1169
|
if (!templateName) {
|
|
1127
1170
|
console.log(chalk.red(' Error: No doswiftly-template.json found or missing "name" field.'));
|
|
1128
|
-
console.log(chalk.gray(
|
|
1171
|
+
console.log(chalk.gray(" Run this from a template project root.\n"));
|
|
1129
1172
|
process.exit(1);
|
|
1130
1173
|
}
|
|
1131
1174
|
// Determine version
|
|
@@ -1133,21 +1176,21 @@ export async function templateUploadCommand(options = {}) {
|
|
|
1133
1176
|
const manifestVersion = manifest?.version;
|
|
1134
1177
|
const version = options.version || packageVersion || manifestVersion;
|
|
1135
1178
|
if (!version) {
|
|
1136
|
-
console.log(chalk.red(
|
|
1179
|
+
console.log(chalk.red(" Error: No version found."));
|
|
1137
1180
|
console.log(chalk.gray(' Set "version" in package.json or use --version flag\n'));
|
|
1138
1181
|
process.exit(1);
|
|
1139
1182
|
}
|
|
1140
1183
|
// Validate version format
|
|
1141
1184
|
const versionRegex = /^\d+\.\d+\.\d+(-[\w.]+)?$/;
|
|
1142
1185
|
if (!versionRegex.test(version)) {
|
|
1143
|
-
console.log(chalk.red(
|
|
1186
|
+
console.log(chalk.red(" Error: Version must be semver format (e.g., 1.0.0 or 1.0.0-beta.1)\n"));
|
|
1144
1187
|
process.exit(1);
|
|
1145
1188
|
}
|
|
1146
1189
|
// Find worker bundle
|
|
1147
|
-
const workerPath = options.workerPath ||
|
|
1190
|
+
const workerPath = options.workerPath || ".open-next/worker.js";
|
|
1148
1191
|
if (!existsSync(workerPath)) {
|
|
1149
1192
|
console.log(chalk.red(` Error: Worker bundle not found at ${workerPath}`));
|
|
1150
|
-
console.log(chalk.gray(
|
|
1193
|
+
console.log(chalk.gray(" Build first with: doswiftly template build\n"));
|
|
1151
1194
|
process.exit(1);
|
|
1152
1195
|
}
|
|
1153
1196
|
const workerBuffer = readFileSync(workerPath);
|
|
@@ -1155,25 +1198,25 @@ export async function templateUploadCommand(options = {}) {
|
|
|
1155
1198
|
console.log(chalk.gray(` Template: ${chalk.cyan(templateName)}`));
|
|
1156
1199
|
console.log(chalk.gray(` Version: ${chalk.cyan(version)}`));
|
|
1157
1200
|
console.log(chalk.gray(` Bundle: ${workerPath} (${sizeKB} KB)`));
|
|
1158
|
-
console.log(
|
|
1201
|
+
console.log("");
|
|
1159
1202
|
// Upload to API
|
|
1160
|
-
const spinner = ora(
|
|
1203
|
+
const spinner = ora("Uploading bundle to R2...").start();
|
|
1161
1204
|
try {
|
|
1162
1205
|
// Use FormData for multipart upload
|
|
1163
|
-
const FormData = (await import(
|
|
1206
|
+
const FormData = (await import("form-data")).default;
|
|
1164
1207
|
const formData = new FormData();
|
|
1165
|
-
formData.append(
|
|
1166
|
-
filename:
|
|
1167
|
-
contentType:
|
|
1208
|
+
formData.append("bundle", workerBuffer, {
|
|
1209
|
+
filename: "worker.js",
|
|
1210
|
+
contentType: "application/javascript",
|
|
1168
1211
|
});
|
|
1169
|
-
formData.append(
|
|
1212
|
+
formData.append("version", version);
|
|
1170
1213
|
// Get OpenNext version from manifest if available
|
|
1171
1214
|
try {
|
|
1172
|
-
const openNextManifestPath =
|
|
1215
|
+
const openNextManifestPath = ".open-next/open-next.output.json";
|
|
1173
1216
|
if (existsSync(openNextManifestPath)) {
|
|
1174
|
-
const openNextManifest = JSON.parse(readFileSync(openNextManifestPath,
|
|
1217
|
+
const openNextManifest = JSON.parse(readFileSync(openNextManifestPath, "utf-8"));
|
|
1175
1218
|
if (openNextManifest.version) {
|
|
1176
|
-
formData.append(
|
|
1219
|
+
formData.append("openNextVersion", openNextManifest.version);
|
|
1177
1220
|
}
|
|
1178
1221
|
}
|
|
1179
1222
|
}
|
|
@@ -1181,36 +1224,36 @@ export async function templateUploadCommand(options = {}) {
|
|
|
1181
1224
|
// Non-critical
|
|
1182
1225
|
}
|
|
1183
1226
|
// Get Node.js version
|
|
1184
|
-
formData.append(
|
|
1227
|
+
formData.append("nodeVersion", process.version);
|
|
1185
1228
|
// Make API request with multipart form data
|
|
1186
1229
|
const client = createSharedApiClient({ requireShopSlug: false });
|
|
1187
1230
|
const response = await client.request({
|
|
1188
1231
|
url: `/cli/templates/${encodeURIComponent(templateName)}/publish`,
|
|
1189
|
-
method:
|
|
1232
|
+
method: "POST",
|
|
1190
1233
|
data: formData,
|
|
1191
1234
|
headers: formData.getHeaders(),
|
|
1192
1235
|
maxContentLength: Infinity,
|
|
1193
1236
|
maxBodyLength: Infinity,
|
|
1194
1237
|
});
|
|
1195
|
-
spinner.succeed(
|
|
1238
|
+
spinner.succeed("Bundle uploaded successfully");
|
|
1196
1239
|
const result = response.data;
|
|
1197
1240
|
console.log(chalk.gray(`\n Bundle key: ${result.bundleKey}`));
|
|
1198
1241
|
console.log(chalk.gray(` Size: ${(result.bundleSize / 1024).toFixed(1)} KB`));
|
|
1199
1242
|
console.log(chalk.gray(` Uploaded: ${new Date(result.uploadedAt).toLocaleString()}`));
|
|
1200
|
-
console.log(chalk.green(
|
|
1201
|
-
console.log(chalk.gray(
|
|
1243
|
+
console.log(chalk.green("\n ✓ Bundle ready for deployment!"));
|
|
1244
|
+
console.log(chalk.gray(" Deploy from Admin Panel: Template Registry → Deploy to Cloudflare\n"));
|
|
1202
1245
|
}
|
|
1203
1246
|
catch (error) {
|
|
1204
|
-
spinner.fail(
|
|
1247
|
+
spinner.fail("Failed to upload bundle");
|
|
1205
1248
|
// Extract meaningful error message
|
|
1206
|
-
if (error && typeof error ===
|
|
1249
|
+
if (error && typeof error === "object" && "response" in error) {
|
|
1207
1250
|
const axiosError = error;
|
|
1208
1251
|
const serverMessage = axiosError.response?.data?.message;
|
|
1209
1252
|
if (serverMessage) {
|
|
1210
1253
|
console.log(chalk.red(`\n ${serverMessage}\n`));
|
|
1211
1254
|
}
|
|
1212
1255
|
else {
|
|
1213
|
-
console.log(chalk.red(`\n ${axiosError.message ||
|
|
1256
|
+
console.log(chalk.red(`\n ${axiosError.message || "Unknown error"}\n`));
|
|
1214
1257
|
}
|
|
1215
1258
|
}
|
|
1216
1259
|
else {
|
|
@@ -1223,34 +1266,34 @@ export async function templateUploadCommand(options = {}) {
|
|
|
1223
1266
|
* Get information about the uploaded bundle and deployment status.
|
|
1224
1267
|
*/
|
|
1225
1268
|
export async function templateBundleInfoCommand(nameOrId) {
|
|
1226
|
-
console.log(chalk.bold.blue(
|
|
1269
|
+
console.log(chalk.bold.blue("\nDoSwiftly CLI - Template Bundle Info\n"));
|
|
1227
1270
|
// Auto-detect from manifest if not provided
|
|
1228
1271
|
const manifest = readTemplateManifest();
|
|
1229
1272
|
const resolvedName = nameOrId || manifest?.name || null;
|
|
1230
1273
|
if (!resolvedName) {
|
|
1231
|
-
console.log(chalk.red(
|
|
1232
|
-
console.log(chalk.gray(
|
|
1274
|
+
console.log(chalk.red(" Error: Template name required."));
|
|
1275
|
+
console.log(chalk.gray(" Provide name as argument or run from a directory with doswiftly-template.json\n"));
|
|
1233
1276
|
process.exit(1);
|
|
1234
1277
|
}
|
|
1235
1278
|
const spinner = ora(`Fetching bundle info for '${resolvedName}'...`).start();
|
|
1236
1279
|
try {
|
|
1237
1280
|
const info = await apiRequest(`/cli/templates/${encodeURIComponent(resolvedName)}/bundle`);
|
|
1238
1281
|
spinner.stop();
|
|
1239
|
-
console.log(` ${chalk.bold(
|
|
1240
|
-
console.log(
|
|
1282
|
+
console.log(` ${chalk.bold("Template:")} ${chalk.cyan(resolvedName)}`);
|
|
1283
|
+
console.log("");
|
|
1241
1284
|
if (!info.hasBundleInR2) {
|
|
1242
|
-
console.log(chalk.yellow(
|
|
1243
|
-
console.log(chalk.gray(
|
|
1244
|
-
console.log(chalk.cyan(
|
|
1245
|
-
console.log(chalk.cyan(
|
|
1285
|
+
console.log(chalk.yellow(" No bundle uploaded yet."));
|
|
1286
|
+
console.log(chalk.gray("\n Build and upload with:"));
|
|
1287
|
+
console.log(chalk.cyan(" doswiftly template build"));
|
|
1288
|
+
console.log(chalk.cyan(" doswiftly template upload\n"));
|
|
1246
1289
|
return;
|
|
1247
1290
|
}
|
|
1248
|
-
console.log(chalk.bold(
|
|
1291
|
+
console.log(chalk.bold(" R2 Bundle:"));
|
|
1249
1292
|
console.log(` Version: ${info.bundleVersion}`);
|
|
1250
|
-
console.log(` Size: ${info.bundleSize ? (info.bundleSize / 1024).toFixed(1) +
|
|
1251
|
-
console.log(` Uploaded: ${info.bundleUploadedAt ? new Date(info.bundleUploadedAt).toLocaleString() :
|
|
1252
|
-
console.log(
|
|
1253
|
-
console.log(chalk.bold(
|
|
1293
|
+
console.log(` Size: ${info.bundleSize ? (info.bundleSize / 1024).toFixed(1) + " KB" : "-"}`);
|
|
1294
|
+
console.log(` Uploaded: ${info.bundleUploadedAt ? new Date(info.bundleUploadedAt).toLocaleString() : "-"}`);
|
|
1295
|
+
console.log("");
|
|
1296
|
+
console.log(chalk.bold(" Cloudflare Workers:"));
|
|
1254
1297
|
if (info.isDeployedToCloudflare) {
|
|
1255
1298
|
console.log(chalk.green(` Status: Deployed`));
|
|
1256
1299
|
console.log(` Version: ${info.deployedVersion}`);
|
|
@@ -1260,12 +1303,12 @@ export async function templateBundleInfoCommand(nameOrId) {
|
|
|
1260
1303
|
}
|
|
1261
1304
|
else {
|
|
1262
1305
|
console.log(chalk.yellow(` Status: Not deployed`));
|
|
1263
|
-
console.log(chalk.gray(
|
|
1306
|
+
console.log(chalk.gray(" Deploy from Admin Panel: Template Registry → Deploy to Cloudflare"));
|
|
1264
1307
|
}
|
|
1265
|
-
console.log(
|
|
1308
|
+
console.log("");
|
|
1266
1309
|
}
|
|
1267
1310
|
catch (error) {
|
|
1268
|
-
spinner.fail(
|
|
1311
|
+
spinner.fail("Failed to fetch bundle info");
|
|
1269
1312
|
console.log(chalk.red(`\n${error.message}\n`));
|
|
1270
1313
|
process.exit(1);
|
|
1271
1314
|
}
|
|
@@ -1276,32 +1319,32 @@ export async function templateBundleInfoCommand(nameOrId) {
|
|
|
1276
1319
|
*/
|
|
1277
1320
|
export async function templateSetupBuildsCommand(nameOrId) {
|
|
1278
1321
|
requireSaasDeveloperRole();
|
|
1279
|
-
console.log(chalk.bold.blue(
|
|
1322
|
+
console.log(chalk.bold.blue("\nDoSwiftly CLI - Setup CF Workers Builds\n"));
|
|
1280
1323
|
// Auto-detect from manifest if not provided
|
|
1281
1324
|
const manifest = readTemplateManifest();
|
|
1282
1325
|
const resolvedName = nameOrId || manifest?.name || null;
|
|
1283
1326
|
if (!resolvedName) {
|
|
1284
|
-
console.log(chalk.red(
|
|
1285
|
-
console.log(chalk.gray(
|
|
1327
|
+
console.log(chalk.red(" Error: Template name required."));
|
|
1328
|
+
console.log(chalk.gray(" Provide name as argument or run from a directory with doswiftly-template.json\n"));
|
|
1286
1329
|
process.exit(1);
|
|
1287
1330
|
}
|
|
1288
1331
|
const spinner = ora(`Setting up CF Builds for '${resolvedName}'...`).start();
|
|
1289
1332
|
try {
|
|
1290
|
-
const result = await apiRequest(`/cli/templates/${encodeURIComponent(resolvedName)}/setup-builds`, { method:
|
|
1333
|
+
const result = await apiRequest(`/cli/templates/${encodeURIComponent(resolvedName)}/setup-builds`, { method: "POST" });
|
|
1291
1334
|
if (result.success) {
|
|
1292
|
-
spinner.succeed(
|
|
1335
|
+
spinner.succeed("CF Workers Builds configured!");
|
|
1293
1336
|
console.log(chalk.gray(`\n Worker: ${result.workerName}`));
|
|
1294
|
-
console.log(chalk.gray(
|
|
1295
|
-
console.log(chalk.gray(
|
|
1337
|
+
console.log(chalk.gray(" Auto-deploy: pushes to main/master trigger build + deploy"));
|
|
1338
|
+
console.log(chalk.gray("\n Changes will be auto-deployed on push. No manual build/upload needed.\n"));
|
|
1296
1339
|
}
|
|
1297
1340
|
else {
|
|
1298
|
-
spinner.fail(
|
|
1299
|
-
console.log(chalk.red(`\n ${result.error ||
|
|
1341
|
+
spinner.fail("CF Builds setup failed");
|
|
1342
|
+
console.log(chalk.red(`\n ${result.error || "Unknown error"}\n`));
|
|
1300
1343
|
process.exit(1);
|
|
1301
1344
|
}
|
|
1302
1345
|
}
|
|
1303
1346
|
catch (error) {
|
|
1304
|
-
spinner.fail(
|
|
1347
|
+
spinner.fail("Failed to setup CF Builds");
|
|
1305
1348
|
console.log(chalk.red(`\n${error.message}\n`));
|
|
1306
1349
|
process.exit(1);
|
|
1307
1350
|
}
|