create-specra 0.2.2 → 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.
Files changed (42) hide show
  1. package/dist/{api-client-4XZPF7GL.js → api-client-VHQARPDT.js} +3 -3
  2. package/dist/chunk-5765WX4D.js +192 -0
  3. package/dist/chunk-5765WX4D.js.map +1 -0
  4. package/dist/{chunk-CIM73PDF.js → chunk-72RDEJR2.js} +2 -2
  5. package/dist/chunk-SQ2MMFUZ.js +102 -0
  6. package/dist/chunk-SQ2MMFUZ.js.map +1 -0
  7. package/dist/cli.js +7 -7
  8. package/dist/cli.js.map +1 -1
  9. package/dist/{deploy-ETWFNB4Z.js → deploy-V4JO2D6B.js} +32 -6
  10. package/dist/deploy-V4JO2D6B.js.map +1 -0
  11. package/dist/{doctor-IFELWGQB.js → doctor-ICALAJ4N.js} +41 -2
  12. package/dist/doctor-ICALAJ4N.js.map +1 -0
  13. package/dist/index.js +40 -107
  14. package/dist/index.js.map +1 -1
  15. package/dist/{login-DRPP77G4.js → login-UG3WU7DY.js} +17 -11
  16. package/dist/login-UG3WU7DY.js.map +1 -0
  17. package/dist/{logout-UJFYUAQC.js → logout-WJKHJZT6.js} +2 -2
  18. package/dist/{logs-K2CSCKOE.js → logs-BLUJPWNO.js} +3 -3
  19. package/dist/{projects-NORBBC4D.js → projects-LJ57GK3D.js} +3 -3
  20. package/package.json +1 -1
  21. package/templates/book-docs/.env.sample +1 -0
  22. package/templates/book-docs/package.json +1 -1
  23. package/templates/book-docs/src/routes/+layout.server.ts +3 -0
  24. package/templates/book-docs/src/routes/docs/[version]/[...slug]/+page.server.ts +1 -1
  25. package/templates/book-docs/svelte.config.js +6 -1
  26. package/templates/jbrains-docs/.env.sample +1 -0
  27. package/templates/jbrains-docs/package.json +1 -1
  28. package/templates/jbrains-docs/src/routes/+layout.server.ts +3 -0
  29. package/templates/jbrains-docs/src/routes/docs/[version]/[...slug]/+page.server.ts +1 -1
  30. package/templates/jbrains-docs/svelte.config.js +6 -1
  31. package/templates/minimal/.env.sample +1 -0
  32. package/templates/minimal/svelte.config.js +1 -1
  33. package/dist/chunk-ATRUBLRX.js +0 -102
  34. package/dist/chunk-ATRUBLRX.js.map +0 -1
  35. package/dist/deploy-ETWFNB4Z.js.map +0 -1
  36. package/dist/doctor-IFELWGQB.js.map +0 -1
  37. package/dist/login-DRPP77G4.js.map +0 -1
  38. /package/dist/{api-client-4XZPF7GL.js.map → api-client-VHQARPDT.js.map} +0 -0
  39. /package/dist/{chunk-CIM73PDF.js.map → chunk-72RDEJR2.js.map} +0 -0
  40. /package/dist/{logout-UJFYUAQC.js.map → logout-WJKHJZT6.js.map} +0 -0
  41. /package/dist/{logs-K2CSCKOE.js.map → logs-BLUJPWNO.js.map} +0 -0
  42. /package/dist/{projects-NORBBC4D.js.map → projects-LJ57GK3D.js.map} +0 -0
@@ -1,102 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- // src/config.ts
4
- import { readFileSync, writeFileSync, mkdirSync, existsSync, unlinkSync } from "fs";
5
- import { join, resolve } from "path";
6
- import { homedir } from "os";
7
- var GLOBAL_CONFIG_DIR = join(homedir(), ".specra");
8
- var GLOBAL_CONFIG_FILE = join(GLOBAL_CONFIG_DIR, "config.json");
9
- var DEFAULT_GLOBAL_CONFIG = {
10
- apiUrl: "https://specra-docs.com"
11
- };
12
- function getGlobalConfig() {
13
- try {
14
- const raw = readFileSync(GLOBAL_CONFIG_FILE, "utf-8");
15
- return { ...DEFAULT_GLOBAL_CONFIG, ...JSON.parse(raw) };
16
- } catch {
17
- return DEFAULT_GLOBAL_CONFIG;
18
- }
19
- }
20
- function saveGlobalConfig(config) {
21
- mkdirSync(GLOBAL_CONFIG_DIR, { recursive: true });
22
- const current = getGlobalConfig();
23
- const merged = { ...current, ...config };
24
- writeFileSync(GLOBAL_CONFIG_FILE, JSON.stringify(merged, null, 2));
25
- }
26
- function clearGlobalToken() {
27
- const config = getGlobalConfig();
28
- delete config.token;
29
- mkdirSync(GLOBAL_CONFIG_DIR, { recursive: true });
30
- writeFileSync(GLOBAL_CONFIG_FILE, JSON.stringify(config, null, 2));
31
- }
32
- function getLocalConfigPath(dir) {
33
- return join(resolve(dir || "."), "specra.config.json");
34
- }
35
- function readLocalConfig(dir) {
36
- const configPath = getLocalConfigPath(dir);
37
- try {
38
- return JSON.parse(readFileSync(configPath, "utf-8"));
39
- } catch {
40
- return null;
41
- }
42
- }
43
- function getLocalToken(dir) {
44
- const config = readLocalConfig(dir);
45
- return config?.auth?.token;
46
- }
47
- function saveLocalToken(token, dir) {
48
- const configPath = getLocalConfigPath(dir);
49
- if (!existsSync(configPath)) {
50
- throw new Error(`specra.config.json not found in ${resolve(dir || ".")}. Are you in a Specra project?`);
51
- }
52
- const config = JSON.parse(readFileSync(configPath, "utf-8"));
53
- config.auth = { ...config.auth, token };
54
- writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
55
- }
56
- function clearLocalToken(dir) {
57
- const configPath = getLocalConfigPath(dir);
58
- if (!existsSync(configPath)) return;
59
- try {
60
- const config = JSON.parse(readFileSync(configPath, "utf-8"));
61
- if (config.auth) {
62
- delete config.auth.token;
63
- if (Object.keys(config.auth).length === 0) {
64
- delete config.auth;
65
- }
66
- }
67
- writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
68
- } catch {
69
- }
70
- }
71
- function getToken(dir) {
72
- return getLocalToken(dir) || getGlobalConfig().token;
73
- }
74
- function getConfig() {
75
- return getGlobalConfig();
76
- }
77
- function isAuthenticated(dir) {
78
- return !!getToken(dir);
79
- }
80
- function saveToken(token, options) {
81
- if (options?.global) {
82
- saveGlobalConfig({ token });
83
- } else {
84
- saveLocalToken(token, options?.dir);
85
- }
86
- }
87
- function clearToken(options) {
88
- if (options?.global) {
89
- clearGlobalToken();
90
- } else {
91
- clearLocalToken(options?.dir);
92
- }
93
- }
94
-
95
- export {
96
- getToken,
97
- getConfig,
98
- isAuthenticated,
99
- saveToken,
100
- clearToken
101
- };
102
- //# sourceMappingURL=chunk-ATRUBLRX.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/config.ts"],"sourcesContent":["import { readFileSync, writeFileSync, mkdirSync, existsSync, unlinkSync } from 'fs'\nimport { join, resolve } from 'path'\nimport { homedir } from 'os'\n\n// Global config: ~/.specra/config.json\nconst GLOBAL_CONFIG_DIR = join(homedir(), '.specra')\nconst GLOBAL_CONFIG_FILE = join(GLOBAL_CONFIG_DIR, 'config.json')\n\ninterface GlobalConfig {\n apiUrl: string\n token?: string\n defaultProject?: string\n}\n\nconst DEFAULT_GLOBAL_CONFIG: GlobalConfig = {\n apiUrl: 'https://specra-docs.com',\n}\n\n// --------------- Global config ---------------\n\nexport function getGlobalConfig(): GlobalConfig {\n try {\n const raw = readFileSync(GLOBAL_CONFIG_FILE, 'utf-8')\n return { ...DEFAULT_GLOBAL_CONFIG, ...JSON.parse(raw) }\n } catch {\n return DEFAULT_GLOBAL_CONFIG\n }\n}\n\nexport function saveGlobalConfig(config: Partial<GlobalConfig>) {\n mkdirSync(GLOBAL_CONFIG_DIR, { recursive: true })\n const current = getGlobalConfig()\n const merged = { ...current, ...config }\n writeFileSync(GLOBAL_CONFIG_FILE, JSON.stringify(merged, null, 2))\n}\n\nexport function clearGlobalToken() {\n const config = getGlobalConfig()\n delete config.token\n mkdirSync(GLOBAL_CONFIG_DIR, { recursive: true })\n writeFileSync(GLOBAL_CONFIG_FILE, JSON.stringify(config, null, 2))\n}\n\n// --------------- Local config (specra.config.json) ---------------\n\nfunction getLocalConfigPath(dir?: string): string {\n return join(resolve(dir || '.'), 'specra.config.json')\n}\n\nfunction readLocalConfig(dir?: string): Record<string, any> | null {\n const configPath = getLocalConfigPath(dir)\n try {\n return JSON.parse(readFileSync(configPath, 'utf-8'))\n } catch {\n return null\n }\n}\n\nexport function getLocalToken(dir?: string): string | undefined {\n const config = readLocalConfig(dir)\n return config?.auth?.token\n}\n\nexport function saveLocalToken(token: string, dir?: string) {\n const configPath = getLocalConfigPath(dir)\n if (!existsSync(configPath)) {\n throw new Error(`specra.config.json not found in ${resolve(dir || '.')}. Are you in a Specra project?`)\n }\n const config = JSON.parse(readFileSync(configPath, 'utf-8'))\n config.auth = { ...config.auth, token }\n writeFileSync(configPath, JSON.stringify(config, null, 2) + '\\n')\n}\n\nexport function clearLocalToken(dir?: string) {\n const configPath = getLocalConfigPath(dir)\n if (!existsSync(configPath)) return\n try {\n const config = JSON.parse(readFileSync(configPath, 'utf-8'))\n if (config.auth) {\n delete config.auth.token\n if (Object.keys(config.auth).length === 0) {\n delete config.auth\n }\n }\n writeFileSync(configPath, JSON.stringify(config, null, 2) + '\\n')\n } catch {\n // ignore parse errors\n }\n}\n\n// --------------- Unified accessors ---------------\n\n/** Get token: checks local specra.config.json first, then global ~/.specra/config.json */\nexport function getToken(dir?: string): string | undefined {\n return getLocalToken(dir) || getGlobalConfig().token\n}\n\n/** Get the API URL from global config */\nexport function getConfig(): GlobalConfig {\n return getGlobalConfig()\n}\n\nexport function isAuthenticated(dir?: string): boolean {\n return !!getToken(dir)\n}\n\n/** Save token to local config (default) or global config */\nexport function saveToken(token: string, options?: { global?: boolean; dir?: string }) {\n if (options?.global) {\n saveGlobalConfig({ token })\n } else {\n saveLocalToken(token, options?.dir)\n }\n}\n\n/** Clear token from local config (default) or global config */\nexport function clearToken(options?: { global?: boolean; dir?: string }) {\n if (options?.global) {\n clearGlobalToken()\n } else {\n clearLocalToken(options?.dir)\n }\n}\n\n// Legacy exports for backwards compat\nexport function saveConfig(config: Partial<GlobalConfig>) {\n saveGlobalConfig(config)\n}\n\nexport function clearConfig() {\n if (existsSync(GLOBAL_CONFIG_FILE)) {\n unlinkSync(GLOBAL_CONFIG_FILE)\n }\n}\n"],"mappings":";;;AAAA,SAAS,cAAc,eAAe,WAAW,YAAY,kBAAkB;AAC/E,SAAS,MAAM,eAAe;AAC9B,SAAS,eAAe;AAGxB,IAAM,oBAAoB,KAAK,QAAQ,GAAG,SAAS;AACnD,IAAM,qBAAqB,KAAK,mBAAmB,aAAa;AAQhE,IAAM,wBAAsC;AAAA,EAC1C,QAAQ;AACV;AAIO,SAAS,kBAAgC;AAC9C,MAAI;AACF,UAAM,MAAM,aAAa,oBAAoB,OAAO;AACpD,WAAO,EAAE,GAAG,uBAAuB,GAAG,KAAK,MAAM,GAAG,EAAE;AAAA,EACxD,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEO,SAAS,iBAAiB,QAA+B;AAC9D,YAAU,mBAAmB,EAAE,WAAW,KAAK,CAAC;AAChD,QAAM,UAAU,gBAAgB;AAChC,QAAM,SAAS,EAAE,GAAG,SAAS,GAAG,OAAO;AACvC,gBAAc,oBAAoB,KAAK,UAAU,QAAQ,MAAM,CAAC,CAAC;AACnE;AAEO,SAAS,mBAAmB;AACjC,QAAM,SAAS,gBAAgB;AAC/B,SAAO,OAAO;AACd,YAAU,mBAAmB,EAAE,WAAW,KAAK,CAAC;AAChD,gBAAc,oBAAoB,KAAK,UAAU,QAAQ,MAAM,CAAC,CAAC;AACnE;AAIA,SAAS,mBAAmB,KAAsB;AAChD,SAAO,KAAK,QAAQ,OAAO,GAAG,GAAG,oBAAoB;AACvD;AAEA,SAAS,gBAAgB,KAA0C;AACjE,QAAM,aAAa,mBAAmB,GAAG;AACzC,MAAI;AACF,WAAO,KAAK,MAAM,aAAa,YAAY,OAAO,CAAC;AAAA,EACrD,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEO,SAAS,cAAc,KAAkC;AAC9D,QAAM,SAAS,gBAAgB,GAAG;AAClC,SAAO,QAAQ,MAAM;AACvB;AAEO,SAAS,eAAe,OAAe,KAAc;AAC1D,QAAM,aAAa,mBAAmB,GAAG;AACzC,MAAI,CAAC,WAAW,UAAU,GAAG;AAC3B,UAAM,IAAI,MAAM,mCAAmC,QAAQ,OAAO,GAAG,CAAC,gCAAgC;AAAA,EACxG;AACA,QAAM,SAAS,KAAK,MAAM,aAAa,YAAY,OAAO,CAAC;AAC3D,SAAO,OAAO,EAAE,GAAG,OAAO,MAAM,MAAM;AACtC,gBAAc,YAAY,KAAK,UAAU,QAAQ,MAAM,CAAC,IAAI,IAAI;AAClE;AAEO,SAAS,gBAAgB,KAAc;AAC5C,QAAM,aAAa,mBAAmB,GAAG;AACzC,MAAI,CAAC,WAAW,UAAU,EAAG;AAC7B,MAAI;AACF,UAAM,SAAS,KAAK,MAAM,aAAa,YAAY,OAAO,CAAC;AAC3D,QAAI,OAAO,MAAM;AACf,aAAO,OAAO,KAAK;AACnB,UAAI,OAAO,KAAK,OAAO,IAAI,EAAE,WAAW,GAAG;AACzC,eAAO,OAAO;AAAA,MAChB;AAAA,IACF;AACA,kBAAc,YAAY,KAAK,UAAU,QAAQ,MAAM,CAAC,IAAI,IAAI;AAAA,EAClE,QAAQ;AAAA,EAER;AACF;AAKO,SAAS,SAAS,KAAkC;AACzD,SAAO,cAAc,GAAG,KAAK,gBAAgB,EAAE;AACjD;AAGO,SAAS,YAA0B;AACxC,SAAO,gBAAgB;AACzB;AAEO,SAAS,gBAAgB,KAAuB;AACrD,SAAO,CAAC,CAAC,SAAS,GAAG;AACvB;AAGO,SAAS,UAAU,OAAe,SAA8C;AACrF,MAAI,SAAS,QAAQ;AACnB,qBAAiB,EAAE,MAAM,CAAC;AAAA,EAC5B,OAAO;AACL,mBAAe,OAAO,SAAS,GAAG;AAAA,EACpC;AACF;AAGO,SAAS,WAAW,SAA8C;AACvE,MAAI,SAAS,QAAQ;AACnB,qBAAiB;AAAA,EACnB,OAAO;AACL,oBAAgB,SAAS,GAAG;AAAA,EAC9B;AACF;","names":[]}
@@ -1 +0,0 @@
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'\n\ninterface DeployOptions {\n project?: string\n dir: string\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 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 try {\n execSync('npm run build', { cwd: dir, stdio: 'pipe' })\n } catch (err) {\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\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 // 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"]}
@@ -1 +0,0 @@
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":[]}
@@ -1 +0,0 @@
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":[]}