create-specra 0.2.1 → 0.2.2

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.
Files changed (49) hide show
  1. package/README.md +11 -14
  2. package/dist/api-client-4XZPF7GL.js +15 -0
  3. package/dist/api-client-4XZPF7GL.js.map +1 -0
  4. package/dist/chunk-ATRUBLRX.js +102 -0
  5. package/dist/chunk-ATRUBLRX.js.map +1 -0
  6. package/dist/{chunk-MA7QG54W.js → chunk-CIM73PDF.js} +22 -2
  7. package/dist/chunk-CIM73PDF.js.map +1 -0
  8. package/dist/cli.js +17 -10
  9. package/dist/cli.js.map +1 -1
  10. package/dist/{deploy-SCEMUQNS.js → deploy-ETWFNB4Z.js} +48 -36
  11. package/dist/deploy-ETWFNB4Z.js.map +1 -0
  12. package/dist/doctor-IFELWGQB.js +270 -0
  13. package/dist/doctor-IFELWGQB.js.map +1 -0
  14. package/dist/index.js +43 -9
  15. package/dist/index.js.map +1 -1
  16. package/dist/{login-NKDRQXRE.js → login-DRPP77G4.js} +24 -9
  17. package/dist/login-DRPP77G4.js.map +1 -0
  18. package/dist/logout-UJFYUAQC.js +24 -0
  19. package/dist/logout-UJFYUAQC.js.map +1 -0
  20. package/dist/{logs-YDAUCMAV.js → logs-K2CSCKOE.js} +26 -20
  21. package/dist/logs-K2CSCKOE.js.map +1 -0
  22. package/dist/{projects-3TAY7EDJ.js → projects-NORBBC4D.js} +13 -6
  23. package/dist/projects-NORBBC4D.js.map +1 -0
  24. package/package.json +3 -2
  25. package/templates/book-docs/src/app.css +3 -4
  26. package/templates/jbrains-docs/src/app.css +3 -4
  27. package/templates/minimal/package.json +1 -1
  28. package/templates/minimal/specra.config.json +13 -1
  29. package/templates/minimal/src/app.css +3 -4
  30. package/templates/minimal/src/hooks.server.ts +8 -0
  31. package/templates/minimal/src/routes/+error.svelte +10 -0
  32. package/templates/minimal/src/routes/+layout.server.ts +3 -0
  33. package/templates/minimal/src/routes/+page.svelte +149 -0
  34. package/templates/minimal/src/routes/docs/[version]/+page.server.ts +14 -0
  35. package/templates/minimal/src/routes/docs/[version]/[...slug]/+page.server.ts +1 -1
  36. package/templates/minimal/svelte.config.js +6 -1
  37. package/dist/chunk-3DKWECRK.js +0 -45
  38. package/dist/chunk-3DKWECRK.js.map +0 -1
  39. package/dist/chunk-MA7QG54W.js.map +0 -1
  40. package/dist/deploy-SCEMUQNS.js.map +0 -1
  41. package/dist/login-NKDRQXRE.js.map +0 -1
  42. package/dist/logout-H543QEKU.js +0 -20
  43. package/dist/logout-H543QEKU.js.map +0 -1
  44. package/dist/logs-YDAUCMAV.js.map +0 -1
  45. package/dist/projects-3TAY7EDJ.js.map +0 -1
  46. /package/templates/minimal/{public → static}/api-specs/openapi-example.json +0 -0
  47. /package/templates/minimal/{public → static}/api-specs/postman-example.json +0 -0
  48. /package/templates/minimal/{public → static}/api-specs/test-api.json +0 -0
  49. /package/templates/minimal/{public → static}/api-specs/users-api.json +0 -0
@@ -0,0 +1,270 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/commands/doctor.ts
4
+ import pc from "picocolors";
5
+ import { existsSync, readFileSync, readdirSync } from "fs";
6
+ import { join, resolve } from "path";
7
+ async function doctor(options) {
8
+ const dir = resolve(options.dir);
9
+ const results = [];
10
+ console.log();
11
+ console.log(pc.bold("Specra Doctor"));
12
+ console.log(pc.dim(`Checking project in ${dir}`));
13
+ console.log();
14
+ const configPath = join(dir, "specra.config.json");
15
+ if (!existsSync(configPath)) {
16
+ results.push({
17
+ label: "specra.config.json",
18
+ status: "fail",
19
+ message: "File not found. Run `create-specra init` to create a new project."
20
+ });
21
+ printResults(results);
22
+ return;
23
+ }
24
+ results.push({
25
+ label: "specra.config.json",
26
+ status: "pass",
27
+ message: "Found"
28
+ });
29
+ let config;
30
+ try {
31
+ const raw = readFileSync(configPath, "utf-8");
32
+ config = JSON.parse(raw);
33
+ } catch (err) {
34
+ results.push({
35
+ label: "Config parsing",
36
+ status: "fail",
37
+ message: `Invalid JSON: ${err instanceof Error ? err.message : err}`
38
+ });
39
+ printResults(results);
40
+ return;
41
+ }
42
+ results.push({
43
+ label: "Config parsing",
44
+ status: "pass",
45
+ message: "Valid JSON"
46
+ });
47
+ if (!config.site) {
48
+ results.push({
49
+ label: "site",
50
+ status: "fail",
51
+ message: 'Missing required "site" section'
52
+ });
53
+ } else {
54
+ if (!config.site.title || typeof config.site.title !== "string") {
55
+ results.push({
56
+ label: "site.title",
57
+ status: "fail",
58
+ message: 'Missing required "site.title" (string)'
59
+ });
60
+ } else {
61
+ results.push({
62
+ label: "site.title",
63
+ status: "pass",
64
+ message: config.site.title
65
+ });
66
+ }
67
+ if (!config.site.description) {
68
+ results.push({
69
+ label: "site.description",
70
+ status: "warn",
71
+ message: "No description set. Recommended for SEO."
72
+ });
73
+ } else {
74
+ results.push({
75
+ label: "site.description",
76
+ status: "pass",
77
+ message: "Set"
78
+ });
79
+ }
80
+ if (!config.site.url) {
81
+ results.push({
82
+ label: "site.url",
83
+ status: "warn",
84
+ message: "No URL set. Recommended for canonical URLs and SEO."
85
+ });
86
+ } else {
87
+ results.push({
88
+ label: "site.url",
89
+ status: "pass",
90
+ message: config.site.url
91
+ });
92
+ }
93
+ }
94
+ const versioning = config.features?.versioning !== false;
95
+ const docsDir = join(dir, "docs");
96
+ if (existsSync(docsDir)) {
97
+ results.push({
98
+ label: "docs/ directory",
99
+ status: "pass",
100
+ message: "Found"
101
+ });
102
+ if (versioning) {
103
+ const entries = readdirSync(docsDir, { withFileTypes: true });
104
+ const versionDirs = entries.filter((e) => e.isDirectory());
105
+ if (versionDirs.length === 0) {
106
+ results.push({
107
+ label: "Version directories",
108
+ status: "fail",
109
+ message: "Versioning enabled but no version directories found in docs/ (e.g., docs/v1.0.0/)"
110
+ });
111
+ } else {
112
+ const names = versionDirs.map((d) => d.name).join(", ");
113
+ results.push({
114
+ label: "Version directories",
115
+ status: "pass",
116
+ message: `Found: ${names}`
117
+ });
118
+ if (config.site?.activeVersion) {
119
+ const hasMatch = versionDirs.some((d) => d.name === config.site.activeVersion);
120
+ if (!hasMatch) {
121
+ results.push({
122
+ label: "site.activeVersion",
123
+ status: "warn",
124
+ message: `"${config.site.activeVersion}" does not match any docs directory (${names})`
125
+ });
126
+ } else {
127
+ results.push({
128
+ label: "site.activeVersion",
129
+ status: "pass",
130
+ message: config.site.activeVersion
131
+ });
132
+ }
133
+ } else {
134
+ results.push({
135
+ label: "site.activeVersion",
136
+ status: "warn",
137
+ message: "Not set. The first version directory will be used as default."
138
+ });
139
+ }
140
+ }
141
+ }
142
+ } else {
143
+ results.push({
144
+ label: "docs/ directory",
145
+ status: "fail",
146
+ message: "Not found. Create a docs/ directory with your documentation files."
147
+ });
148
+ }
149
+ if (config.search?.provider === "meilisearch") {
150
+ if (!config.search.meilisearch?.host) {
151
+ results.push({
152
+ label: "search.meilisearch.host",
153
+ status: "fail",
154
+ message: "Meilisearch provider selected but no host configured"
155
+ });
156
+ }
157
+ if (!config.search.meilisearch?.indexName) {
158
+ results.push({
159
+ label: "search.meilisearch.indexName",
160
+ status: "fail",
161
+ message: "Meilisearch provider selected but no indexName configured"
162
+ });
163
+ }
164
+ if (config.search.meilisearch?.host && config.search.meilisearch?.indexName) {
165
+ results.push({
166
+ label: "Search (Meilisearch)",
167
+ status: "pass",
168
+ message: `${config.search.meilisearch.host} / ${config.search.meilisearch.indexName}`
169
+ });
170
+ }
171
+ }
172
+ if (config.features?.i18n && typeof config.features.i18n === "object") {
173
+ const i18n = config.features.i18n;
174
+ if (!i18n.defaultLocale) {
175
+ results.push({
176
+ label: "i18n.defaultLocale",
177
+ status: "fail",
178
+ message: "i18n enabled but no defaultLocale set"
179
+ });
180
+ }
181
+ if (!i18n.locales || !Array.isArray(i18n.locales) || i18n.locales.length === 0) {
182
+ results.push({
183
+ label: "i18n.locales",
184
+ status: "fail",
185
+ message: "i18n enabled but no locales array defined"
186
+ });
187
+ }
188
+ if (i18n.defaultLocale && i18n.locales?.length) {
189
+ results.push({
190
+ label: "Internationalization",
191
+ status: "pass",
192
+ message: `${i18n.locales.join(", ")} (default: ${i18n.defaultLocale})`
193
+ });
194
+ }
195
+ }
196
+ const pkgPath = join(dir, "package.json");
197
+ if (existsSync(pkgPath)) {
198
+ try {
199
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
200
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies };
201
+ if (deps["specra"]) {
202
+ results.push({
203
+ label: "specra dependency",
204
+ status: "pass",
205
+ message: `${deps["specra"]}`
206
+ });
207
+ } else {
208
+ results.push({
209
+ label: "specra dependency",
210
+ status: "fail",
211
+ message: "specra package not found in dependencies. Run `npm install specra`."
212
+ });
213
+ }
214
+ } catch {
215
+ }
216
+ }
217
+ if (config.auth?.token) {
218
+ results.push({
219
+ label: "auth.token in config",
220
+ status: "warn",
221
+ message: "Token found in specra.config.json. Make sure this file is in .gitignore, or use `specra login --global` to store it in ~/.specra/ instead."
222
+ });
223
+ }
224
+ if (config.navigation?.tabGroups && Array.isArray(config.navigation.tabGroups)) {
225
+ const tabGroups = config.navigation.tabGroups;
226
+ const invalid = tabGroups.filter((t) => !t.id || !t.label);
227
+ if (invalid.length > 0) {
228
+ results.push({
229
+ label: "navigation.tabGroups",
230
+ status: "fail",
231
+ message: `${invalid.length} tab group(s) missing required "id" or "label" fields`
232
+ });
233
+ } else {
234
+ results.push({
235
+ label: "navigation.tabGroups",
236
+ status: "pass",
237
+ message: `${tabGroups.length} tab group(s) configured`
238
+ });
239
+ }
240
+ }
241
+ printResults(results);
242
+ }
243
+ function printResults(results) {
244
+ const icons = {
245
+ pass: pc.green("\u2713"),
246
+ warn: pc.yellow("\u26A0"),
247
+ fail: pc.red("\u2717")
248
+ };
249
+ for (const r of results) {
250
+ const icon = icons[r.status];
251
+ const label = r.status === "fail" ? pc.red(r.label) : r.status === "warn" ? pc.yellow(r.label) : r.label;
252
+ console.log(` ${icon} ${label}: ${pc.dim(r.message)}`);
253
+ }
254
+ const fails = results.filter((r) => r.status === "fail").length;
255
+ const warns = results.filter((r) => r.status === "warn").length;
256
+ const passes = results.filter((r) => r.status === "pass").length;
257
+ console.log();
258
+ if (fails > 0) {
259
+ console.log(pc.red(` ${fails} error(s)`), warns > 0 ? pc.yellow(`${warns} warning(s)`) : "", pc.green(`${passes} passed`));
260
+ } else if (warns > 0) {
261
+ console.log(pc.yellow(` ${warns} warning(s)`), pc.green(`${passes} passed`));
262
+ } else {
263
+ console.log(pc.green(` All ${passes} checks passed!`));
264
+ }
265
+ console.log();
266
+ }
267
+ export {
268
+ doctor
269
+ };
270
+ //# sourceMappingURL=doctor-IFELWGQB.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/doctor.ts"],"sourcesContent":["import pc from 'picocolors'\nimport { existsSync, readFileSync, readdirSync } from 'fs'\nimport { join, resolve } from 'path'\n\ninterface DoctorOptions {\n dir: string\n}\n\ninterface DiagnosticResult {\n label: string\n status: 'pass' | 'warn' | 'fail'\n message: string\n}\n\nexport async function doctor(options: DoctorOptions) {\n const dir = resolve(options.dir)\n const results: DiagnosticResult[] = []\n\n console.log()\n console.log(pc.bold('Specra Doctor'))\n console.log(pc.dim(`Checking project in ${dir}`))\n console.log()\n\n // 1. Check specra.config.json exists\n const configPath = join(dir, 'specra.config.json')\n if (!existsSync(configPath)) {\n results.push({\n label: 'specra.config.json',\n status: 'fail',\n message: 'File not found. Run `create-specra init` to create a new project.',\n })\n printResults(results)\n return\n }\n\n results.push({\n label: 'specra.config.json',\n status: 'pass',\n message: 'Found',\n })\n\n // 2. Parse config\n let config: Record<string, any>\n try {\n const raw = readFileSync(configPath, 'utf-8')\n config = JSON.parse(raw)\n } catch (err) {\n results.push({\n label: 'Config parsing',\n status: 'fail',\n message: `Invalid JSON: ${err instanceof Error ? err.message : err}`,\n })\n printResults(results)\n return\n }\n\n results.push({\n label: 'Config parsing',\n status: 'pass',\n message: 'Valid JSON',\n })\n\n // 3. Check required: site\n if (!config.site) {\n results.push({\n label: 'site',\n status: 'fail',\n message: 'Missing required \"site\" section',\n })\n } else {\n // Check site.title\n if (!config.site.title || typeof config.site.title !== 'string') {\n results.push({\n label: 'site.title',\n status: 'fail',\n message: 'Missing required \"site.title\" (string)',\n })\n } else {\n results.push({\n label: 'site.title',\n status: 'pass',\n message: config.site.title,\n })\n }\n\n // Check site.description\n if (!config.site.description) {\n results.push({\n label: 'site.description',\n status: 'warn',\n message: 'No description set. Recommended for SEO.',\n })\n } else {\n results.push({\n label: 'site.description',\n status: 'pass',\n message: 'Set',\n })\n }\n\n // Check site.url\n if (!config.site.url) {\n results.push({\n label: 'site.url',\n status: 'warn',\n message: 'No URL set. Recommended for canonical URLs and SEO.',\n })\n } else {\n results.push({\n label: 'site.url',\n status: 'pass',\n message: config.site.url,\n })\n }\n }\n\n // 4. Check versioning + docs directory\n const versioning = config.features?.versioning !== false\n const docsDir = join(dir, 'docs')\n\n if (existsSync(docsDir)) {\n results.push({\n label: 'docs/ directory',\n status: 'pass',\n message: 'Found',\n })\n\n if (versioning) {\n // Check for version directories\n const entries = readdirSync(docsDir, { withFileTypes: true })\n const versionDirs = entries.filter((e) => e.isDirectory())\n\n if (versionDirs.length === 0) {\n results.push({\n label: 'Version directories',\n status: 'fail',\n message: 'Versioning enabled but no version directories found in docs/ (e.g., docs/v1.0.0/)',\n })\n } else {\n const names = versionDirs.map((d) => d.name).join(', ')\n results.push({\n label: 'Version directories',\n status: 'pass',\n message: `Found: ${names}`,\n })\n\n // Check activeVersion matches a directory\n if (config.site?.activeVersion) {\n const hasMatch = versionDirs.some((d) => d.name === config.site.activeVersion)\n if (!hasMatch) {\n results.push({\n label: 'site.activeVersion',\n status: 'warn',\n message: `\"${config.site.activeVersion}\" does not match any docs directory (${names})`,\n })\n } else {\n results.push({\n label: 'site.activeVersion',\n status: 'pass',\n message: config.site.activeVersion,\n })\n }\n } else {\n results.push({\n label: 'site.activeVersion',\n status: 'warn',\n message: 'Not set. The first version directory will be used as default.',\n })\n }\n }\n }\n } else {\n results.push({\n label: 'docs/ directory',\n status: 'fail',\n message: 'Not found. Create a docs/ directory with your documentation files.',\n })\n }\n\n // 5. Check search config\n if (config.search?.provider === 'meilisearch') {\n if (!config.search.meilisearch?.host) {\n results.push({\n label: 'search.meilisearch.host',\n status: 'fail',\n message: 'Meilisearch provider selected but no host configured',\n })\n }\n if (!config.search.meilisearch?.indexName) {\n results.push({\n label: 'search.meilisearch.indexName',\n status: 'fail',\n message: 'Meilisearch provider selected but no indexName configured',\n })\n }\n if (config.search.meilisearch?.host && config.search.meilisearch?.indexName) {\n results.push({\n label: 'Search (Meilisearch)',\n status: 'pass',\n message: `${config.search.meilisearch.host} / ${config.search.meilisearch.indexName}`,\n })\n }\n }\n\n // 6. Check i18n config\n if (config.features?.i18n && typeof config.features.i18n === 'object') {\n const i18n = config.features.i18n\n if (!i18n.defaultLocale) {\n results.push({\n label: 'i18n.defaultLocale',\n status: 'fail',\n message: 'i18n enabled but no defaultLocale set',\n })\n }\n if (!i18n.locales || !Array.isArray(i18n.locales) || i18n.locales.length === 0) {\n results.push({\n label: 'i18n.locales',\n status: 'fail',\n message: 'i18n enabled but no locales array defined',\n })\n }\n if (i18n.defaultLocale && i18n.locales?.length) {\n results.push({\n label: 'Internationalization',\n status: 'pass',\n message: `${i18n.locales.join(', ')} (default: ${i18n.defaultLocale})`,\n })\n }\n }\n\n // 7. Check package.json for specra dependency\n const pkgPath = join(dir, 'package.json')\n if (existsSync(pkgPath)) {\n try {\n const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'))\n const deps = { ...pkg.dependencies, ...pkg.devDependencies }\n if (deps['specra']) {\n results.push({\n label: 'specra dependency',\n status: 'pass',\n message: `${deps['specra']}`,\n })\n } else {\n results.push({\n label: 'specra dependency',\n status: 'fail',\n message: 'specra package not found in dependencies. Run `npm install specra`.',\n })\n }\n } catch {\n // ignore parse errors\n }\n }\n\n // 8. Warn if auth token is in specra.config.json (should be gitignored)\n if (config.auth?.token) {\n results.push({\n label: 'auth.token in config',\n status: 'warn',\n message: 'Token found in specra.config.json. Make sure this file is in .gitignore, or use `specra login --global` to store it in ~/.specra/ instead.',\n })\n }\n\n // 9. Check navigation.tabGroups have required fields\n if (config.navigation?.tabGroups && Array.isArray(config.navigation.tabGroups)) {\n const tabGroups = config.navigation.tabGroups\n const invalid = tabGroups.filter((t: any) => !t.id || !t.label)\n if (invalid.length > 0) {\n results.push({\n label: 'navigation.tabGroups',\n status: 'fail',\n message: `${invalid.length} tab group(s) missing required \"id\" or \"label\" fields`,\n })\n } else {\n results.push({\n label: 'navigation.tabGroups',\n status: 'pass',\n message: `${tabGroups.length} tab group(s) configured`,\n })\n }\n }\n\n printResults(results)\n}\n\nfunction printResults(results: DiagnosticResult[]) {\n const icons = {\n pass: pc.green('✓'),\n warn: pc.yellow('⚠'),\n fail: pc.red('✗'),\n }\n\n for (const r of results) {\n const icon = icons[r.status]\n const label = r.status === 'fail' ? pc.red(r.label) : r.status === 'warn' ? pc.yellow(r.label) : r.label\n console.log(` ${icon} ${label}: ${pc.dim(r.message)}`)\n }\n\n const fails = results.filter((r) => r.status === 'fail').length\n const warns = results.filter((r) => r.status === 'warn').length\n const passes = results.filter((r) => r.status === 'pass').length\n\n console.log()\n if (fails > 0) {\n console.log(pc.red(` ${fails} error(s)`), warns > 0 ? pc.yellow(`${warns} warning(s)`) : '', pc.green(`${passes} passed`))\n } else if (warns > 0) {\n console.log(pc.yellow(` ${warns} warning(s)`), pc.green(`${passes} passed`))\n } else {\n console.log(pc.green(` All ${passes} checks passed!`))\n }\n console.log()\n}\n"],"mappings":"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","names":[]}
package/dist/index.js CHANGED
@@ -3,6 +3,7 @@
3
3
  // src/index.ts
4
4
  import { Command } from "commander";
5
5
  import prompts from "prompts";
6
+ import path3 from "path";
6
7
 
7
8
  // src/create-project.ts
8
9
  import fs2 from "fs";
@@ -37,8 +38,8 @@ function isWriteable(directory) {
37
38
  return false;
38
39
  }
39
40
  }
40
- function isFolderEmpty(path3) {
41
- const files = fs.readdirSync(path3);
41
+ function isFolderEmpty(path4) {
42
+ const files = fs.readdirSync(path4);
42
43
  return files.length === 0 || files.length === 1 && files[0] === ".git";
43
44
  }
44
45
  function getPackageManagerCommand(packageManager) {
@@ -119,7 +120,7 @@ async function createProject({
119
120
  } else {
120
121
  fs2.mkdirSync(root, { recursive: true });
121
122
  }
122
- console.log(`Creating a new Specra documentation site in ${pc.cyan(root)}`);
123
+ console.log(`Creating a new Specra documentation site (SvelteKit) in ${pc.cyan(root)}`);
123
124
  console.log();
124
125
  const __filename2 = fileURLToPath(import.meta.url);
125
126
  const __dirname2 = path2.dirname(__filename2);
@@ -166,8 +167,8 @@ async function createProject({
166
167
  console.log(pc.cyan(` ${command.run("build")}`));
167
168
  console.log(" Builds the app for production.");
168
169
  console.log();
169
- console.log(pc.cyan(` ${command.run("start")}`));
170
- console.log(" Runs the built app in production mode.");
170
+ console.log(pc.cyan(` ${command.run("preview")}`));
171
+ console.log(" Previews the built app locally.");
171
172
  console.log();
172
173
  console.log("We suggest that you begin by typing:");
173
174
  console.log();
@@ -178,8 +179,12 @@ async function createProject({
178
179
 
179
180
  // src/index.ts
180
181
  import pc2 from "picocolors";
182
+ import { createRequire } from "module";
183
+ var require2 = createRequire(import.meta.url);
184
+ var { version } = require2("../package.json");
181
185
  var program = new Command();
182
- program.name("create-specra").description("Create a new Specra documentation site").argument("[project-directory]", "Directory to create the project in").option("--template <template>", "Template to use (minimal, book-docs, jbrains-docs)").option("--use-npm", "Use npm as the package manager").option("--use-pnpm", "Use pnpm as the package manager").option("--use-yarn", "Use yarn as the package manager").option("--skip-install", "Skip package installation").action(async (projectDirectory, options) => {
186
+ program.name("create-specra").description("Specra CLI - Create, deploy, and manage your documentation sites").version(version);
187
+ var initCmd = program.command("init [project-directory]", { isDefault: true }).description("Create a new Specra documentation site").option("--template <template>", "Template to use (minimal)").option("--use-npm", "Use npm as the package manager").option("--use-pnpm", "Use pnpm as the package manager").option("--use-yarn", "Use yarn as the package manager").option("--skip-install", "Skip package installation").action(async (projectDirectory, options) => {
183
188
  console.log();
184
189
  console.log(pc2.bold(pc2.cyan("Create Specra Documentation Site")));
185
190
  console.log();
@@ -205,12 +210,13 @@ program.name("create-specra").description("Create a new Specra documentation sit
205
210
  }
206
211
  projectName = response.projectName;
207
212
  }
208
- const validation = validateProjectName(projectName);
213
+ const projectBaseName = path3.basename(path3.resolve(projectName));
214
+ const validation = validateProjectName(projectBaseName);
209
215
  if (!validation.valid) {
210
216
  console.error(
211
217
  pc2.red(
212
218
  `Cannot create a project named ${pc2.cyan(
213
- `"${projectName}"`
219
+ `"${projectBaseName}"`
214
220
  )} because of npm naming restrictions:
215
221
  `
216
222
  )
@@ -275,7 +281,7 @@ program.name("create-specra").description("Create a new Specra documentation sit
275
281
  try {
276
282
  await createProject({
277
283
  projectName,
278
- template: template || "default",
284
+ template: template || "minimal",
279
285
  packageManager: packageManager || "npm",
280
286
  skipInstall: options.skipInstall
281
287
  });
@@ -285,5 +291,33 @@ program.name("create-specra").description("Create a new Specra documentation sit
285
291
  process.exit(1);
286
292
  }
287
293
  });
294
+ program.command("login").description("Authenticate with your Specra account").option("-g, --global", "Store credentials in ~/.specra/ instead of local specra.config.json").action(async (options) => {
295
+ const { login } = await import("./login-DRPP77G4.js");
296
+ await login(options);
297
+ });
298
+ program.command("logout").description("Clear stored credentials").option("-g, --global", "Clear credentials from ~/.specra/ instead of local specra.config.json").action(async (options) => {
299
+ const { logout } = await import("./logout-UJFYUAQC.js");
300
+ await logout(options);
301
+ });
302
+ program.command("deploy").description("Deploy your docs project").option("-p, --project <id>", "Project ID to deploy to").option("-d, --dir <directory>", "Docs directory to deploy", ".").action(async (options) => {
303
+ const { deploy } = await import("./deploy-ETWFNB4Z.js");
304
+ await deploy(options);
305
+ });
306
+ program.command("projects").description("List your projects").action(async () => {
307
+ const { projects } = await import("./projects-NORBBC4D.js");
308
+ await projects();
309
+ });
310
+ program.command("logs").description("View deployment logs").argument("<projectId>", "Project ID").option("--deployment <id>", "Specific deployment ID").action(async (projectId, options) => {
311
+ const { logs } = await import("./logs-K2CSCKOE.js");
312
+ await logs(projectId, options);
313
+ });
314
+ program.command("doctor").description("Check specra.config.json for issues").option("-d, --dir <directory>", "Project directory to check", ".").action(async (options) => {
315
+ const { doctor } = await import("./doctor-IFELWGQB.js");
316
+ await doctor(options);
317
+ });
288
318
  program.parse();
319
+ process.on("unhandledRejection", (err) => {
320
+ console.error(pc2.red("Error:"), err);
321
+ process.exit(1);
322
+ });
289
323
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/create-project.ts","../src/utils.ts"],"sourcesContent":["import { Command } from 'commander'\nimport prompts from 'prompts'\nimport { createProject } from './create-project.js'\nimport { validateProjectName } from './utils.js'\nimport pc from 'picocolors'\n\nconst program = new Command()\n\nprogram\n .name('create-specra')\n .description('Create a new Specra documentation site')\n .argument('[project-directory]', 'Directory to create the project in')\n .option('--template <template>', 'Template to use (minimal, book-docs, jbrains-docs)')\n .option('--use-npm', 'Use npm as the package manager')\n .option('--use-pnpm', 'Use pnpm as the package manager')\n .option('--use-yarn', 'Use yarn as the package manager')\n .option('--skip-install', 'Skip package installation')\n .action(async (projectDirectory: string | undefined, options) => {\n console.log()\n console.log(pc.bold(pc.cyan('Create Specra Documentation Site')))\n console.log()\n\n let projectName = projectDirectory\n\n // Prompt for project name if not provided\n if (!projectName) {\n const response = await prompts({\n type: 'text',\n name: 'projectName',\n message: 'What is your project named?',\n initial: 'my-docs',\n validate: (name) => {\n const validation = validateProjectName(name)\n if (validation.valid) {\n return true\n }\n return validation.problems![0]\n },\n })\n\n if (!response.projectName) {\n console.log()\n console.log('Aborting.')\n process.exit(1)\n }\n\n projectName = response.projectName\n }\n\n // Validate project name\n const validation = validateProjectName(projectName!)\n if (!validation.valid) {\n console.error(\n pc.red(\n `Cannot create a project named ${pc.cyan(\n `\"${projectName}\"`\n )} because of npm naming restrictions:\\n`\n )\n )\n validation.problems!.forEach((p) =>\n console.error(` ${pc.red('•')} ${p}`)\n )\n process.exit(1)\n }\n\n // Prompt for template if not provided\n let template = options.template\n\n if (!template) {\n const response = await prompts({\n type: 'select',\n name: 'template',\n message: 'Which template would you like to use?',\n choices: [\n {\n title: 'Minimal',\n value: 'minimal',\n description: 'Minimal setup to get started quickly',\n },\n {\n title: 'Book Docs',\n value: 'book-docs',\n description: 'Knowledge base style with dark theme and categorized sidebar',\n },\n {\n title: 'JBrains Docs',\n value: 'jbrains-docs',\n description: 'Reference docs style with light theme and tab groups',\n },\n ],\n initial: 0,\n })\n\n if (!response.template) {\n console.log()\n console.log('Aborting.')\n process.exit(1)\n }\n\n template = response.template\n }\n\n // Detect or prompt for package manager\n let packageManager = options.useNpm\n ? 'npm'\n : options.usePnpm\n ? 'pnpm'\n : options.useYarn\n ? 'yarn'\n : undefined\n\n if (!packageManager && !options.skipInstall) {\n const response = await prompts({\n type: 'select',\n name: 'packageManager',\n message: 'Which package manager do you want to use?',\n choices: [\n { title: 'npm', value: 'npm' },\n { title: 'yarn', value: 'yarn' },\n { title: 'pnpm', value: 'pnpm' },\n ],\n initial: 0,\n })\n\n if (!response.packageManager) {\n console.log()\n console.log('Aborting.')\n process.exit(1)\n }\n\n packageManager = response.packageManager\n }\n\n // Create the project\n try {\n await createProject({\n projectName: projectName!,\n template: template || 'default',\n packageManager: packageManager || 'npm',\n skipInstall: options.skipInstall,\n })\n } catch (error) {\n console.error(pc.red('\\nError creating project:'))\n console.error(error)\n process.exit(1)\n }\n })\n\nprogram.parse()\n","import fs from 'fs'\nimport path from 'path'\nimport { fileURLToPath } from 'url'\nimport { execSync } from 'child_process'\nimport pc from 'picocolors'\nimport {\n isWriteable,\n isFolderEmpty,\n getPackageManagerCommand,\n tryGitInit,\n copyRecursive,\n} from './utils.js'\n\ninterface CreateProjectOptions {\n projectName: string\n template: string\n packageManager: string\n skipInstall: boolean\n}\n\nexport async function createProject({\n projectName,\n template,\n packageManager,\n skipInstall,\n}: CreateProjectOptions) {\n const root = path.resolve(projectName)\n const appName = path.basename(root)\n\n // Check if directory exists\n if (fs.existsSync(root)) {\n if (!isWriteable(root)) {\n console.error(\n pc.red(`The directory ${pc.cyan(projectName)} is not writable.`)\n )\n process.exit(1)\n }\n\n if (!isFolderEmpty(root)) {\n console.error(\n pc.red(\n `The directory ${pc.cyan(\n projectName\n )} contains files that could conflict. Please use a new directory.`\n )\n )\n process.exit(1)\n }\n } else {\n fs.mkdirSync(root, { recursive: true })\n }\n\n console.log(`Creating a new Specra documentation site in ${pc.cyan(root)}`)\n console.log()\n\n // Create project structure based on template\n const __filename = fileURLToPath(import.meta.url)\n const __dirname = path.dirname(__filename)\n const templateDir = path.join(__dirname, '..', 'templates', template)\n\n if (!fs.existsSync(templateDir)) {\n console.error(pc.red(`Template ${pc.cyan(template)} not found.`))\n process.exit(1)\n }\n\n console.log(`Using template: ${pc.cyan(template)}`)\n console.log()\n\n // Copy template files\n copyRecursive(templateDir, root)\n\n // Update package.json with project name\n const packageJsonPath = path.join(root, 'package.json')\n const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'))\n packageJson.name = appName\n fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\\n')\n\n // Rename gitignore\n const gitignorePath = path.join(root, 'gitignore')\n if (fs.existsSync(gitignorePath)) {\n fs.renameSync(gitignorePath, path.join(root, '.gitignore'))\n }\n\n // Install dependencies\n if (!skipInstall) {\n console.log('Installing dependencies...')\n console.log()\n\n const command = getPackageManagerCommand(packageManager)\n\n try {\n execSync(command.install, { cwd: root, stdio: 'inherit' })\n } catch (error) {\n console.error(pc.red('Failed to install dependencies.'))\n process.exit(1)\n }\n\n console.log()\n }\n\n // Initialize git\n if (tryGitInit(root)) {\n console.log('Initialized a git repository.')\n console.log()\n }\n\n // Success message\n console.log(pc.green('Success!') + ` Created ${appName} at ${root}`)\n console.log()\n console.log('Inside that directory, you can run several commands:')\n console.log()\n\n const command = getPackageManagerCommand(packageManager)\n\n console.log(pc.cyan(` ${command.run('dev')}`))\n console.log(' Starts the development server.')\n console.log()\n console.log(pc.cyan(` ${command.run('build')}`))\n console.log(' Builds the app for production.')\n console.log()\n console.log(pc.cyan(` ${command.run('start')}`))\n console.log(' Runs the built app in production mode.')\n console.log()\n console.log('We suggest that you begin by typing:')\n console.log()\n console.log(pc.cyan(' cd'), projectName)\n console.log(` ${pc.cyan(command.run('dev'))}`)\n console.log()\n}\n","import validateNpmPackageName from 'validate-npm-package-name'\nimport { execSync } from 'child_process'\nimport fs from 'fs'\nimport path from 'path'\n\nexport function validateProjectName(name: string) {\n const validation = validateNpmPackageName(name)\n\n if (validation.validForNewPackages) {\n return { valid: true }\n }\n\n return {\n valid: false,\n problems: [\n ...(validation.errors || []),\n ...(validation.warnings || []),\n ],\n }\n}\n\nexport function isWriteable(directory: string): boolean {\n try {\n fs.accessSync(directory, fs.constants.W_OK)\n return true\n } catch {\n return false\n }\n}\n\nexport function isFolderEmpty(path: string): boolean {\n const files = fs.readdirSync(path)\n return files.length === 0 || (files.length === 1 && files[0] === '.git')\n}\n\nexport function getPackageManagerCommand(packageManager: string): {\n install: string\n run: (script: string) => string\n} {\n switch (packageManager) {\n case 'yarn':\n return {\n install: 'yarn install',\n run: (script) => `yarn ${script}`,\n }\n case 'pnpm':\n return {\n install: 'pnpm install',\n run: (script) => `pnpm ${script}`,\n }\n case 'npm':\n default:\n return {\n install: 'npm install',\n run: (script) => `npm run ${script}`,\n }\n }\n}\n\nexport function tryGitInit(root: string): boolean {\n try {\n execSync('git --version', { stdio: 'ignore' })\n execSync('git init', { cwd: root, stdio: 'ignore' })\n execSync('git add -A', { cwd: root, stdio: 'ignore' })\n execSync('git commit -m \"Initial commit from create-specra\"', {\n cwd: root,\n stdio: 'ignore',\n })\n return true\n } catch {\n return false\n }\n}\n\nexport function copyRecursive(src: string, dest: string) {\n const stat = fs.statSync(src)\n\n if (stat.isDirectory()) {\n fs.mkdirSync(dest, { recursive: true })\n const entries = fs.readdirSync(src)\n\n for (const entry of entries) {\n const srcPath = path.join(src, entry)\n const destPath = path.join(dest, entry)\n copyRecursive(srcPath, destPath)\n }\n } else {\n fs.copyFileSync(src, dest)\n }\n}\n"],"mappings":";;;AAAA,SAAS,eAAe;AACxB,OAAO,aAAa;;;ACDpB,OAAOA,SAAQ;AACf,OAAOC,WAAU;AACjB,SAAS,qBAAqB;AAC9B,SAAS,YAAAC,iBAAgB;AACzB,OAAO,QAAQ;;;ACJf,OAAO,4BAA4B;AACnC,SAAS,gBAAgB;AACzB,OAAO,QAAQ;AACf,OAAO,UAAU;AAEV,SAAS,oBAAoB,MAAc;AAChD,QAAM,aAAa,uBAAuB,IAAI;AAE9C,MAAI,WAAW,qBAAqB;AAClC,WAAO,EAAE,OAAO,KAAK;AAAA,EACvB;AAEA,SAAO;AAAA,IACL,OAAO;AAAA,IACP,UAAU;AAAA,MACR,GAAI,WAAW,UAAU,CAAC;AAAA,MAC1B,GAAI,WAAW,YAAY,CAAC;AAAA,IAC9B;AAAA,EACF;AACF;AAEO,SAAS,YAAY,WAA4B;AACtD,MAAI;AACF,OAAG,WAAW,WAAW,GAAG,UAAU,IAAI;AAC1C,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEO,SAAS,cAAcC,OAAuB;AACnD,QAAM,QAAQ,GAAG,YAAYA,KAAI;AACjC,SAAO,MAAM,WAAW,KAAM,MAAM,WAAW,KAAK,MAAM,CAAC,MAAM;AACnE;AAEO,SAAS,yBAAyB,gBAGvC;AACA,UAAQ,gBAAgB;AAAA,IACtB,KAAK;AACH,aAAO;AAAA,QACL,SAAS;AAAA,QACT,KAAK,CAAC,WAAW,QAAQ,MAAM;AAAA,MACjC;AAAA,IACF,KAAK;AACH,aAAO;AAAA,QACL,SAAS;AAAA,QACT,KAAK,CAAC,WAAW,QAAQ,MAAM;AAAA,MACjC;AAAA,IACF,KAAK;AAAA,IACL;AACE,aAAO;AAAA,QACL,SAAS;AAAA,QACT,KAAK,CAAC,WAAW,WAAW,MAAM;AAAA,MACpC;AAAA,EACJ;AACF;AAEO,SAAS,WAAW,MAAuB;AAChD,MAAI;AACF,aAAS,iBAAiB,EAAE,OAAO,SAAS,CAAC;AAC7C,aAAS,YAAY,EAAE,KAAK,MAAM,OAAO,SAAS,CAAC;AACnD,aAAS,cAAc,EAAE,KAAK,MAAM,OAAO,SAAS,CAAC;AACrD,aAAS,qDAAqD;AAAA,MAC5D,KAAK;AAAA,MACL,OAAO;AAAA,IACT,CAAC;AACD,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEO,SAAS,cAAc,KAAa,MAAc;AACvD,QAAM,OAAO,GAAG,SAAS,GAAG;AAE5B,MAAI,KAAK,YAAY,GAAG;AACtB,OAAG,UAAU,MAAM,EAAE,WAAW,KAAK,CAAC;AACtC,UAAM,UAAU,GAAG,YAAY,GAAG;AAElC,eAAW,SAAS,SAAS;AAC3B,YAAM,UAAU,KAAK,KAAK,KAAK,KAAK;AACpC,YAAM,WAAW,KAAK,KAAK,MAAM,KAAK;AACtC,oBAAc,SAAS,QAAQ;AAAA,IACjC;AAAA,EACF,OAAO;AACL,OAAG,aAAa,KAAK,IAAI;AAAA,EAC3B;AACF;;;ADrEA,eAAsB,cAAc;AAAA,EAClC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAyB;AACvB,QAAM,OAAOC,MAAK,QAAQ,WAAW;AACrC,QAAM,UAAUA,MAAK,SAAS,IAAI;AAGlC,MAAIC,IAAG,WAAW,IAAI,GAAG;AACvB,QAAI,CAAC,YAAY,IAAI,GAAG;AACtB,cAAQ;AAAA,QACN,GAAG,IAAI,iBAAiB,GAAG,KAAK,WAAW,CAAC,mBAAmB;AAAA,MACjE;AACA,cAAQ,KAAK,CAAC;AAAA,IAChB;AAEA,QAAI,CAAC,cAAc,IAAI,GAAG;AACxB,cAAQ;AAAA,QACN,GAAG;AAAA,UACD,iBAAiB,GAAG;AAAA,YAClB;AAAA,UACF,CAAC;AAAA,QACH;AAAA,MACF;AACA,cAAQ,KAAK,CAAC;AAAA,IAChB;AAAA,EACF,OAAO;AACL,IAAAA,IAAG,UAAU,MAAM,EAAE,WAAW,KAAK,CAAC;AAAA,EACxC;AAEA,UAAQ,IAAI,+CAA+C,GAAG,KAAK,IAAI,CAAC,EAAE;AAC1E,UAAQ,IAAI;AAGZ,QAAMC,cAAa,cAAc,YAAY,GAAG;AAChD,QAAMC,aAAYH,MAAK,QAAQE,WAAU;AACzC,QAAM,cAAcF,MAAK,KAAKG,YAAW,MAAM,aAAa,QAAQ;AAEpE,MAAI,CAACF,IAAG,WAAW,WAAW,GAAG;AAC/B,YAAQ,MAAM,GAAG,IAAI,YAAY,GAAG,KAAK,QAAQ,CAAC,aAAa,CAAC;AAChE,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,UAAQ,IAAI,mBAAmB,GAAG,KAAK,QAAQ,CAAC,EAAE;AAClD,UAAQ,IAAI;AAGZ,gBAAc,aAAa,IAAI;AAG/B,QAAM,kBAAkBD,MAAK,KAAK,MAAM,cAAc;AACtD,QAAM,cAAc,KAAK,MAAMC,IAAG,aAAa,iBAAiB,MAAM,CAAC;AACvE,cAAY,OAAO;AACnB,EAAAA,IAAG,cAAc,iBAAiB,KAAK,UAAU,aAAa,MAAM,CAAC,IAAI,IAAI;AAG7E,QAAM,gBAAgBD,MAAK,KAAK,MAAM,WAAW;AACjD,MAAIC,IAAG,WAAW,aAAa,GAAG;AAChC,IAAAA,IAAG,WAAW,eAAeD,MAAK,KAAK,MAAM,YAAY,CAAC;AAAA,EAC5D;AAGA,MAAI,CAAC,aAAa;AAChB,YAAQ,IAAI,4BAA4B;AACxC,YAAQ,IAAI;AAEZ,UAAMI,WAAU,yBAAyB,cAAc;AAEvD,QAAI;AACF,MAAAC,UAASD,SAAQ,SAAS,EAAE,KAAK,MAAM,OAAO,UAAU,CAAC;AAAA,IAC3D,SAAS,OAAO;AACd,cAAQ,MAAM,GAAG,IAAI,iCAAiC,CAAC;AACvD,cAAQ,KAAK,CAAC;AAAA,IAChB;AAEA,YAAQ,IAAI;AAAA,EACd;AAGA,MAAI,WAAW,IAAI,GAAG;AACpB,YAAQ,IAAI,+BAA+B;AAC3C,YAAQ,IAAI;AAAA,EACd;AAGA,UAAQ,IAAI,GAAG,MAAM,UAAU,IAAI,YAAY,OAAO,OAAO,IAAI,EAAE;AACnE,UAAQ,IAAI;AACZ,UAAQ,IAAI,sDAAsD;AAClE,UAAQ,IAAI;AAEZ,QAAM,UAAU,yBAAyB,cAAc;AAEvD,UAAQ,IAAI,GAAG,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,EAAE,CAAC;AAC9C,UAAQ,IAAI,oCAAoC;AAChD,UAAQ,IAAI;AACZ,UAAQ,IAAI,GAAG,KAAK,KAAK,QAAQ,IAAI,OAAO,CAAC,EAAE,CAAC;AAChD,UAAQ,IAAI,oCAAoC;AAChD,UAAQ,IAAI;AACZ,UAAQ,IAAI,GAAG,KAAK,KAAK,QAAQ,IAAI,OAAO,CAAC,EAAE,CAAC;AAChD,UAAQ,IAAI,4CAA4C;AACxD,UAAQ,IAAI;AACZ,UAAQ,IAAI,sCAAsC;AAClD,UAAQ,IAAI;AACZ,UAAQ,IAAI,GAAG,KAAK,MAAM,GAAG,WAAW;AACxC,UAAQ,IAAI,KAAK,GAAG,KAAK,QAAQ,IAAI,KAAK,CAAC,CAAC,EAAE;AAC9C,UAAQ,IAAI;AACd;;;AD5HA,OAAOE,SAAQ;AAEf,IAAM,UAAU,IAAI,QAAQ;AAE5B,QACG,KAAK,eAAe,EACpB,YAAY,wCAAwC,EACpD,SAAS,uBAAuB,oCAAoC,EACpE,OAAO,yBAAyB,oDAAoD,EACpF,OAAO,aAAa,gCAAgC,EACpD,OAAO,cAAc,iCAAiC,EACtD,OAAO,cAAc,iCAAiC,EACtD,OAAO,kBAAkB,2BAA2B,EACpD,OAAO,OAAO,kBAAsC,YAAY;AAC/D,UAAQ,IAAI;AACZ,UAAQ,IAAIA,IAAG,KAAKA,IAAG,KAAK,kCAAkC,CAAC,CAAC;AAChE,UAAQ,IAAI;AAEZ,MAAI,cAAc;AAGlB,MAAI,CAAC,aAAa;AAChB,UAAM,WAAW,MAAM,QAAQ;AAAA,MAC7B,MAAM;AAAA,MACN,MAAM;AAAA,MACN,SAAS;AAAA,MACT,SAAS;AAAA,MACT,UAAU,CAAC,SAAS;AAClB,cAAMC,cAAa,oBAAoB,IAAI;AAC3C,YAAIA,YAAW,OAAO;AACpB,iBAAO;AAAA,QACT;AACA,eAAOA,YAAW,SAAU,CAAC;AAAA,MAC/B;AAAA,IACF,CAAC;AAED,QAAI,CAAC,SAAS,aAAa;AACzB,cAAQ,IAAI;AACZ,cAAQ,IAAI,WAAW;AACvB,cAAQ,KAAK,CAAC;AAAA,IAChB;AAEA,kBAAc,SAAS;AAAA,EACzB;AAGA,QAAM,aAAa,oBAAoB,WAAY;AACnD,MAAI,CAAC,WAAW,OAAO;AACrB,YAAQ;AAAA,MACND,IAAG;AAAA,QACD,iCAAiCA,IAAG;AAAA,UAClC,IAAI,WAAW;AAAA,QACjB,CAAC;AAAA;AAAA,MACH;AAAA,IACF;AACA,eAAW,SAAU;AAAA,MAAQ,CAAC,MAC5B,QAAQ,MAAM,KAAKA,IAAG,IAAI,QAAG,CAAC,IAAI,CAAC,EAAE;AAAA,IACvC;AACA,YAAQ,KAAK,CAAC;AAAA,EAChB;AAGA,MAAI,WAAW,QAAQ;AAEvB,MAAI,CAAC,UAAU;AACb,UAAM,WAAW,MAAM,QAAQ;AAAA,MAC7B,MAAM;AAAA,MACN,MAAM;AAAA,MACN,SAAS;AAAA,MACT,SAAS;AAAA,QACP;AAAA,UACE,OAAO;AAAA,UACP,OAAO;AAAA,UACP,aAAa;AAAA,QACf;AAAA,QACA;AAAA,UACE,OAAO;AAAA,UACP,OAAO;AAAA,UACP,aAAa;AAAA,QACf;AAAA,QACA;AAAA,UACE,OAAO;AAAA,UACP,OAAO;AAAA,UACP,aAAa;AAAA,QACf;AAAA,MACF;AAAA,MACA,SAAS;AAAA,IACX,CAAC;AAED,QAAI,CAAC,SAAS,UAAU;AACtB,cAAQ,IAAI;AACZ,cAAQ,IAAI,WAAW;AACvB,cAAQ,KAAK,CAAC;AAAA,IAChB;AAEA,eAAW,SAAS;AAAA,EACtB;AAGA,MAAI,iBAAiB,QAAQ,SACzB,QACA,QAAQ,UACR,SACA,QAAQ,UACR,SACA;AAEJ,MAAI,CAAC,kBAAkB,CAAC,QAAQ,aAAa;AAC3C,UAAM,WAAW,MAAM,QAAQ;AAAA,MAC7B,MAAM;AAAA,MACN,MAAM;AAAA,MACN,SAAS;AAAA,MACT,SAAS;AAAA,QACP,EAAE,OAAO,OAAO,OAAO,MAAM;AAAA,QAC7B,EAAE,OAAO,QAAQ,OAAO,OAAO;AAAA,QAC/B,EAAE,OAAO,QAAQ,OAAO,OAAO;AAAA,MACjC;AAAA,MACA,SAAS;AAAA,IACX,CAAC;AAED,QAAI,CAAC,SAAS,gBAAgB;AAC5B,cAAQ,IAAI;AACZ,cAAQ,IAAI,WAAW;AACvB,cAAQ,KAAK,CAAC;AAAA,IAChB;AAEA,qBAAiB,SAAS;AAAA,EAC5B;AAGA,MAAI;AACF,UAAM,cAAc;AAAA,MAClB;AAAA,MACA,UAAU,YAAY;AAAA,MACtB,gBAAgB,kBAAkB;AAAA,MAClC,aAAa,QAAQ;AAAA,IACvB,CAAC;AAAA,EACH,SAAS,OAAO;AACd,YAAQ,MAAMA,IAAG,IAAI,2BAA2B,CAAC;AACjD,YAAQ,MAAM,KAAK;AACnB,YAAQ,KAAK,CAAC;AAAA,EAChB;AACF,CAAC;AAEH,QAAQ,MAAM;","names":["fs","path","execSync","path","path","fs","__filename","__dirname","command","execSync","pc","validation"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/create-project.ts","../src/utils.ts"],"sourcesContent":["import { Command } from 'commander'\nimport prompts from 'prompts'\nimport path from 'path'\nimport { createProject } from './create-project.js'\nimport { validateProjectName } from './utils.js'\nimport pc from 'picocolors'\nimport { createRequire } from 'module'\n\nconst require = createRequire(import.meta.url)\nconst { version } = require('../package.json')\n\nconst program = new Command()\n\nprogram\n .name('create-specra')\n .description('Specra CLI - Create, deploy, and manage your documentation sites')\n .version(version)\n\n// init command - create a new project (default when no subcommand given)\nconst initCmd = program\n .command('init [project-directory]', { isDefault: true })\n .description('Create a new Specra documentation site')\n .option('--template <template>', 'Template to use (minimal)')\n .option('--use-npm', 'Use npm as the package manager')\n .option('--use-pnpm', 'Use pnpm as the package manager')\n .option('--use-yarn', 'Use yarn as the package manager')\n .option('--skip-install', 'Skip package installation')\n .action(async (projectDirectory: string | undefined, options) => {\n console.log()\n console.log(pc.bold(pc.cyan('Create Specra Documentation Site')))\n console.log()\n\n let projectName = projectDirectory\n\n // Prompt for project name if not provided\n if (!projectName) {\n const response = await prompts({\n type: 'text',\n name: 'projectName',\n message: 'What is your project named?',\n initial: 'my-docs',\n validate: (name) => {\n const validation = validateProjectName(name)\n if (validation.valid) {\n return true\n }\n return validation.problems![0]\n },\n })\n\n if (!response.projectName) {\n console.log()\n console.log('Aborting.')\n process.exit(1)\n }\n\n projectName = response.projectName\n }\n\n // Validate the basename as a package name (allow full paths like /tmp/my-docs)\n const projectBaseName = path.basename(path.resolve(projectName!))\n const validation = validateProjectName(projectBaseName)\n if (!validation.valid) {\n console.error(\n pc.red(\n `Cannot create a project named ${pc.cyan(\n `\"${projectBaseName}\"`\n )} because of npm naming restrictions:\\n`\n )\n )\n validation.problems!.forEach((p) =>\n console.error(` ${pc.red('•')} ${p}`)\n )\n process.exit(1)\n }\n\n // Prompt for template if not provided\n let template = options.template\n\n if (!template) {\n const response = await prompts({\n type: 'select',\n name: 'template',\n message: 'Which template would you like to use?',\n choices: [\n {\n title: 'Minimal',\n value: 'minimal',\n description: 'Minimal setup to get started quickly',\n },\n {\n title: 'Book Docs',\n value: 'book-docs',\n description: 'Knowledge base style with dark theme and categorized sidebar',\n },\n {\n title: 'JBrains Docs',\n value: 'jbrains-docs',\n description: 'Reference docs style with light theme and tab groups',\n },\n ],\n initial: 0,\n })\n\n if (!response.template) {\n console.log()\n console.log('Aborting.')\n process.exit(1)\n }\n\n template = response.template\n }\n\n // Detect or prompt for package manager\n let packageManager = options.useNpm\n ? 'npm'\n : options.usePnpm\n ? 'pnpm'\n : options.useYarn\n ? 'yarn'\n : undefined\n\n if (!packageManager && !options.skipInstall) {\n const response = await prompts({\n type: 'select',\n name: 'packageManager',\n message: 'Which package manager do you want to use?',\n choices: [\n { title: 'npm', value: 'npm' },\n { title: 'yarn', value: 'yarn' },\n { title: 'pnpm', value: 'pnpm' },\n ],\n initial: 0,\n })\n\n if (!response.packageManager) {\n console.log()\n console.log('Aborting.')\n process.exit(1)\n }\n\n packageManager = response.packageManager\n }\n\n // Create the project\n try {\n await createProject({\n projectName: projectName!,\n template: template || 'minimal',\n packageManager: packageManager || 'npm',\n skipInstall: options.skipInstall,\n })\n } catch (error) {\n console.error(pc.red('\\nError creating project:'))\n console.error(error)\n process.exit(1)\n }\n })\n\n// login\nprogram\n .command('login')\n .description('Authenticate with your Specra account')\n .option('-g, --global', 'Store credentials in ~/.specra/ instead of local specra.config.json')\n .action(async (options) => {\n const { login } = await import('./commands/login.js')\n await login(options)\n })\n\n// logout\nprogram\n .command('logout')\n .description('Clear stored credentials')\n .option('-g, --global', 'Clear credentials from ~/.specra/ instead of local specra.config.json')\n .action(async (options) => {\n const { logout } = await import('./commands/logout.js')\n await logout(options)\n })\n\n// deploy\nprogram\n .command('deploy')\n .description('Deploy your docs project')\n .option('-p, --project <id>', 'Project ID to deploy to')\n .option('-d, --dir <directory>', 'Docs directory to deploy', '.')\n .action(async (options) => {\n const { deploy } = await import('./commands/deploy.js')\n await deploy(options)\n })\n\n// projects\nprogram\n .command('projects')\n .description('List your projects')\n .action(async () => {\n const { projects } = await import('./commands/projects.js')\n await projects()\n })\n\n// logs\nprogram\n .command('logs')\n .description('View deployment logs')\n .argument('<projectId>', 'Project ID')\n .option('--deployment <id>', 'Specific deployment ID')\n .action(async (projectId: string, options) => {\n const { logs } = await import('./commands/logs.js')\n await logs(projectId, options)\n })\n\n// doctor\nprogram\n .command('doctor')\n .description('Check specra.config.json for issues')\n .option('-d, --dir <directory>', 'Project directory to check', '.')\n .action(async (options) => {\n const { doctor } = await import('./commands/doctor.js')\n await doctor(options)\n })\n\nprogram.parse()\n\n// Handle unhandled rejections\nprocess.on('unhandledRejection', (err) => {\n console.error(pc.red('Error:'), err)\n process.exit(1)\n})\n","import fs from 'fs'\nimport path from 'path'\nimport { fileURLToPath } from 'url'\nimport { execSync } from 'child_process'\nimport pc from 'picocolors'\nimport {\n isWriteable,\n isFolderEmpty,\n getPackageManagerCommand,\n tryGitInit,\n copyRecursive,\n} from './utils.js'\n\ninterface CreateProjectOptions {\n projectName: string\n template: string\n packageManager: string\n skipInstall: boolean\n}\n\nexport async function createProject({\n projectName,\n template,\n packageManager,\n skipInstall,\n}: CreateProjectOptions) {\n const root = path.resolve(projectName)\n const appName = path.basename(root)\n\n // Check if directory exists\n if (fs.existsSync(root)) {\n if (!isWriteable(root)) {\n console.error(\n pc.red(`The directory ${pc.cyan(projectName)} is not writable.`)\n )\n process.exit(1)\n }\n\n if (!isFolderEmpty(root)) {\n console.error(\n pc.red(\n `The directory ${pc.cyan(\n projectName\n )} contains files that could conflict. Please use a new directory.`\n )\n )\n process.exit(1)\n }\n } else {\n fs.mkdirSync(root, { recursive: true })\n }\n\n console.log(`Creating a new Specra documentation site (SvelteKit) in ${pc.cyan(root)}`)\n console.log()\n\n // Create project structure based on template\n const __filename = fileURLToPath(import.meta.url)\n const __dirname = path.dirname(__filename)\n const templateDir = path.join(__dirname, '..', 'templates', template)\n\n if (!fs.existsSync(templateDir)) {\n console.error(pc.red(`Template ${pc.cyan(template)} not found.`))\n process.exit(1)\n }\n\n console.log(`Using template: ${pc.cyan(template)}`)\n console.log()\n\n // Copy template files\n copyRecursive(templateDir, root)\n\n // Update package.json with project name\n const packageJsonPath = path.join(root, 'package.json')\n const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'))\n packageJson.name = appName\n fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\\n')\n\n // Rename gitignore\n const gitignorePath = path.join(root, 'gitignore')\n if (fs.existsSync(gitignorePath)) {\n fs.renameSync(gitignorePath, path.join(root, '.gitignore'))\n }\n\n // Install dependencies\n if (!skipInstall) {\n console.log('Installing dependencies...')\n console.log()\n\n const command = getPackageManagerCommand(packageManager)\n\n try {\n execSync(command.install, { cwd: root, stdio: 'inherit' })\n } catch (error) {\n console.error(pc.red('Failed to install dependencies.'))\n process.exit(1)\n }\n\n console.log()\n }\n\n // Initialize git\n if (tryGitInit(root)) {\n console.log('Initialized a git repository.')\n console.log()\n }\n\n // Success message\n console.log(pc.green('Success!') + ` Created ${appName} at ${root}`)\n console.log()\n console.log('Inside that directory, you can run several commands:')\n console.log()\n\n const command = getPackageManagerCommand(packageManager)\n\n console.log(pc.cyan(` ${command.run('dev')}`))\n console.log(' Starts the development server.')\n console.log()\n console.log(pc.cyan(` ${command.run('build')}`))\n console.log(' Builds the app for production.')\n console.log()\n console.log(pc.cyan(` ${command.run('preview')}`))\n console.log(' Previews the built app locally.')\n console.log()\n console.log('We suggest that you begin by typing:')\n console.log()\n console.log(pc.cyan(' cd'), projectName)\n console.log(` ${pc.cyan(command.run('dev'))}`)\n console.log()\n}\n","import validateNpmPackageName from 'validate-npm-package-name'\nimport { execSync } from 'child_process'\nimport fs from 'fs'\nimport path from 'path'\n\nexport function validateProjectName(name: string) {\n const validation = validateNpmPackageName(name)\n\n if (validation.validForNewPackages) {\n return { valid: true }\n }\n\n return {\n valid: false,\n problems: [\n ...(validation.errors || []),\n ...(validation.warnings || []),\n ],\n }\n}\n\nexport function isWriteable(directory: string): boolean {\n try {\n fs.accessSync(directory, fs.constants.W_OK)\n return true\n } catch {\n return false\n }\n}\n\nexport function isFolderEmpty(path: string): boolean {\n const files = fs.readdirSync(path)\n return files.length === 0 || (files.length === 1 && files[0] === '.git')\n}\n\nexport function getPackageManagerCommand(packageManager: string): {\n install: string\n run: (script: string) => string\n} {\n switch (packageManager) {\n case 'yarn':\n return {\n install: 'yarn install',\n run: (script) => `yarn ${script}`,\n }\n case 'pnpm':\n return {\n install: 'pnpm install',\n run: (script) => `pnpm ${script}`,\n }\n case 'npm':\n default:\n return {\n install: 'npm install',\n run: (script) => `npm run ${script}`,\n }\n }\n}\n\nexport function tryGitInit(root: string): boolean {\n try {\n execSync('git --version', { stdio: 'ignore' })\n execSync('git init', { cwd: root, stdio: 'ignore' })\n execSync('git add -A', { cwd: root, stdio: 'ignore' })\n execSync('git commit -m \"Initial commit from create-specra\"', {\n cwd: root,\n stdio: 'ignore',\n })\n return true\n } catch {\n return false\n }\n}\n\nexport function copyRecursive(src: string, dest: string) {\n const stat = fs.statSync(src)\n\n if (stat.isDirectory()) {\n fs.mkdirSync(dest, { recursive: true })\n const entries = fs.readdirSync(src)\n\n for (const entry of entries) {\n const srcPath = path.join(src, entry)\n const destPath = path.join(dest, entry)\n copyRecursive(srcPath, destPath)\n }\n } else {\n fs.copyFileSync(src, dest)\n }\n}\n"],"mappings":"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","names":["path","fs","path","execSync","path","path","fs","__filename","__dirname","command","execSync","pc","require","validation","path"]}
@@ -1,20 +1,23 @@
1
1
  #!/usr/bin/env node
2
- import {
3
- apiRequest
4
- } from "./chunk-MA7QG54W.js";
5
2
  import {
6
3
  getConfig,
7
- saveConfig
8
- } from "./chunk-3DKWECRK.js";
4
+ saveToken
5
+ } from "./chunk-ATRUBLRX.js";
9
6
 
10
7
  // src/commands/login.ts
11
8
  import pc from "picocolors";
12
9
  import open from "open";
13
10
  import { createServer } from "http";
14
11
  import { randomBytes } from "crypto";
15
- async function login() {
12
+ async function login(options = {}) {
16
13
  console.log(pc.bold("Specra Login"));
17
14
  console.log();
15
+ if (options.global) {
16
+ console.log(pc.dim("Storing credentials globally in ~/.specra/"));
17
+ } else {
18
+ console.log(pc.dim("Storing credentials in local specra.config.json"));
19
+ }
20
+ console.log();
18
21
  const state = randomBytes(16).toString("hex");
19
22
  const port = 9876;
20
23
  const { apiUrl } = getConfig();
@@ -32,12 +35,24 @@ async function login() {
32
35
  return;
33
36
  }
34
37
  if (token) {
35
- saveConfig({ token });
36
38
  try {
39
+ saveToken(token, { global: options.global });
40
+ console.log(pc.green("Token saved."));
41
+ } catch (err) {
42
+ if (!options.global) {
43
+ console.log(pc.yellow(`Could not save to local config: ${err instanceof Error ? err.message : err}`));
44
+ console.log(pc.yellow("Falling back to global ~/.specra/ config"));
45
+ saveToken(token, { global: true });
46
+ console.log(pc.green("Token saved globally."));
47
+ } else {
48
+ throw err;
49
+ }
50
+ }
51
+ try {
52
+ const { apiRequest } = await import("./api-client-4XZPF7GL.js");
37
53
  const user = await apiRequest("/api/auth/verify");
38
54
  console.log(pc.green(`Authenticated as ${user.email}`));
39
55
  } catch {
40
- console.log(pc.green("Token saved."));
41
56
  }
42
57
  res.writeHead(200, { "Content-Type": "text/html" });
43
58
  res.end(
@@ -68,4 +83,4 @@ async function login() {
68
83
  export {
69
84
  login
70
85
  };
71
- //# sourceMappingURL=login-NKDRQXRE.js.map
86
+ //# sourceMappingURL=login-DRPP77G4.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/login.ts"],"sourcesContent":["import pc from 'picocolors'\nimport open from 'open'\nimport { createServer } from 'http'\nimport { saveToken, getGlobalConfig } from '../config.js'\nimport { getConfig, saveConfig } from '../config.js'\nimport { apiRequest } from '../api-client.js'\nimport { randomBytes } from 'crypto'\n\ninterface LoginOptions {\n global?: boolean\n}\n\nexport async function login(options: LoginOptions = {}) {\n console.log(pc.bold('Specra Login'))\n console.log()\n\n if (options.global) {\n console.log(pc.dim('Storing credentials globally in ~/.specra/'))\n } else {\n console.log(pc.dim('Storing credentials in local specra.config.json'))\n }\n console.log()\n\n const state = randomBytes(16).toString('hex')\n const port = 9876\n // const apiUrl = getGlobalConfig().apiUrl\n const { apiUrl } = getConfig()\n\n return new Promise<void>((resolve) => {\n const server = createServer(async (req, res) => {\n const url = new URL(req.url!, `http://localhost:${port}`)\n\n // Backend redirects to root path with token as query param\n const token = url.searchParams.get('token')\n const returnedState = url.searchParams.get('state')\n\n if (token || returnedState) {\n if (returnedState !== state) {\n res.writeHead(400, { 'Content-Type': 'text/html' })\n res.end('<html><body><h1>State mismatch. Please try again.</h1></body></html>')\n server.close()\n resolve()\n return\n }\n\n if (token) {\n try {\n saveToken(token, { global: options.global })\n console.log(pc.green('Token saved.'))\n } catch (err) {\n if (!options.global) {\n console.log(pc.yellow(`Could not save to local config: ${err instanceof Error ? err.message : err}`))\n console.log(pc.yellow('Falling back to global ~/.specra/ config'))\n saveToken(token, { global: true })\n console.log(pc.green('Token saved globally.'))\n } else {\n throw err\n }\n }\n\n // Verify the token works\n try {\n const { apiRequest } = await import('../api-client.js')\n const user = await apiRequest<{ email: string }>('/api/auth/verify')\n console.log(pc.green(`Authenticated as ${user.email}`))\n } catch {\n // verification is optional\n }\n\n res.writeHead(200, { 'Content-Type': 'text/html' })\n res.end(\n '<html><body><h1>Authenticated!</h1><p>You can close this window and return to the terminal.</p></body></html>'\n )\n } else {\n res.writeHead(400, { 'Content-Type': 'text/html' })\n res.end('<html><body><h1>Authentication failed.</h1></body></html>')\n }\n\n server.close()\n resolve()\n }\n })\n\n server.listen(port, () => {\n const loginUrl = `${apiUrl}/auth/cli?port=${port}&state=${state}`\n console.log(`Opening browser to authenticate...`)\n console.log(pc.dim(`If the browser doesn't open, visit: ${loginUrl}`))\n console.log()\n open(loginUrl)\n })\n\n // Timeout after 5 minutes\n setTimeout(() => {\n console.log(pc.yellow('Login timed out.'))\n server.close()\n resolve()\n }, 300000)\n })\n}\n"],"mappings":";;;;;;;AAAA,OAAO,QAAQ;AACf,OAAO,UAAU;AACjB,SAAS,oBAAoB;AAI7B,SAAS,mBAAmB;AAM5B,eAAsB,MAAM,UAAwB,CAAC,GAAG;AACtD,UAAQ,IAAI,GAAG,KAAK,cAAc,CAAC;AACnC,UAAQ,IAAI;AAEZ,MAAI,QAAQ,QAAQ;AAClB,YAAQ,IAAI,GAAG,IAAI,4CAA4C,CAAC;AAAA,EAClE,OAAO;AACL,YAAQ,IAAI,GAAG,IAAI,iDAAiD,CAAC;AAAA,EACvE;AACA,UAAQ,IAAI;AAEZ,QAAM,QAAQ,YAAY,EAAE,EAAE,SAAS,KAAK;AAC5C,QAAM,OAAO;AAEb,QAAM,EAAE,OAAO,IAAI,UAAU;AAE7B,SAAO,IAAI,QAAc,CAAC,YAAY;AACpC,UAAM,SAAS,aAAa,OAAO,KAAK,QAAQ;AAC9C,YAAM,MAAM,IAAI,IAAI,IAAI,KAAM,oBAAoB,IAAI,EAAE;AAGxD,YAAM,QAAQ,IAAI,aAAa,IAAI,OAAO;AAC1C,YAAM,gBAAgB,IAAI,aAAa,IAAI,OAAO;AAElD,UAAI,SAAS,eAAe;AAC1B,YAAI,kBAAkB,OAAO;AAC3B,cAAI,UAAU,KAAK,EAAE,gBAAgB,YAAY,CAAC;AAClD,cAAI,IAAI,sEAAsE;AAC9E,iBAAO,MAAM;AACb,kBAAQ;AACR;AAAA,QACF;AAEA,YAAI,OAAO;AACT,cAAI;AACF,sBAAU,OAAO,EAAE,QAAQ,QAAQ,OAAO,CAAC;AAC3C,oBAAQ,IAAI,GAAG,MAAM,cAAc,CAAC;AAAA,UACtC,SAAS,KAAK;AACZ,gBAAI,CAAC,QAAQ,QAAQ;AACnB,sBAAQ,IAAI,GAAG,OAAO,mCAAmC,eAAe,QAAQ,IAAI,UAAU,GAAG,EAAE,CAAC;AACpG,sBAAQ,IAAI,GAAG,OAAO,0CAA0C,CAAC;AACjE,wBAAU,OAAO,EAAE,QAAQ,KAAK,CAAC;AACjC,sBAAQ,IAAI,GAAG,MAAM,uBAAuB,CAAC;AAAA,YAC/C,OAAO;AACL,oBAAM;AAAA,YACR;AAAA,UACF;AAGA,cAAI;AACF,kBAAM,EAAE,WAAW,IAAI,MAAM,OAAO,0BAAkB;AACtD,kBAAM,OAAO,MAAM,WAA8B,kBAAkB;AACnE,oBAAQ,IAAI,GAAG,MAAM,oBAAoB,KAAK,KAAK,EAAE,CAAC;AAAA,UACxD,QAAQ;AAAA,UAER;AAEA,cAAI,UAAU,KAAK,EAAE,gBAAgB,YAAY,CAAC;AAClD,cAAI;AAAA,YACF;AAAA,UACF;AAAA,QACF,OAAO;AACL,cAAI,UAAU,KAAK,EAAE,gBAAgB,YAAY,CAAC;AAClD,cAAI,IAAI,2DAA2D;AAAA,QACrE;AAEA,eAAO,MAAM;AACb,gBAAQ;AAAA,MACV;AAAA,IACF,CAAC;AAED,WAAO,OAAO,MAAM,MAAM;AACxB,YAAM,WAAW,GAAG,MAAM,kBAAkB,IAAI,UAAU,KAAK;AAC/D,cAAQ,IAAI,oCAAoC;AAChD,cAAQ,IAAI,GAAG,IAAI,uCAAuC,QAAQ,EAAE,CAAC;AACrE,cAAQ,IAAI;AACZ,WAAK,QAAQ;AAAA,IACf,CAAC;AAGD,eAAW,MAAM;AACf,cAAQ,IAAI,GAAG,OAAO,kBAAkB,CAAC;AACzC,aAAO,MAAM;AACb,cAAQ;AAAA,IACV,GAAG,GAAM;AAAA,EACX,CAAC;AACH;","names":[]}
@@ -0,0 +1,24 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ clearToken,
4
+ isAuthenticated
5
+ } from "./chunk-ATRUBLRX.js";
6
+
7
+ // src/commands/logout.ts
8
+ import pc from "picocolors";
9
+ async function logout(options = {}) {
10
+ if (!isAuthenticated()) {
11
+ console.log(pc.yellow("Not currently logged in."));
12
+ return;
13
+ }
14
+ clearToken({ global: options.global });
15
+ if (options.global) {
16
+ console.log(pc.green("Logged out globally (cleared ~/.specra/ credentials)."));
17
+ } else {
18
+ console.log(pc.green("Logged out (cleared local project credentials)."));
19
+ }
20
+ }
21
+ export {
22
+ logout
23
+ };
24
+ //# sourceMappingURL=logout-UJFYUAQC.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/logout.ts"],"sourcesContent":["import pc from 'picocolors'\nimport { clearToken, isAuthenticated } from '../config.js'\n\ninterface LogoutOptions {\n global?: boolean\n}\n\nexport async function logout(options: LogoutOptions = {}) {\n if (!isAuthenticated()) {\n console.log(pc.yellow('Not currently logged in.'))\n return\n }\n\n clearToken({ global: options.global })\n\n if (options.global) {\n console.log(pc.green('Logged out globally (cleared ~/.specra/ credentials).'))\n } else {\n console.log(pc.green('Logged out (cleared local project credentials).'))\n }\n}\n"],"mappings":";;;;;;;AAAA,OAAO,QAAQ;AAOf,eAAsB,OAAO,UAAyB,CAAC,GAAG;AACxD,MAAI,CAAC,gBAAgB,GAAG;AACtB,YAAQ,IAAI,GAAG,OAAO,0BAA0B,CAAC;AACjD;AAAA,EACF;AAEA,aAAW,EAAE,QAAQ,QAAQ,OAAO,CAAC;AAErC,MAAI,QAAQ,QAAQ;AAClB,YAAQ,IAAI,GAAG,MAAM,uDAAuD,CAAC;AAAA,EAC/E,OAAO;AACL,YAAQ,IAAI,GAAG,MAAM,iDAAiD,CAAC;AAAA,EACzE;AACF;","names":[]}