@doswiftly/cli 0.1.5 → 0.1.7

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.
@@ -1,13 +1,13 @@
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';
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 !== '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'));
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 || 'GET'} ${endpoint}`);
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 || 'GET'),
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 === 'object' && 'response' in 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('\nDoSwiftly CLI - Template Registry\n'));
56
- const spinner = ora('Fetching templates...').start();
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('/cli/templates');
58
+ const templates = await apiRequest("/cli/templates");
59
59
  spinner.stop();
60
60
  if (!templates || templates.length === 0) {
61
- console.log(chalk.yellow(' No templates available.\n'));
61
+ console.log(chalk.yellow(" No templates available.\n"));
62
62
  return;
63
63
  }
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)));
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 === 'PUBLISHED' ? chalk.green : chalk.yellow;
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 || '-').padEnd(15))}` +
75
+ `${chalk.gray((t.uiLibrary || "-").padEnd(15))}` +
76
76
  `${statusColor(t.status.padEnd(12))}` +
77
- `${chalk.gray((t.description || '').slice(0, 30))}`);
77
+ `${chalk.gray((t.description || "").slice(0, 30))}`);
78
78
  }
79
- console.log(chalk.gray(''.repeat(80)));
80
- console.log('');
79
+ console.log(chalk.gray("".repeat(80)));
80
+ console.log("");
81
81
  }
82
82
  catch (error) {
83
- spinner.fail('Failed to fetch templates');
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('\nDoSwiftly CLI - Template Info\n'));
92
+ console.log(chalk.bold.blue("\nDoSwiftly CLI - Template Info\n"));
93
93
  if (!name) {
94
- console.log(chalk.red(' Error: Template name required\n'));
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('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}`);
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('Description:')} ${t.description}`);
106
+ console.log(` ${chalk.bold("Description:")} ${t.description}`);
107
107
  }
108
108
  if (t.uiLibrary) {
109
- console.log(` ${chalk.bold('UI Library:')} ${t.uiLibrary}`);
109
+ console.log(` ${chalk.bold("UI Library:")} ${t.uiLibrary}`);
110
110
  }
111
111
  if (t.features.length > 0) {
112
- console.log(` ${chalk.bold('Features:')} ${t.features.join(', ')}`);
112
+ console.log(` ${chalk.bold("Features:")} ${t.features.join(", ")}`);
113
113
  }
114
114
  if (t.repoUrl) {
115
- console.log(` ${chalk.bold('Repository:')} ${chalk.cyan(t.repoUrl)}`);
115
+ console.log(` ${chalk.bold("Repository:")} ${chalk.cyan(t.repoUrl)}`);
116
116
  }
117
117
  if (t.previewUrl) {
118
- console.log(` ${chalk.bold('Preview:')} ${chalk.cyan(t.previewUrl)}`);
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('Failed to fetch template');
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('doswiftly-template.json'))
132
+ if (!existsSync("doswiftly-template.json"))
133
133
  return null;
134
134
  try {
135
- return JSON.parse(readFileSync('doswiftly-template.json', 'utf-8'));
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('git rev-parse --git-dir', { stdio: 'pipe' });
148
- const url = execSync('git remote get-url origin', { encoding: 'utf-8', stdio: 'pipe' }).trim();
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('git@github.com:')) {
153
- return url.replace('git@github.com:', 'https://github.com/').replace(/\.git$/, '');
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('git rev-parse --git-dir', { stdio: 'pipe' });
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('\nDoSwiftly CLI - Register Template\n'));
188
+ console.log(chalk.bold.blue("\nDoSwiftly CLI - Register Template\n"));
184
189
  // Validate project structure
185
- if (!existsSync('package.json')) {
186
- console.log(chalk.red(' Error: No package.json found. Run this from a template project root.\n'));
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(' Error: Git is not initialized in this directory.'));
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(' Auto-detected doswiftly-template.json'));
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(' Error: Template name required.'));
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(' Warning: No git remote detected. Use --repo-url to specify.'));
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('\n Registration summary:'));
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('/cli/templates', {
229
- method: 'POST',
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('\n Next steps:'));
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(' 2. Publish: doswiftly template publish'));
243
- console.log(chalk.gray(' 3. Set visibility: doswiftly template visibility ' + template.name + ' PUBLIC\n'));
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('Failed to register template');
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('\nDoSwiftly CLI - Update Template\n'));
263
+ console.log(chalk.bold.blue("\nDoSwiftly CLI - Update Template\n"));
257
264
  if (!nameOrId) {
258
- console.log(chalk.red(' Error: Template name or ID required\n'));
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(' No fields to update. Use --description, --repo-url, --ui-library, --preview-url, or --visibility.\n'));
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: 'PUT',
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('Failed to update template');
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('package.json'))
308
+ if (!existsSync("package.json"))
302
309
  return null;
303
310
  try {
304
- const pkg = JSON.parse(readFileSync('package.json', 'utf-8'));
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('\nDoSwiftly CLI - Publish Template\n'));
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(' Error: Template name required.'));
328
- console.log(chalk.gray(' Provide name as argument or run from a directory with doswiftly-template.json\n'));
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(' Error: No version found.'));
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 ? 'package.json' : 'doswiftly-template.json';
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: 'PUT',
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 === 'PRIVATE') {
354
- console.log(chalk.yellow('\n Note: Template is PRIVATE. Set visibility to PUBLIC for users to see it:'));
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('Failed to publish template');
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(' The version is now read automatically from package.json.\n'));
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: 'PUT',
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('Failed to publish template');
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('\nDoSwiftly CLI - Unpublish Template\n'));
404
+ console.log(chalk.bold.blue("\nDoSwiftly CLI - Unpublish Template\n"));
396
405
  if (!name) {
397
- console.log(chalk.red(' Error: Template name required\n'));
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: 'POST',
412
+ method: "POST",
404
413
  });
405
414
  spinner.succeed(`Template '${name}' unpublished`);
406
- console.log(chalk.yellow('\n Template has been deprecated and will no longer appear in listings.\n'));
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('Failed to unpublish template');
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('\n Error: Template name or ID required\n'));
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 || !['PUBLIC', 'PRIVATE'].includes(normalizedVis)) {
425
- console.log(chalk.red('\n Error: Visibility must be PUBLIC or PRIVATE\n'));
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, 'utf-8');
453
+ let content = readFileSync(srcPath, "utf-8");
445
454
  for (const [placeholder, value] of Object.entries(replacements)) {
446
- content = content.replace(new RegExp(placeholder, 'g'), value);
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.replace(baseDir + '/', '').replace(baseDir + '\\', '');
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 === '.git')
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, 'utf-8');
482
+ const content = readFileSync(fullPath, "utf-8");
472
483
  // Use forward slashes for GitHub API
473
- files.push({ path: relativePath.replace(/\\/g, '/'), content });
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('\nDoSwiftly CLI - Create Template\n'));
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: 'Template name:',
494
- placeholder: 'my-awesome-template',
504
+ message: "Template name:",
505
+ placeholder: "my-awesome-template",
495
506
  validate: (v) => {
496
507
  if (!v)
497
- return 'Name is required';
508
+ return "Name is required";
498
509
  if (!/^[a-z0-9-]+$/.test(v))
499
- return 'Use lowercase letters, numbers, and hyphens only';
510
+ return "Use lowercase letters, numbers, and hyphens only";
500
511
  if (v.length < 3)
501
- return 'At least 3 characters';
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('\n Operation cancelled.\n'));
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('\n Error: Template name must be lowercase, alphanumeric, and may contain hyphens.\n'));
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: 'Template description (optional):',
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('\n Operation cancelled.\n'));
535
+ console.log(chalk.yellow("\n Operation cancelled.\n"));
525
536
  process.exit(0);
526
537
  }
527
- description = descResult || `DoSwiftly storefront template: ${templateName}`;
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: 'Base template to start from:',
546
+ message: "Base template to start from:",
534
547
  options: [
535
548
  {
536
- value: 'storefront-nextjs-shadcn',
537
- label: 'shadcn/ui (Recommended)',
538
- hint: 'Next.js + shadcn/ui + Tailwind CSS',
549
+ value: "storefront-nextjs-shadcn",
550
+ label: "shadcn/ui (Recommended)",
551
+ hint: "Next.js + shadcn/ui + Tailwind CSS",
539
552
  },
540
553
  {
541
- value: 'storefront-minimal',
542
- label: 'Minimal (Next.js only)',
543
- hint: 'Minimal Next.js starter',
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('\n Operation cancelled.\n'));
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
- 'storefront-nextjs-shadcn': 'shadcn/ui',
556
- 'storefront-minimal': 'none',
568
+ "storefront-nextjs-shadcn": "shadcn/ui",
569
+ "storefront-minimal": "none",
557
570
  };
558
- const uiLibrary = uiLibraryMap[baseTemplate] || 'none';
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: 'Create GitHub repository in DoSwiftly organization?',
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('\n Operation cancelled.\n'));
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: 'GitHub username to add as collaborator (Enter to skip):',
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 'Invalid GitHub username format';
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('\n Operation cancelled.\n'));
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('Creating GitHub repository...').start();
617
+ const repoSpinner = ora("Creating GitHub repository...").start();
605
618
  try {
606
- repoInfo = await apiRequest('/cli/templates/create-repo', {
607
- method: 'POST',
608
- body: JSON.stringify({ name: templateName, description, githubUsername }),
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('Failed to create GitHub repository');
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('not configured') || errorMsg.includes('not installed')) {
623
- console.log(chalk.yellow('\n GitHub integration is not available.'));
624
- console.log(chalk.gray(' You can continue without GitHub integration.\n'));
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: 'Continue without GitHub integration?',
644
+ message: "Continue without GitHub integration?",
627
645
  initialValue: true,
628
646
  });
629
647
  if (p.isCancel(continueResult) || !continueResult) {
630
- console.log(chalk.yellow('\n Operation cancelled.\n'));
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('already exists')) {
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('permission') || errorMsg.includes('403')) {
657
+ else if (errorMsg.includes("permission") || errorMsg.includes("403")) {
640
658
  // Permission-related error - offer to continue without GitHub
641
- console.log(chalk.yellow('\n GitHub App does not have required permissions.'));
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: 'Continue without GitHub integration?',
662
+ message: "Continue without GitHub integration?",
645
663
  initialValue: true,
646
664
  });
647
665
  if (p.isCancel(continueResult) || !continueResult) {
648
- console.log(chalk.yellow('\n Operation cancelled.\n'));
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('Creating template project...').start();
678
+ const scaffoldSpinner = ora("Creating template project...").start();
661
679
  try {
662
- const templateDir = join(__dirname, '..', '..', 'templates', baseTemplate);
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
- '\\{\\{PROJECT_NAME\\}\\}': templateName,
669
- '\\{\\{SHOP_SLUG\\}\\}': '{{SHOP_SLUG}}',
670
- '\\{\\{API_URL\\}\\}': '{{API_URL}}',
671
- '\\{\\{SDK_VERSION\\}\\}': '^1.0.0',
672
- '\\{\\{COMMERCE_SDK_VERSION\\}\\}': '^1.0.0',
673
- '\\{\\{STOREFRONT_OPS_VERSION\\}\\}': '^1.0.0',
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: '0.1.0',
700
+ version: "0.1.0",
683
701
  };
684
- writeFileSync(join(targetDir, 'doswiftly-template.json'), JSON.stringify(manifest, null, 2) + '\n');
702
+ writeFileSync(join(targetDir, "doswiftly-template.json"), JSON.stringify(manifest, null, 2) + "\n");
685
703
  // Initialize git
686
- scaffoldSpinner.text = 'Initializing git repository...';
704
+ scaffoldSpinner.text = "Initializing git repository...";
687
705
  let gitInitialized = false;
688
- let localBranch = 'main';
706
+ let localBranch = "main";
689
707
  try {
690
- execFileSync('git', ['init'], { stdio: 'pipe', cwd: targetDir });
691
- execFileSync('git', ['add', '-A'], { stdio: 'pipe', cwd: targetDir });
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('git', ['config', 'user.name'], { stdio: 'pipe', cwd: targetDir });
712
+ execFileSync("git", ["config", "user.name"], {
713
+ stdio: "pipe",
714
+ cwd: targetDir,
715
+ });
695
716
  }
696
717
  catch {
697
- execFileSync('git', ['config', 'user.name', 'DoSwiftly CLI'], { stdio: 'pipe', cwd: targetDir });
718
+ execFileSync("git", ["config", "user.name", "DoSwiftly CLI"], {
719
+ stdio: "pipe",
720
+ cwd: targetDir,
721
+ });
698
722
  }
699
723
  try {
700
- execFileSync('git', ['config', 'user.email'], { stdio: 'pipe', cwd: targetDir });
724
+ execFileSync("git", ["config", "user.email"], {
725
+ stdio: "pipe",
726
+ cwd: targetDir,
727
+ });
701
728
  }
702
729
  catch {
703
- execFileSync('git', ['config', 'user.email', 'cli@doswiftly.com'], { stdio: 'pipe', cwd: targetDir });
730
+ execFileSync("git", ["config", "user.email", "cli@doswiftly.pl"], {
731
+ stdio: "pipe",
732
+ cwd: targetDir,
733
+ });
704
734
  }
705
- execFileSync('git', ['commit', '-m', `chore: initialize ${templateName} template`], {
706
- stdio: 'pipe',
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 = execSync('git branch --show-current', {
712
- stdio: 'pipe',
713
- cwd: targetDir,
714
- encoding: 'utf-8',
715
- }).trim() || 'main';
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 = 'main';
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 || 'Unknown git error';
725
- scaffoldSpinner.fail('Failed to initialize git');
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 = 'Configuring git remote...';
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('ssh -T git@github.com 2>&1 || true', { stdio: 'pipe', encoding: 'utf-8' });
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('ssh -o BatchMode=yes -o StrictHostKeyChecking=no git@github.com 2>&1 || true', {
739
- stdio: 'pipe',
740
- encoding: 'utf-8',
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('successfully authenticated') || sshTest.includes('Hi ')) {
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: 'pipe',
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('Template project created');
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('Pushing to GitHub...').start();
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: ['pipe', 'pipe', 'pipe'],
771
- encoding: 'utf-8',
805
+ stdio: ["pipe", "pipe", "pipe"],
806
+ encoding: "utf-8",
772
807
  });
773
- pushSpinner.succeed('Pushed to GitHub');
808
+ pushSpinner.succeed("Pushed to GitHub");
774
809
  }
775
810
  catch (pushError) {
776
- pushSpinner.fail('Failed to push to GitHub');
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 || 'Unknown error';
780
- console.log(chalk.red('\n Git error:'));
781
- console.log(chalk.gray(` ${gitError.trim().split('\n').join('\n ')}`));
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('\n Possible solutions:'));
784
- console.log(chalk.gray(' 1. Run: gh auth login (if you have GitHub CLI)'));
785
- console.log(chalk.gray(' 2. Configure SSH key for GitHub'));
786
- console.log(chalk.gray(' 3. Use HTTPS with credential manager'));
787
- console.log(chalk.yellow('\n Then push manually:'));
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('\n Git not initialized. Push manually after fixing git config:'));
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('Registering template in registry...').start();
832
+ const registerSpinner = ora("Registering template in registry...").start();
798
833
  try {
799
- const template = await apiRequest('/cli/templates', {
800
- method: 'POST',
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('Failed to register template');
814
- const errorMsg = regError.message || 'Unknown error';
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(' You can register manually with:'));
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('\n Cloudflare Workers Builds configured!'));
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(' Auto-deploy: pushes to main/master trigger build + deploy'));
860
+ console.log(chalk.gray(" Auto-deploy: pushes to main/master trigger build + deploy"));
826
861
  }
827
862
  else {
828
- console.log(chalk.yellow('\n CF Builds setup failed: ' + (repoInfo.buildsSetup.error || 'unknown')));
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('Failed to create template project');
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('\n ✓ Template created successfully!\n'));
840
- console.log(chalk.gray(' Location: ') + chalk.cyan(`${targetDir}/`));
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(' Repository: ') + chalk.cyan(repoInfo.repoUrl));
878
+ console.log(chalk.gray(" Repository: ") + chalk.cyan(repoInfo.repoUrl));
843
879
  }
844
- console.log(chalk.bold('\n Next steps:\n'));
880
+ console.log(chalk.bold("\n Next steps:\n"));
845
881
  console.log(chalk.cyan(` cd ${templateName}`));
846
- console.log(chalk.gray(' # Customize your template'));
882
+ console.log(chalk.gray(" # Customize your template"));
847
883
  if (!repoInfo) {
848
- console.log(chalk.cyan(' git remote add origin <your-github-repo-url>'));
849
- console.log(chalk.cyan(' git push -u origin main'));
850
- console.log(chalk.cyan(' doswiftly template register'));
851
- console.log(chalk.gray(' # Update version in package.json, then:'));
852
- console.log(chalk.cyan(' doswiftly template publish'));
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(' # Make changes, commit and push:'));
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(' # CF Builds will auto-build and deploy on push'));
893
+ console.log(chalk.gray(" # CF Builds will auto-build and deploy on push"));
858
894
  }
859
895
  else {
860
- console.log(chalk.gray(' # Update version in package.json, then:'));
861
- console.log(chalk.cyan(' doswiftly template publish'));
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('\nDoSwiftly CLI - Validate Template\n'));
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(' Checking doswiftly-template.json...'));
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('doswiftly-template.json not found. Create one with required fields: name, description, uiLibrary');
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(' ✓ doswiftly-template.json exists'));
944
+ console.log(chalk.green(" ✓ doswiftly-template.json exists"));
909
945
  }
910
946
  // Check 2: package.json
911
- console.log(chalk.gray(' Checking package.json...'));
912
- if (!existsSync('package.json')) {
947
+ console.log(chalk.gray(" Checking package.json..."));
948
+ if (!existsSync("package.json")) {
913
949
  result.valid = false;
914
- result.errors.push('package.json not found');
950
+ result.errors.push("package.json not found");
915
951
  }
916
952
  else {
917
953
  try {
918
- const pkg = JSON.parse(readFileSync('package.json', 'utf-8'));
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 = ['next', 'react', 'react-dom'];
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['@doswiftly/commerce-sdk'] && !allDeps['@doswiftly/storefront-operations']) {
931
- result.warnings.push('package.json: Consider adding @doswiftly/commerce-sdk for storefront functionality');
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(' ✓ package.json exists'));
970
+ console.log(chalk.green(" ✓ package.json exists"));
934
971
  }
935
972
  catch {
936
973
  result.valid = false;
937
- result.errors.push('package.json is not valid JSON');
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(' Checking git configuration...'));
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(' Checking required files...'));
992
+ console.log(chalk.gray(" Checking required files..."));
956
993
  const recommendedFiles = [
957
- { path: 'README.md', required: false },
958
- { path: '.gitignore', required: false },
959
- { path: 'tsconfig.json', required: false },
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(' Checking placeholder tokens...'));
974
- const checkFiles = ['package.json', 'doswiftly-template.json'];
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, 'utf-8');
1014
+ const content = readFileSync(file, "utf-8");
978
1015
  // Templates should contain placeholders like {{SHOP_SLUG}}
979
- if (content.includes('{{SHOP_SLUG}}') || content.includes('{{API_URL}}')) {
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(' Running build test...'));
987
- const buildSpinner = ora('Building project...').start();
1024
+ console.log(chalk.gray(" Running build test..."));
1025
+ const buildSpinner = ora("Building project...").start();
988
1026
  try {
989
- execSync('npm run build', { stdio: 'pipe' });
990
- buildSpinner.succeed('Build succeeded');
1027
+ execSync("npm run build", { stdio: "pipe" });
1028
+ buildSpinner.succeed("Build succeeded");
991
1029
  }
992
1030
  catch (buildError) {
993
- buildSpinner.fail('Build failed');
1031
+ buildSpinner.fail("Build failed");
994
1032
  result.valid = false;
995
- result.errors.push('Build failed. Fix build errors before publishing.');
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(' Errors:'));
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(' Warnings:'));
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(' ✓ Template is valid and ready for publishing!\n'));
1016
- console.log(chalk.gray(' Next steps:'));
1017
- console.log(chalk.cyan(' doswiftly template register'));
1018
- console.log(chalk.cyan(' doswiftly template version <name> 1.0.0\n'));
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(' ✗ Template validation failed. Fix errors before publishing.\n'));
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('\nDoSwiftly CLI - Build Template for Cloudflare\n'));
1072
+ console.log(chalk.bold.blue("\nDoSwiftly CLI - Build Template for Cloudflare\n"));
1035
1073
  // Check for required files
1036
- if (!existsSync('package.json')) {
1037
- console.log(chalk.red(' Error: No package.json found. Run this from a template project root.\n'));
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 || 'template';
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('package.json', 'utf-8'));
1085
+ const pkg = JSON.parse(readFileSync("package.json", "utf-8"));
1048
1086
  const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
1049
- hasOpenNext = !!allDeps['@opennextjs/cloudflare'] || !!allDeps['@opennextjs/cloudflare'];
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('\n Warning: @opennextjs/cloudflare not found in dependencies.'));
1056
- console.log(chalk.gray(' Install it with: pnpm add -D @opennextjs/cloudflare'));
1057
- console.log(chalk.gray(' Or: npx @opennextjs/cloudflare build (will use npx version)\n'));
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('Building Next.js application...').start();
1101
+ const nextSpinner = ora("Building Next.js application...").start();
1062
1102
  try {
1063
1103
  // Try pnpm first, then npm
1064
1104
  try {
1065
- execSync('pnpm build', { stdio: 'pipe', cwd: process.cwd() });
1105
+ execSync("pnpm build", { stdio: "pipe", cwd: process.cwd() });
1066
1106
  }
1067
1107
  catch {
1068
- execSync('npm run build', { stdio: 'pipe', cwd: process.cwd() });
1108
+ execSync("npm run build", { stdio: "pipe", cwd: process.cwd() });
1069
1109
  }
1070
- nextSpinner.succeed('Next.js build completed');
1110
+ nextSpinner.succeed("Next.js build completed");
1071
1111
  }
1072
1112
  catch (error) {
1073
- nextSpinner.fail('Next.js build failed');
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('\n').slice(0, 10).join('\n ')}\n`));
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(' Skipping Next.js build (--skip-next-build)\n'));
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('Building for Cloudflare Workers (OpenNext)...').start();
1124
+ const openNextSpinner = ora("Building for Cloudflare Workers (OpenNext)...").start();
1085
1125
  try {
1086
- execSync('npx @opennextjs/cloudflare build', { stdio: 'pipe', cwd: process.cwd() });
1087
- openNextSpinner.succeed('OpenNext build completed');
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('OpenNext build failed');
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('\n').slice(0, 10).join('\n ')}\n`));
1094
- console.log(chalk.yellow(' Make sure @opennextjs/cloudflare is installed:'));
1095
- console.log(chalk.gray(' pnpm add -D @opennextjs/cloudflare\n'));
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 = '.open-next/worker.js';
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(' The OpenNext build may have failed silently.\n'));
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 ? '' : ' --version 1.0.0'}\n`));
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('\nDoSwiftly CLI - Upload Template Bundle\n'));
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(' Run this from a template project root.\n'));
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(' Error: No version found.'));
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(' Error: Version must be semver format (e.g., 1.0.0 or 1.0.0-beta.1)\n'));
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 || '.open-next/worker.js';
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(' Build first with: doswiftly template build\n'));
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('Uploading bundle to R2...').start();
1203
+ const spinner = ora("Uploading bundle to R2...").start();
1161
1204
  try {
1162
1205
  // Use FormData for multipart upload
1163
- const FormData = (await import('form-data')).default;
1206
+ const FormData = (await import("form-data")).default;
1164
1207
  const formData = new FormData();
1165
- formData.append('bundle', workerBuffer, {
1166
- filename: 'worker.js',
1167
- contentType: 'application/javascript',
1208
+ formData.append("bundle", workerBuffer, {
1209
+ filename: "worker.js",
1210
+ contentType: "application/javascript",
1168
1211
  });
1169
- formData.append('version', version);
1212
+ formData.append("version", version);
1170
1213
  // Get OpenNext version from manifest if available
1171
1214
  try {
1172
- const openNextManifestPath = '.open-next/open-next.output.json';
1215
+ const openNextManifestPath = ".open-next/open-next.output.json";
1173
1216
  if (existsSync(openNextManifestPath)) {
1174
- const openNextManifest = JSON.parse(readFileSync(openNextManifestPath, 'utf-8'));
1217
+ const openNextManifest = JSON.parse(readFileSync(openNextManifestPath, "utf-8"));
1175
1218
  if (openNextManifest.version) {
1176
- formData.append('openNextVersion', openNextManifest.version);
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('nodeVersion', process.version);
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: 'POST',
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('Bundle uploaded successfully');
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('\n ✓ Bundle ready for deployment!'));
1201
- console.log(chalk.gray(' Deploy from Admin Panel: Template Registry → Deploy to Cloudflare\n'));
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('Failed to upload bundle');
1247
+ spinner.fail("Failed to upload bundle");
1205
1248
  // Extract meaningful error message
1206
- if (error && typeof error === 'object' && 'response' in 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 || 'Unknown error'}\n`));
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('\nDoSwiftly CLI - Template Bundle Info\n'));
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(' Error: Template name required.'));
1232
- console.log(chalk.gray(' Provide name as argument or run from a directory with doswiftly-template.json\n'));
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('Template:')} ${chalk.cyan(resolvedName)}`);
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(' No bundle uploaded yet.'));
1243
- console.log(chalk.gray('\n Build and upload with:'));
1244
- console.log(chalk.cyan(' doswiftly template build'));
1245
- console.log(chalk.cyan(' doswiftly template upload\n'));
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(' R2 Bundle:'));
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) + ' KB' : '-'}`);
1251
- console.log(` Uploaded: ${info.bundleUploadedAt ? new Date(info.bundleUploadedAt).toLocaleString() : '-'}`);
1252
- console.log('');
1253
- console.log(chalk.bold(' Cloudflare Workers:'));
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(' Deploy from Admin Panel: Template Registry → Deploy to Cloudflare'));
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('Failed to fetch bundle info');
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('\nDoSwiftly CLI - Setup CF Workers Builds\n'));
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(' Error: Template name required.'));
1285
- console.log(chalk.gray(' Provide name as argument or run from a directory with doswiftly-template.json\n'));
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: 'POST' });
1333
+ const result = await apiRequest(`/cli/templates/${encodeURIComponent(resolvedName)}/setup-builds`, { method: "POST" });
1291
1334
  if (result.success) {
1292
- spinner.succeed('CF Workers Builds configured!');
1335
+ spinner.succeed("CF Workers Builds configured!");
1293
1336
  console.log(chalk.gray(`\n Worker: ${result.workerName}`));
1294
- console.log(chalk.gray(' Auto-deploy: pushes to main/master trigger build + deploy'));
1295
- console.log(chalk.gray('\n Changes will be auto-deployed on push. No manual build/upload needed.\n'));
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('CF Builds setup failed');
1299
- console.log(chalk.red(`\n ${result.error || 'Unknown error'}\n`));
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('Failed to setup CF Builds');
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
  }