specra-cli 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.MD +33 -0
- package/README.md +246 -0
- package/dist/api-client-VHQARPDT.js +15 -0
- package/dist/api-client-VHQARPDT.js.map +1 -0
- package/dist/chunk-5765WX4D.js +192 -0
- package/dist/chunk-5765WX4D.js.map +1 -0
- package/dist/chunk-72RDEJR2.js +94 -0
- package/dist/chunk-72RDEJR2.js.map +1 -0
- package/dist/chunk-SQ2MMFUZ.js +102 -0
- package/dist/chunk-SQ2MMFUZ.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +242 -0
- package/dist/cli.js.map +1 -0
- package/dist/deploy-V4JO2D6B.js +179 -0
- package/dist/deploy-V4JO2D6B.js.map +1 -0
- package/dist/doctor-ICALAJ4N.js +309 -0
- package/dist/doctor-ICALAJ4N.js.map +1 -0
- package/dist/login-UG3WU7DY.js +92 -0
- package/dist/login-UG3WU7DY.js.map +1 -0
- package/dist/logout-WJKHJZT6.js +24 -0
- package/dist/logout-WJKHJZT6.js.map +1 -0
- package/dist/logs-BLUJPWNO.js +77 -0
- package/dist/logs-BLUJPWNO.js.map +1 -0
- package/dist/projects-LJ57GK3D.js +49 -0
- package/dist/projects-LJ57GK3D.js.map +1 -0
- package/package.json +50 -0
- package/templates/book-docs/.env.sample +1 -0
- package/templates/book-docs/docs/v1.0.0/concepts.mdx +89 -0
- package/templates/book-docs/docs/v1.0.0/content/_category_.json +7 -0
- package/templates/book-docs/docs/v1.0.0/content/formatting.mdx +128 -0
- package/templates/book-docs/docs/v1.0.0/content/reusable-content.mdx +116 -0
- package/templates/book-docs/docs/v1.0.0/content/structure.mdx +92 -0
- package/templates/book-docs/docs/v1.0.0/customization/_category_.json +7 -0
- package/templates/book-docs/docs/v1.0.0/customization/branding.mdx +115 -0
- package/templates/book-docs/docs/v1.0.0/customization/themes.mdx +81 -0
- package/templates/book-docs/docs/v1.0.0/introduction.mdx +38 -0
- package/templates/book-docs/docs/v1.0.0/quickstart.mdx +112 -0
- package/templates/book-docs/docs/v2.0.0/concepts.mdx +89 -0
- package/templates/book-docs/docs/v2.0.0/content/_category_.json +7 -0
- package/templates/book-docs/docs/v2.0.0/content/formatting.mdx +128 -0
- package/templates/book-docs/docs/v2.0.0/content/reusable-content.mdx +116 -0
- package/templates/book-docs/docs/v2.0.0/content/structure.mdx +92 -0
- package/templates/book-docs/docs/v2.0.0/customization/_category_.json +7 -0
- package/templates/book-docs/docs/v2.0.0/customization/branding.mdx +115 -0
- package/templates/book-docs/docs/v2.0.0/customization/themes.mdx +81 -0
- package/templates/book-docs/docs/v2.0.0/introduction.mdx +39 -0
- package/templates/book-docs/docs/v2.0.0/quickstart.mdx +112 -0
- package/templates/book-docs/gitignore +7 -0
- package/templates/book-docs/package.json +28 -0
- package/templates/book-docs/postcss.config.mjs +8 -0
- package/templates/book-docs/public/api-specs/openapi-example.json +259 -0
- package/templates/book-docs/public/api-specs/postman-example.json +205 -0
- package/templates/book-docs/public/api-specs/test-api.json +256 -0
- package/templates/book-docs/public/api-specs/users-api.json +264 -0
- package/templates/book-docs/specra.config.json +77 -0
- package/templates/book-docs/src/app.css +86 -0
- package/templates/book-docs/src/app.html +17 -0
- package/templates/book-docs/src/params/product.ts +7 -0
- package/templates/book-docs/src/routes/+layout.server.ts +14 -0
- package/templates/book-docs/src/routes/+layout.svelte +21 -0
- package/templates/book-docs/src/routes/+page.server.ts +9 -0
- package/templates/book-docs/src/routes/docs/[product=product]/[version]/+layout.server.ts +40 -0
- package/templates/book-docs/src/routes/docs/[product=product]/[version]/+page.server.ts +24 -0
- package/templates/book-docs/src/routes/docs/[product=product]/[version]/[...slug]/+page.server.ts +131 -0
- package/templates/book-docs/src/routes/docs/[product=product]/[version]/[...slug]/+page.svelte +180 -0
- package/templates/book-docs/src/routes/docs/[version]/+layout.server.ts +42 -0
- package/templates/book-docs/src/routes/docs/[version]/+page.server.ts +27 -0
- package/templates/book-docs/src/routes/docs/[version]/[...slug]/+page.server.ts +106 -0
- package/templates/book-docs/src/routes/docs/[version]/[...slug]/+page.svelte +172 -0
- package/templates/book-docs/static/favicon.svg +4 -0
- package/templates/book-docs/svelte.config.js +13 -0
- package/templates/book-docs/tsconfig.json +12 -0
- package/templates/book-docs/vite.config.ts +6 -0
- package/templates/jbrains-docs/.env.sample +1 -0
- package/templates/jbrains-docs/docs/v1.0.0/advanced/_category_.json +8 -0
- package/templates/jbrains-docs/docs/v1.0.0/advanced/async.mdx +95 -0
- package/templates/jbrains-docs/docs/v1.0.0/advanced/generics.mdx +126 -0
- package/templates/jbrains-docs/docs/v1.0.0/basics/_category_.json +8 -0
- package/templates/jbrains-docs/docs/v1.0.0/basics/control-flow.mdx +106 -0
- package/templates/jbrains-docs/docs/v1.0.0/basics/syntax.mdx +129 -0
- package/templates/jbrains-docs/docs/v1.0.0/basics/types.mdx +135 -0
- package/templates/jbrains-docs/docs/v1.0.0/getting-started.mdx +111 -0
- package/templates/jbrains-docs/docs/v1.0.0/home.mdx +37 -0
- package/templates/jbrains-docs/docs/v1.0.0/tools/_category_.json +8 -0
- package/templates/jbrains-docs/docs/v1.0.0/tools/build-tools.mdx +165 -0
- package/templates/jbrains-docs/docs/v1.0.0/tools/testing.mdx +112 -0
- package/templates/jbrains-docs/docs/v2.0.0/advanced/_category_.json +8 -0
- package/templates/jbrains-docs/docs/v2.0.0/advanced/async.mdx +95 -0
- package/templates/jbrains-docs/docs/v2.0.0/advanced/generics.mdx +126 -0
- package/templates/jbrains-docs/docs/v2.0.0/basics/_category_.json +8 -0
- package/templates/jbrains-docs/docs/v2.0.0/basics/control-flow.mdx +106 -0
- package/templates/jbrains-docs/docs/v2.0.0/basics/syntax.mdx +129 -0
- package/templates/jbrains-docs/docs/v2.0.0/basics/types.mdx +135 -0
- package/templates/jbrains-docs/docs/v2.0.0/getting-started.mdx +111 -0
- package/templates/jbrains-docs/docs/v2.0.0/home.mdx +37 -0
- package/templates/jbrains-docs/docs/v2.0.0/tools/_category_.json +8 -0
- package/templates/jbrains-docs/docs/v2.0.0/tools/build-tools.mdx +165 -0
- package/templates/jbrains-docs/docs/v2.0.0/tools/testing.mdx +112 -0
- package/templates/jbrains-docs/gitignore +7 -0
- package/templates/jbrains-docs/package.json +28 -0
- package/templates/jbrains-docs/postcss.config.mjs +8 -0
- package/templates/jbrains-docs/public/api-specs/openapi-example.json +259 -0
- package/templates/jbrains-docs/public/api-specs/postman-example.json +205 -0
- package/templates/jbrains-docs/public/api-specs/test-api.json +256 -0
- package/templates/jbrains-docs/public/api-specs/users-api.json +264 -0
- package/templates/jbrains-docs/specra.config.json +80 -0
- package/templates/jbrains-docs/src/app.css +86 -0
- package/templates/jbrains-docs/src/app.html +17 -0
- package/templates/jbrains-docs/src/params/product.ts +7 -0
- package/templates/jbrains-docs/src/routes/+layout.server.ts +14 -0
- package/templates/jbrains-docs/src/routes/+layout.svelte +21 -0
- package/templates/jbrains-docs/src/routes/+page.server.ts +9 -0
- package/templates/jbrains-docs/src/routes/docs/[product=product]/[version]/+layout.server.ts +40 -0
- package/templates/jbrains-docs/src/routes/docs/[product=product]/[version]/+page.server.ts +24 -0
- package/templates/jbrains-docs/src/routes/docs/[product=product]/[version]/[...slug]/+page.server.ts +131 -0
- package/templates/jbrains-docs/src/routes/docs/[product=product]/[version]/[...slug]/+page.svelte +180 -0
- package/templates/jbrains-docs/src/routes/docs/[version]/+layout.server.ts +42 -0
- package/templates/jbrains-docs/src/routes/docs/[version]/+page.server.ts +27 -0
- package/templates/jbrains-docs/src/routes/docs/[version]/[...slug]/+page.server.ts +106 -0
- package/templates/jbrains-docs/src/routes/docs/[version]/[...slug]/+page.svelte +172 -0
- package/templates/jbrains-docs/static/favicon.svg +4 -0
- package/templates/jbrains-docs/svelte.config.js +13 -0
- package/templates/jbrains-docs/tsconfig.json +12 -0
- package/templates/jbrains-docs/vite.config.ts +6 -0
- package/templates/minimal/.env.sample +1 -0
- package/templates/minimal/docs/v1.0.0/about.mdx +57 -0
- package/templates/minimal/docs/v1.0.0/components/_category_.json +8 -0
- package/templates/minimal/docs/v1.0.0/components/callout.mdx +83 -0
- package/templates/minimal/docs/v1.0.0/components/code-block.mdx +103 -0
- package/templates/minimal/docs/v1.0.0/components/index.mdx +8 -0
- package/templates/minimal/docs/v1.0.0/components/tabs.mdx +92 -0
- package/templates/minimal/docs/v1.0.0/configuration.mdx +322 -0
- package/templates/minimal/docs/v1.0.0/features.mdx +197 -0
- package/templates/minimal/docs/v1.0.0/getting-started.mdx +183 -0
- package/templates/minimal/docs/v2.0.0/about.mdx +57 -0
- package/templates/minimal/docs/v2.0.0/components/_category_.json +8 -0
- package/templates/minimal/docs/v2.0.0/components/callout.mdx +83 -0
- package/templates/minimal/docs/v2.0.0/components/code-block.mdx +103 -0
- package/templates/minimal/docs/v2.0.0/components/index.mdx +8 -0
- package/templates/minimal/docs/v2.0.0/components/tabs.mdx +92 -0
- package/templates/minimal/docs/v2.0.0/configuration.mdx +322 -0
- package/templates/minimal/docs/v2.0.0/features.mdx +197 -0
- package/templates/minimal/docs/v2.0.0/getting-started.mdx +183 -0
- package/templates/minimal/gitignore +7 -0
- package/templates/minimal/package.json +29 -0
- package/templates/minimal/postcss.config.mjs +8 -0
- package/templates/minimal/specra.config.json +91 -0
- package/templates/minimal/src/app.css +86 -0
- package/templates/minimal/src/app.html +17 -0
- package/templates/minimal/src/hooks.server.ts +8 -0
- package/templates/minimal/src/params/product.ts +7 -0
- package/templates/minimal/src/routes/+error.svelte +10 -0
- package/templates/minimal/src/routes/+layout.server.ts +14 -0
- package/templates/minimal/src/routes/+layout.svelte +21 -0
- package/templates/minimal/src/routes/+page.server.ts +9 -0
- package/templates/minimal/src/routes/+page.svelte +149 -0
- package/templates/minimal/src/routes/docs/[product=product]/[version]/+layout.server.ts +40 -0
- package/templates/minimal/src/routes/docs/[product=product]/[version]/+page.server.ts +24 -0
- package/templates/minimal/src/routes/docs/[product=product]/[version]/[...slug]/+page.server.ts +131 -0
- package/templates/minimal/src/routes/docs/[product=product]/[version]/[...slug]/+page.svelte +180 -0
- package/templates/minimal/src/routes/docs/[version]/+layout.server.ts +42 -0
- package/templates/minimal/src/routes/docs/[version]/+page.server.ts +27 -0
- package/templates/minimal/src/routes/docs/[version]/[...slug]/+page.server.ts +106 -0
- package/templates/minimal/src/routes/docs/[version]/[...slug]/+page.svelte +172 -0
- package/templates/minimal/static/api-specs/openapi-example.json +259 -0
- package/templates/minimal/static/api-specs/postman-example.json +205 -0
- package/templates/minimal/static/api-specs/test-api.json +256 -0
- package/templates/minimal/static/api-specs/users-api.json +264 -0
- package/templates/minimal/static/favicon.svg +4 -0
- package/templates/minimal/svelte.config.js +13 -0
- package/templates/minimal/tsconfig.json +12 -0
- package/templates/minimal/vite.config.ts +6 -0
|
@@ -0,0 +1,309 @@
|
|
|
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: "Raw token found in specra.config.json. Run `specra login` again to migrate it to .env."
|
|
222
|
+
});
|
|
223
|
+
} else if (config.auth?.tokenEnv) {
|
|
224
|
+
const envPath = join(dir, ".env");
|
|
225
|
+
if (existsSync(envPath)) {
|
|
226
|
+
try {
|
|
227
|
+
const envContent = readFileSync(envPath, "utf-8");
|
|
228
|
+
const hasVar = envContent.split("\n").some((line) => line.trim().startsWith(`${config.auth.tokenEnv}=`));
|
|
229
|
+
if (hasVar) {
|
|
230
|
+
results.push({
|
|
231
|
+
label: "Auth",
|
|
232
|
+
status: "pass",
|
|
233
|
+
message: `Token stored in .env as ${config.auth.tokenEnv}`
|
|
234
|
+
});
|
|
235
|
+
} else {
|
|
236
|
+
results.push({
|
|
237
|
+
label: "Auth",
|
|
238
|
+
status: "fail",
|
|
239
|
+
message: `specra.config.json references ${config.auth.tokenEnv} but it's not set in .env. Run \`specra login\`.`
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
} catch {
|
|
243
|
+
results.push({
|
|
244
|
+
label: "Auth",
|
|
245
|
+
status: "fail",
|
|
246
|
+
message: `.env file unreadable. Run \`specra login\` to re-authenticate.`
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
} else {
|
|
250
|
+
results.push({
|
|
251
|
+
label: "Auth",
|
|
252
|
+
status: "fail",
|
|
253
|
+
message: `No .env file found but specra.config.json references ${config.auth.tokenEnv}. Run \`specra login\`.`
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
} else if (config.auth?.source === "global") {
|
|
257
|
+
results.push({
|
|
258
|
+
label: "Auth",
|
|
259
|
+
status: "pass",
|
|
260
|
+
message: "Using global credentials from ~/.specra/config.json"
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
if (config.navigation?.tabGroups && Array.isArray(config.navigation.tabGroups)) {
|
|
264
|
+
const tabGroups = config.navigation.tabGroups;
|
|
265
|
+
const invalid = tabGroups.filter((t) => !t.id || !t.label);
|
|
266
|
+
if (invalid.length > 0) {
|
|
267
|
+
results.push({
|
|
268
|
+
label: "navigation.tabGroups",
|
|
269
|
+
status: "fail",
|
|
270
|
+
message: `${invalid.length} tab group(s) missing required "id" or "label" fields`
|
|
271
|
+
});
|
|
272
|
+
} else {
|
|
273
|
+
results.push({
|
|
274
|
+
label: "navigation.tabGroups",
|
|
275
|
+
status: "pass",
|
|
276
|
+
message: `${tabGroups.length} tab group(s) configured`
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
printResults(results);
|
|
281
|
+
}
|
|
282
|
+
function printResults(results) {
|
|
283
|
+
const icons = {
|
|
284
|
+
pass: pc.green("\u2713"),
|
|
285
|
+
warn: pc.yellow("\u26A0"),
|
|
286
|
+
fail: pc.red("\u2717")
|
|
287
|
+
};
|
|
288
|
+
for (const r of results) {
|
|
289
|
+
const icon = icons[r.status];
|
|
290
|
+
const label = r.status === "fail" ? pc.red(r.label) : r.status === "warn" ? pc.yellow(r.label) : r.label;
|
|
291
|
+
console.log(` ${icon} ${label}: ${pc.dim(r.message)}`);
|
|
292
|
+
}
|
|
293
|
+
const fails = results.filter((r) => r.status === "fail").length;
|
|
294
|
+
const warns = results.filter((r) => r.status === "warn").length;
|
|
295
|
+
const passes = results.filter((r) => r.status === "pass").length;
|
|
296
|
+
console.log();
|
|
297
|
+
if (fails > 0) {
|
|
298
|
+
console.log(pc.red(` ${fails} error(s)`), warns > 0 ? pc.yellow(`${warns} warning(s)`) : "", pc.green(`${passes} passed`));
|
|
299
|
+
} else if (warns > 0) {
|
|
300
|
+
console.log(pc.yellow(` ${warns} warning(s)`), pc.green(`${passes} passed`));
|
|
301
|
+
} else {
|
|
302
|
+
console.log(pc.green(` All ${passes} checks passed!`));
|
|
303
|
+
}
|
|
304
|
+
console.log();
|
|
305
|
+
}
|
|
306
|
+
export {
|
|
307
|
+
doctor
|
|
308
|
+
};
|
|
309
|
+
//# sourceMappingURL=doctor-ICALAJ4N.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. Check auth configuration\n if (config.auth?.token) {\n results.push({\n label: 'auth.token in config',\n status: 'warn',\n message: 'Raw token found in specra.config.json. Run `specra login` again to migrate it to .env.',\n })\n } else if (config.auth?.tokenEnv) {\n // Check the .env file has the referenced var\n const envPath = join(dir, '.env')\n if (existsSync(envPath)) {\n try {\n const envContent = readFileSync(envPath, 'utf-8')\n const hasVar = envContent.split('\\n').some((line: string) => line.trim().startsWith(`${config.auth.tokenEnv}=`))\n if (hasVar) {\n results.push({\n label: 'Auth',\n status: 'pass',\n message: `Token stored in .env as ${config.auth.tokenEnv}`,\n })\n } else {\n results.push({\n label: 'Auth',\n status: 'fail',\n message: `specra.config.json references ${config.auth.tokenEnv} but it's not set in .env. Run \\`specra login\\`.`,\n })\n }\n } catch {\n results.push({\n label: 'Auth',\n status: 'fail',\n message: `.env file unreadable. Run \\`specra login\\` to re-authenticate.`,\n })\n }\n } else {\n results.push({\n label: 'Auth',\n status: 'fail',\n message: `No .env file found but specra.config.json references ${config.auth.tokenEnv}. Run \\`specra login\\`.`,\n })\n }\n } else if (config.auth?.source === 'global') {\n results.push({\n label: 'Auth',\n status: 'pass',\n message: 'Using global credentials from ~/.specra/config.json',\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":[]}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
getConfig,
|
|
4
|
+
saveToken
|
|
5
|
+
} from "./chunk-5765WX4D.js";
|
|
6
|
+
|
|
7
|
+
// src/commands/login.ts
|
|
8
|
+
import pc from "picocolors";
|
|
9
|
+
import open from "open";
|
|
10
|
+
import { createServer } from "http";
|
|
11
|
+
import { randomBytes } from "crypto";
|
|
12
|
+
async function login(options = {}) {
|
|
13
|
+
console.log(pc.bold("Specra Login"));
|
|
14
|
+
console.log();
|
|
15
|
+
if (options.global) {
|
|
16
|
+
console.log(pc.dim("Storing credentials globally in ~/.specra/config.json"));
|
|
17
|
+
} else {
|
|
18
|
+
console.log(pc.dim("Storing credentials in local .env (referenced by specra.config.json)"));
|
|
19
|
+
}
|
|
20
|
+
console.log();
|
|
21
|
+
const state = randomBytes(16).toString("hex");
|
|
22
|
+
const port = 9876;
|
|
23
|
+
const { apiUrl } = getConfig();
|
|
24
|
+
return new Promise((resolve) => {
|
|
25
|
+
const server = createServer(async (req, res) => {
|
|
26
|
+
const url = new URL(req.url, `http://localhost:${port}`);
|
|
27
|
+
const token = url.searchParams.get("token");
|
|
28
|
+
const returnedState = url.searchParams.get("state");
|
|
29
|
+
if (token || returnedState) {
|
|
30
|
+
if (returnedState !== state) {
|
|
31
|
+
res.writeHead(400, { "Content-Type": "text/html" });
|
|
32
|
+
res.end("<html><body><h1>State mismatch. Please try again.</h1></body></html>");
|
|
33
|
+
server.close(() => {
|
|
34
|
+
resolve();
|
|
35
|
+
process.exit(1);
|
|
36
|
+
});
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
if (token) {
|
|
40
|
+
try {
|
|
41
|
+
saveToken(token, { global: options.global });
|
|
42
|
+
console.log(pc.green("Token saved."));
|
|
43
|
+
} catch (err) {
|
|
44
|
+
if (!options.global) {
|
|
45
|
+
console.log(pc.yellow(`Could not save to local config: ${err instanceof Error ? err.message : err}`));
|
|
46
|
+
console.log(pc.yellow("Falling back to global ~/.specra/ config"));
|
|
47
|
+
saveToken(token, { global: true });
|
|
48
|
+
console.log(pc.green("Token saved globally."));
|
|
49
|
+
} else {
|
|
50
|
+
throw err;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
try {
|
|
54
|
+
const { apiRequest } = await import("./api-client-VHQARPDT.js");
|
|
55
|
+
const user = await apiRequest("/api/auth/verify");
|
|
56
|
+
console.log(pc.green(`Authenticated as ${user.email}`));
|
|
57
|
+
} catch {
|
|
58
|
+
}
|
|
59
|
+
res.writeHead(200, { "Content-Type": "text/html" });
|
|
60
|
+
res.end(
|
|
61
|
+
"<html><body><h1>Authenticated!</h1><p>You can close this window and return to the terminal.</p></body></html>"
|
|
62
|
+
);
|
|
63
|
+
} else {
|
|
64
|
+
res.writeHead(400, { "Content-Type": "text/html" });
|
|
65
|
+
res.end("<html><body><h1>Authentication failed.</h1></body></html>");
|
|
66
|
+
}
|
|
67
|
+
server.close(() => {
|
|
68
|
+
resolve();
|
|
69
|
+
process.exit(0);
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
server.listen(port, () => {
|
|
74
|
+
const loginUrl = `${apiUrl}/auth/cli?port=${port}&state=${state}`;
|
|
75
|
+
console.log(`Opening browser to authenticate...`);
|
|
76
|
+
console.log(pc.dim(`If the browser doesn't open, visit: ${loginUrl}`));
|
|
77
|
+
console.log();
|
|
78
|
+
open(loginUrl);
|
|
79
|
+
});
|
|
80
|
+
setTimeout(() => {
|
|
81
|
+
console.log(pc.yellow("Login timed out."));
|
|
82
|
+
server.close(() => {
|
|
83
|
+
resolve();
|
|
84
|
+
process.exit(1);
|
|
85
|
+
});
|
|
86
|
+
}, 3e5);
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
export {
|
|
90
|
+
login
|
|
91
|
+
};
|
|
92
|
+
//# sourceMappingURL=login-UG3WU7DY.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/config.json'))\n } else {\n console.log(pc.dim('Storing credentials in local .env (referenced by 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 process.exit(1)\n })\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 process.exit(0)\n })\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 process.exit(1)\n })\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,uDAAuD,CAAC;AAAA,EAC7E,OAAO;AACL,YAAQ,IAAI,GAAG,IAAI,sEAAsE,CAAC;AAAA,EAC5F;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,MAAM;AACjB,oBAAQ;AACR,oBAAQ,KAAK,CAAC;AAAA,UAChB,CAAC;AACD;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,MAAM;AACjB,kBAAQ;AACR,kBAAQ,KAAK,CAAC;AAAA,QAChB,CAAC;AAAA,MACH;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,MAAM;AACjB,gBAAQ;AACR,gBAAQ,KAAK,CAAC;AAAA,MAChB,CAAC;AAAA,IACH,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-5765WX4D.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-WJKHJZT6.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":[]}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
apiRequest,
|
|
4
|
+
formatError
|
|
5
|
+
} from "./chunk-72RDEJR2.js";
|
|
6
|
+
import {
|
|
7
|
+
isAuthenticated
|
|
8
|
+
} from "./chunk-5765WX4D.js";
|
|
9
|
+
|
|
10
|
+
// src/commands/logs.ts
|
|
11
|
+
import pc from "picocolors";
|
|
12
|
+
async function logs(projectId, options) {
|
|
13
|
+
if (!isAuthenticated()) {
|
|
14
|
+
console.error(pc.red("Not authenticated. Run `specra login` first."));
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
try {
|
|
18
|
+
if (options.deployment) {
|
|
19
|
+
const deploy = await apiRequest(
|
|
20
|
+
`/api/projects/${projectId}/deployments/${options.deployment}`
|
|
21
|
+
);
|
|
22
|
+
printDeployment(deploy);
|
|
23
|
+
} else {
|
|
24
|
+
const data = await apiRequest(
|
|
25
|
+
`/api/projects/${projectId}/deployments?limit=1`
|
|
26
|
+
);
|
|
27
|
+
if (data.deployments.length === 0) {
|
|
28
|
+
console.log(pc.yellow("No deployments found."));
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
const deploy = await apiRequest(
|
|
32
|
+
`/api/projects/${projectId}/deployments/${data.deployments[0].id}`
|
|
33
|
+
);
|
|
34
|
+
printDeployment(deploy);
|
|
35
|
+
}
|
|
36
|
+
} catch (err) {
|
|
37
|
+
console.error(pc.red(formatError("Failed to fetch logs", err)));
|
|
38
|
+
process.exit(1);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function printDeployment(deploy) {
|
|
42
|
+
console.log(pc.bold(`Deployment ${deploy.id.slice(0, 8)}`));
|
|
43
|
+
console.log(` Status: ${colorStatus(deploy.status)}`);
|
|
44
|
+
console.log(` Trigger: ${deploy.trigger.toLowerCase()}`);
|
|
45
|
+
console.log(` Created: ${new Date(deploy.createdAt).toLocaleString()}`);
|
|
46
|
+
console.log();
|
|
47
|
+
if (deploy.buildLogs) {
|
|
48
|
+
console.log(pc.bold("Build Logs:"));
|
|
49
|
+
console.log(pc.dim("\u2500".repeat(60)));
|
|
50
|
+
console.log(deploy.buildLogs);
|
|
51
|
+
console.log(pc.dim("\u2500".repeat(60)));
|
|
52
|
+
}
|
|
53
|
+
if (deploy.containerLogs) {
|
|
54
|
+
console.log();
|
|
55
|
+
console.log(pc.bold("Container Logs:"));
|
|
56
|
+
console.log(pc.dim("\u2500".repeat(60)));
|
|
57
|
+
console.log(deploy.containerLogs);
|
|
58
|
+
console.log(pc.dim("\u2500".repeat(60)));
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
function colorStatus(status) {
|
|
62
|
+
switch (status) {
|
|
63
|
+
case "RUNNING":
|
|
64
|
+
return pc.green(status.toLowerCase());
|
|
65
|
+
case "FAILED":
|
|
66
|
+
return pc.red(status.toLowerCase());
|
|
67
|
+
case "BUILDING":
|
|
68
|
+
case "DEPLOYING":
|
|
69
|
+
return pc.yellow(status.toLowerCase());
|
|
70
|
+
default:
|
|
71
|
+
return pc.dim(status.toLowerCase());
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
export {
|
|
75
|
+
logs
|
|
76
|
+
};
|
|
77
|
+
//# sourceMappingURL=logs-BLUJPWNO.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/commands/logs.ts"],"sourcesContent":["import pc from 'picocolors'\nimport { apiRequest, formatError } from '../api-client.js'\nimport { isAuthenticated } from '../config.js'\n\ninterface Deployment {\n id: string\n status: string\n buildLogs: string | null\n containerLogs?: string\n trigger: string\n createdAt: string\n}\n\ninterface DeploymentList {\n deployments: Deployment[]\n}\n\nexport async function logs(\n projectId: string,\n options: { deployment?: string }\n) {\n if (!isAuthenticated()) {\n console.error(pc.red('Not authenticated. Run `specra login` first.'))\n process.exit(1)\n }\n\n try {\n if (options.deployment) {\n // Get specific deployment\n const deploy = await apiRequest<Deployment>(\n `/api/projects/${projectId}/deployments/${options.deployment}`\n )\n\n printDeployment(deploy)\n } else {\n // Get latest deployment\n const data = await apiRequest<DeploymentList>(\n `/api/projects/${projectId}/deployments?limit=1`\n )\n\n if (data.deployments.length === 0) {\n console.log(pc.yellow('No deployments found.'))\n return\n }\n\n // Fetch full details with logs\n const deploy = await apiRequest<Deployment>(\n `/api/projects/${projectId}/deployments/${data.deployments[0].id}`\n )\n\n printDeployment(deploy)\n }\n } catch (err) {\n console.error(pc.red(formatError('Failed to fetch logs', err)))\n process.exit(1)\n }\n}\n\nfunction printDeployment(deploy: Deployment) {\n console.log(pc.bold(`Deployment ${deploy.id.slice(0, 8)}`))\n console.log(` Status: ${colorStatus(deploy.status)}`)\n console.log(` Trigger: ${deploy.trigger.toLowerCase()}`)\n console.log(` Created: ${new Date(deploy.createdAt).toLocaleString()}`)\n console.log()\n\n if (deploy.buildLogs) {\n console.log(pc.bold('Build Logs:'))\n console.log(pc.dim('─'.repeat(60)))\n console.log(deploy.buildLogs)\n console.log(pc.dim('─'.repeat(60)))\n }\n\n if (deploy.containerLogs) {\n console.log()\n console.log(pc.bold('Container Logs:'))\n console.log(pc.dim('─'.repeat(60)))\n console.log(deploy.containerLogs)\n console.log(pc.dim('─'.repeat(60)))\n }\n}\n\nfunction colorStatus(status: string) {\n switch (status) {\n case 'RUNNING':\n return pc.green(status.toLowerCase())\n case 'FAILED':\n return pc.red(status.toLowerCase())\n case 'BUILDING':\n case 'DEPLOYING':\n return pc.yellow(status.toLowerCase())\n default:\n return pc.dim(status.toLowerCase())\n }\n}\n"],"mappings":";;;;;;;;;;AAAA,OAAO,QAAQ;AAiBf,eAAsB,KACpB,WACA,SACA;AACA,MAAI,CAAC,gBAAgB,GAAG;AACtB,YAAQ,MAAM,GAAG,IAAI,8CAA8C,CAAC;AACpE,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,MAAI;AACF,QAAI,QAAQ,YAAY;AAEtB,YAAM,SAAS,MAAM;AAAA,QACnB,iBAAiB,SAAS,gBAAgB,QAAQ,UAAU;AAAA,MAC9D;AAEA,sBAAgB,MAAM;AAAA,IACxB,OAAO;AAEL,YAAM,OAAO,MAAM;AAAA,QACjB,iBAAiB,SAAS;AAAA,MAC5B;AAEA,UAAI,KAAK,YAAY,WAAW,GAAG;AACjC,gBAAQ,IAAI,GAAG,OAAO,uBAAuB,CAAC;AAC9C;AAAA,MACF;AAGA,YAAM,SAAS,MAAM;AAAA,QACnB,iBAAiB,SAAS,gBAAgB,KAAK,YAAY,CAAC,EAAE,EAAE;AAAA,MAClE;AAEA,sBAAgB,MAAM;AAAA,IACxB;AAAA,EACF,SAAS,KAAK;AACZ,YAAQ,MAAM,GAAG,IAAI,YAAY,wBAAwB,GAAG,CAAC,CAAC;AAC9D,YAAQ,KAAK,CAAC;AAAA,EAChB;AACF;AAEA,SAAS,gBAAgB,QAAoB;AAC3C,UAAQ,IAAI,GAAG,KAAK,cAAc,OAAO,GAAG,MAAM,GAAG,CAAC,CAAC,EAAE,CAAC;AAC1D,UAAQ,IAAI,cAAc,YAAY,OAAO,MAAM,CAAC,EAAE;AACtD,UAAQ,IAAI,cAAc,OAAO,QAAQ,YAAY,CAAC,EAAE;AACxD,UAAQ,IAAI,cAAc,IAAI,KAAK,OAAO,SAAS,EAAE,eAAe,CAAC,EAAE;AACvE,UAAQ,IAAI;AAEZ,MAAI,OAAO,WAAW;AACpB,YAAQ,IAAI,GAAG,KAAK,aAAa,CAAC;AAClC,YAAQ,IAAI,GAAG,IAAI,SAAI,OAAO,EAAE,CAAC,CAAC;AAClC,YAAQ,IAAI,OAAO,SAAS;AAC5B,YAAQ,IAAI,GAAG,IAAI,SAAI,OAAO,EAAE,CAAC,CAAC;AAAA,EACpC;AAEA,MAAI,OAAO,eAAe;AACxB,YAAQ,IAAI;AACZ,YAAQ,IAAI,GAAG,KAAK,iBAAiB,CAAC;AACtC,YAAQ,IAAI,GAAG,IAAI,SAAI,OAAO,EAAE,CAAC,CAAC;AAClC,YAAQ,IAAI,OAAO,aAAa;AAChC,YAAQ,IAAI,GAAG,IAAI,SAAI,OAAO,EAAE,CAAC,CAAC;AAAA,EACpC;AACF;AAEA,SAAS,YAAY,QAAgB;AACnC,UAAQ,QAAQ;AAAA,IACd,KAAK;AACH,aAAO,GAAG,MAAM,OAAO,YAAY,CAAC;AAAA,IACtC,KAAK;AACH,aAAO,GAAG,IAAI,OAAO,YAAY,CAAC;AAAA,IACpC,KAAK;AAAA,IACL,KAAK;AACH,aAAO,GAAG,OAAO,OAAO,YAAY,CAAC;AAAA,IACvC;AACE,aAAO,GAAG,IAAI,OAAO,YAAY,CAAC;AAAA,EACtC;AACF;","names":[]}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
apiRequest,
|
|
4
|
+
formatError
|
|
5
|
+
} from "./chunk-72RDEJR2.js";
|
|
6
|
+
import {
|
|
7
|
+
isAuthenticated
|
|
8
|
+
} from "./chunk-5765WX4D.js";
|
|
9
|
+
|
|
10
|
+
// src/commands/projects.ts
|
|
11
|
+
import pc from "picocolors";
|
|
12
|
+
async function projects() {
|
|
13
|
+
if (!isAuthenticated()) {
|
|
14
|
+
console.error(pc.red("Not authenticated. Run `specra login` first."));
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
let list;
|
|
18
|
+
try {
|
|
19
|
+
list = await apiRequest("/api/projects");
|
|
20
|
+
} catch (err) {
|
|
21
|
+
console.error(pc.red(formatError("Failed to fetch projects", err)));
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
if (list.length === 0) {
|
|
25
|
+
console.log(pc.yellow("No projects found."));
|
|
26
|
+
console.log(
|
|
27
|
+
`Create one at ${pc.dim("https://specra-docs.com/dashboard/projects/new")}`
|
|
28
|
+
);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
console.log(pc.bold(`Projects (${list.length}):`));
|
|
32
|
+
console.log();
|
|
33
|
+
for (const p of list) {
|
|
34
|
+
const status = p.deployments[0]?.status || "NOT_DEPLOYED";
|
|
35
|
+
const statusColor = status === "RUNNING" ? pc.green : status === "FAILED" ? pc.red : pc.dim;
|
|
36
|
+
console.log(
|
|
37
|
+
` ${pc.bold(p.name)} ${pc.dim(`(${p.id.slice(0, 8)})`)} ${statusColor(status.toLowerCase())}`
|
|
38
|
+
);
|
|
39
|
+
console.log(` ${pc.dim(`${p.subdomain}.docs.specra.dev`)}`);
|
|
40
|
+
if (p.customDomain) {
|
|
41
|
+
console.log(` ${pc.dim(p.customDomain)}`);
|
|
42
|
+
}
|
|
43
|
+
console.log();
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
export {
|
|
47
|
+
projects
|
|
48
|
+
};
|
|
49
|
+
//# sourceMappingURL=projects-LJ57GK3D.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/commands/projects.ts"],"sourcesContent":["import pc from 'picocolors'\nimport { apiRequest, formatError } from '../api-client.js'\nimport { isAuthenticated } from '../config.js'\n\ninterface Project {\n id: string\n name: string\n subdomain: string\n customDomain: string | null\n deployments: Array<{ status: string }>\n}\n\nexport async function projects() {\n if (!isAuthenticated()) {\n console.error(pc.red('Not authenticated. Run `specra login` first.'))\n process.exit(1)\n }\n\n let list: Project[]\n try {\n list = await apiRequest<Project[]>('/api/projects')\n } catch (err) {\n console.error(pc.red(formatError('Failed to fetch projects', err)))\n process.exit(1)\n }\n\n if (list.length === 0) {\n console.log(pc.yellow('No projects found.'))\n console.log(\n `Create one at ${pc.dim('https://specra-docs.com/dashboard/projects/new')}`\n )\n return\n }\n\n console.log(pc.bold(`Projects (${list.length}):`))\n console.log()\n\n for (const p of list) {\n const status = p.deployments[0]?.status || 'NOT_DEPLOYED'\n const statusColor =\n status === 'RUNNING'\n ? pc.green\n : status === 'FAILED'\n ? pc.red\n : pc.dim\n\n console.log(\n ` ${pc.bold(p.name)} ${pc.dim(`(${p.id.slice(0, 8)})`)} ${statusColor(status.toLowerCase())}`\n )\n console.log(` ${pc.dim(`${p.subdomain}.docs.specra.dev`)}`)\n if (p.customDomain) {\n console.log(` ${pc.dim(p.customDomain)}`)\n }\n console.log()\n }\n}\n"],"mappings":";;;;;;;;;;AAAA,OAAO,QAAQ;AAYf,eAAsB,WAAW;AAC/B,MAAI,CAAC,gBAAgB,GAAG;AACtB,YAAQ,MAAM,GAAG,IAAI,8CAA8C,CAAC;AACpE,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,MAAI;AACJ,MAAI;AACF,WAAO,MAAM,WAAsB,eAAe;AAAA,EACpD,SAAS,KAAK;AACZ,YAAQ,MAAM,GAAG,IAAI,YAAY,4BAA4B,GAAG,CAAC,CAAC;AAClE,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,MAAI,KAAK,WAAW,GAAG;AACrB,YAAQ,IAAI,GAAG,OAAO,oBAAoB,CAAC;AAC3C,YAAQ;AAAA,MACN,iBAAiB,GAAG,IAAI,gDAAgD,CAAC;AAAA,IAC3E;AACA;AAAA,EACF;AAEA,UAAQ,IAAI,GAAG,KAAK,aAAa,KAAK,MAAM,IAAI,CAAC;AACjD,UAAQ,IAAI;AAEZ,aAAW,KAAK,MAAM;AACpB,UAAM,SAAS,EAAE,YAAY,CAAC,GAAG,UAAU;AAC3C,UAAM,cACJ,WAAW,YACP,GAAG,QACH,WAAW,WACT,GAAG,MACH,GAAG;AAEX,YAAQ;AAAA,MACN,KAAK,GAAG,KAAK,EAAE,IAAI,CAAC,IAAI,GAAG,IAAI,IAAI,EAAE,GAAG,MAAM,GAAG,CAAC,CAAC,GAAG,CAAC,KAAK,YAAY,OAAO,YAAY,CAAC,CAAC;AAAA,IAC/F;AACA,YAAQ,IAAI,OAAO,GAAG,IAAI,GAAG,EAAE,SAAS,kBAAkB,CAAC,EAAE;AAC7D,QAAI,EAAE,cAAc;AAClB,cAAQ,IAAI,OAAO,GAAG,IAAI,EAAE,YAAY,CAAC,EAAE;AAAA,IAC7C;AACA,YAAQ,IAAI;AAAA,EACd;AACF;","names":[]}
|