create-specra 0.2.1 → 0.2.3
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/README.md +11 -14
- 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-MA7QG54W.js → chunk-72RDEJR2.js} +22 -2
- 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.js +18 -11
- package/dist/cli.js.map +1 -1
- package/dist/{deploy-SCEMUQNS.js → deploy-V4JO2D6B.js} +74 -36
- 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/index.js +72 -105
- package/dist/index.js.map +1 -1
- package/dist/{login-NKDRQXRE.js → login-UG3WU7DY.js} +36 -15
- 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-YDAUCMAV.js → logs-BLUJPWNO.js} +26 -20
- package/dist/logs-BLUJPWNO.js.map +1 -0
- package/dist/{projects-3TAY7EDJ.js → projects-LJ57GK3D.js} +13 -6
- package/dist/projects-LJ57GK3D.js.map +1 -0
- package/package.json +3 -2
- package/templates/book-docs/.env.sample +1 -0
- package/templates/book-docs/package.json +1 -1
- package/templates/book-docs/src/app.css +3 -4
- package/templates/book-docs/src/routes/+layout.server.ts +3 -0
- package/templates/book-docs/src/routes/docs/[version]/[...slug]/+page.server.ts +1 -1
- package/templates/book-docs/svelte.config.js +6 -1
- package/templates/jbrains-docs/.env.sample +1 -0
- package/templates/jbrains-docs/package.json +1 -1
- package/templates/jbrains-docs/src/app.css +3 -4
- package/templates/jbrains-docs/src/routes/+layout.server.ts +3 -0
- package/templates/jbrains-docs/src/routes/docs/[version]/[...slug]/+page.server.ts +1 -1
- package/templates/jbrains-docs/svelte.config.js +6 -1
- package/templates/minimal/.env.sample +1 -0
- package/templates/minimal/package.json +1 -1
- package/templates/minimal/specra.config.json +13 -1
- package/templates/minimal/src/app.css +3 -4
- package/templates/minimal/src/hooks.server.ts +8 -0
- package/templates/minimal/src/routes/+error.svelte +10 -0
- package/templates/minimal/src/routes/+layout.server.ts +3 -0
- package/templates/minimal/src/routes/+page.svelte +149 -0
- package/templates/minimal/src/routes/docs/[version]/+page.server.ts +14 -0
- package/templates/minimal/src/routes/docs/[version]/[...slug]/+page.server.ts +1 -1
- package/templates/minimal/svelte.config.js +6 -1
- package/dist/chunk-3DKWECRK.js +0 -45
- package/dist/chunk-3DKWECRK.js.map +0 -1
- package/dist/chunk-MA7QG54W.js.map +0 -1
- package/dist/deploy-SCEMUQNS.js.map +0 -1
- package/dist/login-NKDRQXRE.js.map +0 -1
- package/dist/logout-H543QEKU.js +0 -20
- package/dist/logout-H543QEKU.js.map +0 -1
- package/dist/logs-YDAUCMAV.js.map +0 -1
- package/dist/projects-3TAY7EDJ.js.map +0 -1
- /package/templates/minimal/{public → static}/api-specs/openapi-example.json +0 -0
- /package/templates/minimal/{public → static}/api-specs/postman-example.json +0 -0
- /package/templates/minimal/{public → static}/api-specs/test-api.json +0 -0
- /package/templates/minimal/{public → static}/api-specs/users-api.json +0 -0
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
detectPackageManager,
|
|
4
|
+
getPackageManagerCommand
|
|
5
|
+
} from "./chunk-SQ2MMFUZ.js";
|
|
2
6
|
import {
|
|
3
7
|
apiRequest,
|
|
4
|
-
apiUpload
|
|
5
|
-
|
|
8
|
+
apiUpload,
|
|
9
|
+
formatError
|
|
10
|
+
} from "./chunk-72RDEJR2.js";
|
|
6
11
|
import {
|
|
7
12
|
getConfig,
|
|
8
13
|
isAuthenticated
|
|
9
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-5765WX4D.js";
|
|
10
15
|
|
|
11
16
|
// src/commands/deploy.ts
|
|
12
17
|
import pc from "picocolors";
|
|
@@ -15,7 +20,7 @@ import ora from "ora";
|
|
|
15
20
|
// src/archive.ts
|
|
16
21
|
import * as tar from "tar";
|
|
17
22
|
import { existsSync, statSync } from "fs";
|
|
18
|
-
import {
|
|
23
|
+
import { resolve } from "path";
|
|
19
24
|
async function createArchive(dir) {
|
|
20
25
|
const absDir = resolve(dir);
|
|
21
26
|
if (!existsSync(absDir)) {
|
|
@@ -24,37 +29,27 @@ async function createArchive(dir) {
|
|
|
24
29
|
if (!statSync(absDir).isDirectory()) {
|
|
25
30
|
throw new Error(`Not a directory: ${absDir}`);
|
|
26
31
|
}
|
|
27
|
-
const hasPackageJson = existsSync(join(absDir, "package.json"));
|
|
28
|
-
const hasDocsDir = existsSync(join(absDir, "docs"));
|
|
29
|
-
const hasSpecraConfig = existsSync(join(absDir, "specra.config.json"));
|
|
30
|
-
if (!hasPackageJson && !hasDocsDir && !hasSpecraConfig) {
|
|
31
|
-
throw new Error(
|
|
32
|
-
"No Specra project found. Ensure the directory contains package.json, docs/, or specra.config.json"
|
|
33
|
-
);
|
|
34
|
-
}
|
|
35
32
|
const chunks = [];
|
|
36
|
-
|
|
33
|
+
const stream = tar.create(
|
|
37
34
|
{
|
|
38
35
|
gzip: true,
|
|
39
|
-
cwd: absDir
|
|
40
|
-
filter: (path) => {
|
|
41
|
-
if (path.includes("node_modules/")) return false;
|
|
42
|
-
if (path.includes(".next/")) return false;
|
|
43
|
-
if (path.includes(".git/")) return false;
|
|
44
|
-
if (path.includes(".env")) return false;
|
|
45
|
-
return true;
|
|
46
|
-
}
|
|
36
|
+
cwd: absDir
|
|
47
37
|
},
|
|
48
38
|
["."]
|
|
49
|
-
)
|
|
39
|
+
);
|
|
40
|
+
for await (const chunk of stream) {
|
|
50
41
|
chunks.push(chunk);
|
|
51
|
-
}
|
|
52
|
-
|
|
42
|
+
}
|
|
43
|
+
const result = Buffer.concat(chunks);
|
|
44
|
+
if (result.length === 0) {
|
|
45
|
+
throw new Error("Archive is empty \u2014 no files found in the build output");
|
|
46
|
+
}
|
|
47
|
+
return result;
|
|
53
48
|
}
|
|
54
49
|
|
|
55
50
|
// src/commands/deploy.ts
|
|
56
51
|
import { existsSync as existsSync2, readFileSync } from "fs";
|
|
57
|
-
import { join
|
|
52
|
+
import { join, resolve as resolve2 } from "path";
|
|
58
53
|
async function deploy(options) {
|
|
59
54
|
if (!isAuthenticated()) {
|
|
60
55
|
console.error(pc.red("Not authenticated. Run `specra login` first."));
|
|
@@ -63,7 +58,7 @@ async function deploy(options) {
|
|
|
63
58
|
const dir = resolve2(options.dir);
|
|
64
59
|
let projectId = options.project;
|
|
65
60
|
if (!projectId) {
|
|
66
|
-
const configPath =
|
|
61
|
+
const configPath = join(dir, "specra.config.json");
|
|
67
62
|
if (existsSync2(configPath)) {
|
|
68
63
|
try {
|
|
69
64
|
const config = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
@@ -73,12 +68,18 @@ async function deploy(options) {
|
|
|
73
68
|
}
|
|
74
69
|
}
|
|
75
70
|
if (!projectId) {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
71
|
+
let projects;
|
|
72
|
+
try {
|
|
73
|
+
projects = await apiRequest(
|
|
74
|
+
"/api/projects"
|
|
75
|
+
);
|
|
76
|
+
} catch (err) {
|
|
77
|
+
console.error(pc.red(formatError("Failed to fetch projects", err)));
|
|
78
|
+
process.exit(1);
|
|
79
|
+
}
|
|
79
80
|
if (projects.length === 0) {
|
|
80
81
|
console.error(
|
|
81
|
-
pc.red("No projects found. Create one at https://specra.
|
|
82
|
+
pc.red("No projects found. Create one at https://specra-docs.com/dashboard/projects/new")
|
|
82
83
|
);
|
|
83
84
|
process.exit(1);
|
|
84
85
|
}
|
|
@@ -103,13 +104,49 @@ async function deploy(options) {
|
|
|
103
104
|
}
|
|
104
105
|
projectId = response.project;
|
|
105
106
|
}
|
|
106
|
-
const
|
|
107
|
+
const verbose = options.verbose ?? false;
|
|
108
|
+
if (verbose) {
|
|
109
|
+
console.log(pc.dim(`Project ID: ${projectId}`));
|
|
110
|
+
console.log(pc.dim(`Directory: ${dir}`));
|
|
111
|
+
}
|
|
112
|
+
const spinner = ora("Building project...").start();
|
|
107
113
|
try {
|
|
108
|
-
const
|
|
114
|
+
const buildDir = join(dir, "build");
|
|
115
|
+
const { execSync } = await import("child_process");
|
|
116
|
+
const pm = detectPackageManager(dir);
|
|
117
|
+
const pmCmd = getPackageManagerCommand(pm);
|
|
118
|
+
if (verbose) {
|
|
119
|
+
spinner.stop();
|
|
120
|
+
console.log(pc.dim(`Package manager: ${pm}`));
|
|
121
|
+
console.log(pc.dim(`Running: ${pmCmd.run("build")}`));
|
|
122
|
+
console.log();
|
|
123
|
+
}
|
|
124
|
+
try {
|
|
125
|
+
execSync(pmCmd.run("build"), { cwd: dir, stdio: verbose ? "inherit" : "pipe" });
|
|
126
|
+
} catch (err) {
|
|
127
|
+
if (!verbose) {
|
|
128
|
+
const stderr = err instanceof Error && "stderr" in err ? err.stderr?.toString() : "";
|
|
129
|
+
throw new Error(`Build failed:
|
|
130
|
+
${stderr}`);
|
|
131
|
+
}
|
|
132
|
+
throw new Error("Build failed. See output above.");
|
|
133
|
+
}
|
|
134
|
+
if (verbose) {
|
|
135
|
+
spinner.start();
|
|
136
|
+
}
|
|
137
|
+
if (!existsSync2(buildDir)) {
|
|
138
|
+
throw new Error(
|
|
139
|
+
"Build output not found. Expected a `build/` directory.\nMake sure your project uses @sveltejs/adapter-static."
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
spinner.text = "Packaging build output...";
|
|
143
|
+
const archive = await createArchive(buildDir);
|
|
109
144
|
spinner.text = `Uploading (${(archive.length / 1024).toFixed(0)}KB)...`;
|
|
145
|
+
if (verbose) {
|
|
146
|
+
console.log(pc.dim(`Archive size: ${(archive.length / 1024).toFixed(1)}KB`));
|
|
147
|
+
}
|
|
110
148
|
let commitSha;
|
|
111
149
|
try {
|
|
112
|
-
const { execSync } = await import("child_process");
|
|
113
150
|
commitSha = execSync("git rev-parse HEAD", { cwd: dir }).toString().trim();
|
|
114
151
|
} catch {
|
|
115
152
|
}
|
|
@@ -118,10 +155,11 @@ async function deploy(options) {
|
|
|
118
155
|
archive,
|
|
119
156
|
{
|
|
120
157
|
"X-Deploy-Trigger": "CLI",
|
|
158
|
+
"X-Pre-Built": "true",
|
|
121
159
|
...commitSha ? { "X-Commit-Sha": commitSha } : {}
|
|
122
160
|
}
|
|
123
161
|
);
|
|
124
|
-
spinner.succeed(pc.green("
|
|
162
|
+
spinner.succeed(pc.green("Deployed!"));
|
|
125
163
|
console.log();
|
|
126
164
|
console.log(` Deployment ID: ${pc.cyan(result.deploymentId)}`);
|
|
127
165
|
const config = getConfig();
|
|
@@ -131,11 +169,11 @@ async function deploy(options) {
|
|
|
131
169
|
console.log();
|
|
132
170
|
} catch (err) {
|
|
133
171
|
spinner.fail(pc.red("Deploy failed"));
|
|
134
|
-
console.error(
|
|
172
|
+
console.error(pc.red(formatError("", err)));
|
|
135
173
|
process.exit(1);
|
|
136
174
|
}
|
|
137
175
|
}
|
|
138
176
|
export {
|
|
139
177
|
deploy
|
|
140
178
|
};
|
|
141
|
-
//# sourceMappingURL=deploy-
|
|
179
|
+
//# sourceMappingURL=deploy-V4JO2D6B.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/commands/deploy.ts","../src/archive.ts"],"sourcesContent":["import pc from 'picocolors'\nimport ora from 'ora'\nimport { createArchive } from '../archive.js'\nimport { apiUpload, apiRequest, formatError } from '../api-client.js'\nimport { isAuthenticated, getConfig } from '../config.js'\nimport { existsSync, readFileSync } from 'fs'\nimport { join, resolve } from 'path'\nimport { detectPackageManager, getPackageManagerCommand } from '../utils.js'\n\ninterface DeployOptions {\n project?: string\n dir: string\n verbose?: boolean\n}\n\nexport async function deploy(options: DeployOptions) {\n if (!isAuthenticated()) {\n console.error(pc.red('Not authenticated. Run `specra login` first.'))\n process.exit(1)\n }\n\n const dir = resolve(options.dir)\n let projectId = options.project\n\n // If no project specified, try to read from specra.config.json\n if (!projectId) {\n const configPath = join(dir, 'specra.config.json')\n if (existsSync(configPath)) {\n try {\n const config = JSON.parse(readFileSync(configPath, 'utf-8'))\n projectId = config.projectId\n } catch {\n // ignore\n }\n }\n }\n\n if (!projectId) {\n // List projects and ask user to pick\n let projects: Array<{ id: string; name: string; subdomain: string }>\n try {\n projects = await apiRequest<Array<{ id: string; name: string; subdomain: string }>>(\n '/api/projects'\n )\n } catch (err) {\n console.error(pc.red(formatError('Failed to fetch projects', err)))\n process.exit(1)\n }\n\n if (projects.length === 0) {\n console.error(\n pc.red('No projects found. Create one at https://specra-docs.com/dashboard/projects/new')\n )\n process.exit(1)\n }\n\n console.log(pc.bold('Select a project to deploy to:'))\n projects.forEach((p, i) => {\n console.log(` ${pc.dim(`${i + 1}.`)} ${p.name} ${pc.dim(`(${p.subdomain}.docs.specra.dev)`)}`)\n })\n\n const prompts = await import('prompts')\n const response = await prompts.default({\n type: 'select',\n name: 'project',\n message: 'Project',\n choices: projects.map((p) => ({\n title: p.name,\n value: p.id,\n description: `${p.subdomain}.docs.specra.dev`,\n })),\n })\n\n if (!response.project) {\n console.log('Aborted.')\n process.exit(1)\n }\n\n projectId = response.project\n }\n\n const verbose = options.verbose ?? false\n\n if (verbose) {\n console.log(pc.dim(`Project ID: ${projectId}`))\n console.log(pc.dim(`Directory: ${dir}`))\n }\n\n const spinner = ora('Building project...').start()\n\n try {\n // 1. Build the project locally\n const buildDir = join(dir, 'build')\n const { execSync } = await import('child_process')\n\n const pm = detectPackageManager(dir)\n const pmCmd = getPackageManagerCommand(pm)\n\n if (verbose) {\n spinner.stop()\n console.log(pc.dim(`Package manager: ${pm}`))\n console.log(pc.dim(`Running: ${pmCmd.run('build')}`))\n console.log()\n }\n\n try {\n execSync(pmCmd.run('build'), { cwd: dir, stdio: verbose ? 'inherit' : 'pipe' })\n } catch (err) {\n if (!verbose) {\n const stderr = err instanceof Error && 'stderr' in err\n ? (err as { stderr: Buffer }).stderr?.toString()\n : ''\n throw new Error(`Build failed:\\n${stderr}`)\n }\n throw new Error('Build failed. See output above.')\n }\n\n if (verbose) {\n spinner.start()\n }\n\n if (!existsSync(buildDir)) {\n throw new Error(\n 'Build output not found. Expected a `build/` directory.\\n' +\n 'Make sure your project uses @sveltejs/adapter-static.'\n )\n }\n\n // 2. Archive the build output\n spinner.text = 'Packaging build output...'\n const archive = await createArchive(buildDir)\n spinner.text = `Uploading (${(archive.length / 1024).toFixed(0)}KB)...`\n\n if (verbose) {\n console.log(pc.dim(`Archive size: ${(archive.length / 1024).toFixed(1)}KB`))\n }\n\n // 3. Get git commit SHA if available\n let commitSha: string | undefined\n try {\n commitSha = execSync('git rev-parse HEAD', { cwd: dir })\n .toString()\n .trim()\n } catch {\n // not a git repo\n }\n\n // 4. Upload as pre-built\n const result = await apiUpload(\n `/api/projects/${projectId}/deploy`,\n archive,\n {\n 'X-Deploy-Trigger': 'CLI',\n 'X-Pre-Built': 'true',\n ...(commitSha ? { 'X-Commit-Sha': commitSha } : {}),\n }\n ) as { deploymentId: string }\n\n spinner.succeed(pc.green('Deployed!'))\n console.log()\n console.log(` Deployment ID: ${pc.cyan(result.deploymentId)}`)\n\n const config = getConfig()\n console.log(\n ` View status: ${pc.dim(`${config.apiUrl}/dashboard/projects/${projectId}/deployments`)}`\n )\n console.log()\n } catch (err) {\n spinner.fail(pc.red('Deploy failed'))\n console.error(pc.red(formatError('', err)))\n process.exit(1)\n }\n}\n","import * as tar from 'tar'\nimport { existsSync, statSync } from 'fs'\nimport { resolve } from 'path'\n\nexport async function createArchive(dir: string): Promise<Buffer> {\n const absDir = resolve(dir)\n\n if (!existsSync(absDir)) {\n throw new Error(`Directory not found: ${absDir}`)\n }\n\n if (!statSync(absDir).isDirectory()) {\n throw new Error(`Not a directory: ${absDir}`)\n }\n\n const chunks: Buffer[] = []\n\n const stream = tar.create(\n {\n gzip: true,\n cwd: absDir,\n },\n ['.']\n )\n\n for await (const chunk of stream) {\n chunks.push(chunk as Buffer)\n }\n\n const result = Buffer.concat(chunks)\n if (result.length === 0) {\n throw new Error('Archive is empty — no files found in the build output')\n }\n\n return result\n}\n"],"mappings":"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","names":["existsSync","resolve","resolve","existsSync"]}
|
|
@@ -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":[]}
|