shiftapi 0.0.14 → 0.0.16

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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2021 Frank Chiarulli Jr.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -59,7 +59,7 @@ Typically used in a `postinstall` script:
59
59
  |--------|------|---------|-------------|
60
60
  | `server` | `string` | **(required)** | Path to the Go server entry point (e.g. `"./cmd/server"`) |
61
61
  | `baseUrl` | `string` | `"/"` | Base URL for the generated API client |
62
- | `url` | `string` | `"http://localhost:8080"` | Go server address (used by the Vite plugin for dev proxy) |
62
+ | `url` | `string` | `"http://localhost:8080"` | Go server address (used by the Vite/Next.js plugins for dev proxy) |
63
63
 
64
64
  ## License
65
65
 
@@ -37,6 +37,14 @@ async function loadConfig(startDir, configPath) {
37
37
  }
38
38
  return { config, configDir: dirname(resolvedPath) };
39
39
  }
40
+ function findConfigDir(startDir, configPath) {
41
+ if (configPath) {
42
+ const resolved = resolve(startDir, configPath);
43
+ return existsSync(resolved) ? dirname(resolved) : void 0;
44
+ }
45
+ const file = findConfigFile(startDir);
46
+ return file ? dirname(file) : void 0;
47
+ }
40
48
  function findConfigFile(startDir) {
41
49
  let dir = resolve(startDir);
42
50
  const root = resolve("/");
@@ -57,5 +65,6 @@ function findConfigFile(startDir) {
57
65
 
58
66
  export {
59
67
  defineConfig,
60
- loadConfig
68
+ loadConfig,
69
+ findConfigDir
61
70
  };
@@ -80,6 +80,23 @@ export const client = createClient({
80
80
  baseUrl: ${JSON.stringify(baseUrl)},
81
81
  });
82
82
 
83
+ export { createClient };
84
+ `;
85
+ }
86
+ function nextClientJsTemplate(port, baseUrl) {
87
+ const devServerUrl = `http://localhost:${port}`;
88
+ return `// Auto-generated by @shiftapi/next. Do not edit.
89
+ import createClient from "openapi-fetch";
90
+
91
+ const baseUrl =
92
+ process.env.NEXT_PUBLIC_SHIFTAPI_BASE_URL ||
93
+ (typeof window !== "undefined"
94
+ ? ${JSON.stringify("/__shiftapi")}
95
+ : ${JSON.stringify(devServerUrl)});
96
+
97
+ /** Pre-configured, fully-typed API client. */
98
+ export const client = createClient({ baseUrl });
99
+
83
100
  export { createClient };
84
101
  `;
85
102
  }
@@ -111,13 +128,13 @@ async function regenerateTypes(serverEntry, goRoot, baseUrl, isDev, previousType
111
128
  );
112
129
  return { types, virtualModuleSource, changed };
113
130
  }
114
- function writeGeneratedFiles(typesRoot, generatedDts, baseUrl) {
131
+ function writeGeneratedFiles(typesRoot, generatedDts, baseUrl, options) {
115
132
  const outDir = resolve(typesRoot, ".shiftapi");
116
133
  if (!existsSync(outDir)) {
117
134
  mkdirSync(outDir, { recursive: true });
118
135
  }
119
136
  writeFileSync(resolve(outDir, "client.d.ts"), dtsTemplate(generatedDts));
120
- writeFileSync(resolve(outDir, "client.js"), clientJsTemplate(baseUrl));
137
+ writeFileSync(resolve(outDir, "client.js"), options?.clientJsContent ?? clientJsTemplate(baseUrl));
121
138
  writeFileSync(
122
139
  resolve(outDir, "tsconfig.json"),
123
140
  JSON.stringify(
@@ -165,6 +182,7 @@ export {
165
182
  DEV_API_PREFIX,
166
183
  dtsTemplate,
167
184
  clientJsTemplate,
185
+ nextClientJsTemplate,
168
186
  virtualModuleTemplate,
169
187
  regenerateTypes,
170
188
  writeGeneratedFiles,
@@ -22,5 +22,14 @@ declare function loadConfig(startDir: string, configPath?: string): Promise<{
22
22
  config: ShiftAPIConfig;
23
23
  configDir: string;
24
24
  }>;
25
+ interface ShiftAPIPluginOptions {
26
+ /** Explicit path to shiftapi.config.ts (for advanced use only) */
27
+ configPath?: string;
28
+ }
29
+ /**
30
+ * Synchronously walk up from `startDir` to find the directory containing
31
+ * a shiftapi config file. Returns the directory path, or undefined.
32
+ */
33
+ declare function findConfigDir(startDir: string, configPath?: string): string | undefined;
25
34
 
26
- export { type ShiftAPIConfig as S, defineConfig as d, loadConfig as l };
35
+ export { type ShiftAPIConfig as S, type ShiftAPIPluginOptions as a, defineConfig as d, findConfigDir as f, loadConfig as l };
package/dist/index.d.ts CHANGED
@@ -1 +1 @@
1
- export { S as ShiftAPIConfig, d as defineConfig } from './index-PUAivaXi.js';
1
+ export { S as ShiftAPIConfig, d as defineConfig } from './index-BG8_bsaE.js';
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  defineConfig
3
- } from "./chunk-QBXBYTBE.js";
3
+ } from "./chunk-EQN44FQG.js";
4
4
  export {
5
5
  defineConfig
6
6
  };
@@ -1,11 +1,13 @@
1
- export { S as ShiftAPIConfig, d as defineConfig, l as loadConfig } from './index-PUAivaXi.js';
1
+ export { S as ShiftAPIConfig, a as ShiftAPIPluginOptions, d as defineConfig, f as findConfigDir, l as loadConfig } from './index-BG8_bsaE.js';
2
2
 
3
3
  declare function regenerateTypes(serverEntry: string, goRoot: string, baseUrl: string, isDev: boolean, previousTypes: string): Promise<{
4
4
  types: string;
5
5
  virtualModuleSource: string;
6
6
  changed: boolean;
7
7
  }>;
8
- declare function writeGeneratedFiles(typesRoot: string, generatedDts: string, baseUrl: string): void;
8
+ declare function writeGeneratedFiles(typesRoot: string, generatedDts: string, baseUrl: string, options?: {
9
+ clientJsContent?: string;
10
+ }): void;
9
11
  declare function patchTsConfig(tsconfigDir: string, typesRoot: string): void;
10
12
 
11
13
  /**
@@ -23,10 +25,23 @@ declare function generateTypes(spec: object): Promise<string>;
23
25
 
24
26
  declare function dtsTemplate(generatedTypes: string): string;
25
27
  declare function clientJsTemplate(baseUrl: string): string;
28
+ declare function nextClientJsTemplate(port: number, baseUrl: string): string;
26
29
  declare function virtualModuleTemplate(baseUrl: string, devApiPrefix?: string): string;
27
30
 
28
31
  declare const MODULE_ID = "@shiftapi/client";
29
32
  declare const RESOLVED_MODULE_ID: string;
30
33
  declare const DEV_API_PREFIX = "/__shiftapi";
31
34
 
32
- export { DEV_API_PREFIX, MODULE_ID, RESOLVED_MODULE_ID, clientJsTemplate, dtsTemplate, extractSpec, generateTypes, patchTsConfig, regenerateTypes, virtualModuleTemplate, writeGeneratedFiles };
35
+ declare class GoServerManager {
36
+ private readonly serverEntry;
37
+ private readonly goRoot;
38
+ private goProcess;
39
+ constructor(serverEntry: string, goRoot: string);
40
+ start(port: number): Promise<void>;
41
+ stop(): Promise<void>;
42
+ forceKill(): void;
43
+ }
44
+
45
+ declare function findFreePort(startPort: number): Promise<number>;
46
+
47
+ export { DEV_API_PREFIX, GoServerManager, MODULE_ID, RESOLVED_MODULE_ID, clientJsTemplate, dtsTemplate, extractSpec, findFreePort, generateTypes, nextClientJsTemplate, patchTsConfig, regenerateTypes, virtualModuleTemplate, writeGeneratedFiles };
package/dist/internal.js CHANGED
@@ -6,25 +6,138 @@ import {
6
6
  dtsTemplate,
7
7
  extractSpec,
8
8
  generateTypes,
9
+ nextClientJsTemplate,
9
10
  patchTsConfig,
10
11
  regenerateTypes,
11
12
  virtualModuleTemplate,
12
13
  writeGeneratedFiles
13
- } from "./chunk-M6SOET3R.js";
14
+ } from "./chunk-TE7E7CKV.js";
14
15
  import {
15
16
  defineConfig,
17
+ findConfigDir,
16
18
  loadConfig
17
- } from "./chunk-QBXBYTBE.js";
19
+ } from "./chunk-EQN44FQG.js";
20
+
21
+ // src/goServer.ts
22
+ import { spawn } from "child_process";
23
+ import { resolve } from "path";
24
+ var GoServerManager = class {
25
+ constructor(serverEntry, goRoot) {
26
+ this.serverEntry = serverEntry;
27
+ this.goRoot = goRoot;
28
+ }
29
+ goProcess = null;
30
+ start(port) {
31
+ return new Promise((resolveStart, rejectStart) => {
32
+ const proc = spawn(
33
+ "go",
34
+ ["run", "-tags", "shiftapidev", this.serverEntry],
35
+ {
36
+ cwd: resolve(this.goRoot),
37
+ stdio: ["ignore", "inherit", "inherit"],
38
+ detached: true,
39
+ env: {
40
+ ...process.env,
41
+ SHIFTAPI_PORT: String(port)
42
+ }
43
+ }
44
+ );
45
+ this.goProcess = proc;
46
+ let settled = false;
47
+ proc.on("error", (err) => {
48
+ console.error("[shiftapi] Failed to start Go server:", err.message);
49
+ if (!settled) {
50
+ settled = true;
51
+ rejectStart(err);
52
+ }
53
+ });
54
+ proc.on("exit", (code) => {
55
+ if (code !== null && code !== 0) {
56
+ console.error(`[shiftapi] Go server exited with code ${code}`);
57
+ }
58
+ this.goProcess = null;
59
+ });
60
+ proc.on("spawn", () => {
61
+ if (!settled) {
62
+ settled = true;
63
+ resolveStart();
64
+ }
65
+ });
66
+ console.log(
67
+ `[shiftapi] Go server starting on port ${port}: go run ${this.serverEntry}`
68
+ );
69
+ });
70
+ }
71
+ stop() {
72
+ const proc = this.goProcess;
73
+ if (!proc || !proc.pid) return Promise.resolve();
74
+ const pid = proc.pid;
75
+ this.goProcess = null;
76
+ return new Promise((resolve2) => {
77
+ const timeout = setTimeout(() => {
78
+ try {
79
+ process.kill(-pid, "SIGKILL");
80
+ } catch {
81
+ }
82
+ resolve2();
83
+ }, 5e3);
84
+ proc.on("exit", () => {
85
+ clearTimeout(timeout);
86
+ resolve2();
87
+ });
88
+ try {
89
+ process.kill(-pid, "SIGTERM");
90
+ } catch {
91
+ clearTimeout(timeout);
92
+ resolve2();
93
+ }
94
+ });
95
+ }
96
+ forceKill() {
97
+ if (this.goProcess?.pid) {
98
+ try {
99
+ process.kill(-this.goProcess.pid, "SIGTERM");
100
+ } catch {
101
+ }
102
+ }
103
+ }
104
+ };
105
+
106
+ // src/ports.ts
107
+ import { createServer as createTcpServer } from "net";
108
+ function isPortFree(port) {
109
+ return new Promise((resolve2) => {
110
+ const server = createTcpServer();
111
+ server.once("error", () => resolve2(false));
112
+ server.once("listening", () => {
113
+ server.close(() => resolve2(true));
114
+ });
115
+ server.listen(port);
116
+ });
117
+ }
118
+ async function findFreePort(startPort) {
119
+ for (let port = startPort; port < startPort + 20; port++) {
120
+ if (await isPortFree(port)) return port;
121
+ }
122
+ console.warn(
123
+ `[shiftapi] No free port found in range ${startPort}-${startPort + 19}, falling back to ${startPort}`
124
+ );
125
+ return startPort;
126
+ }
18
127
  export {
19
128
  DEV_API_PREFIX,
129
+ GoServerManager,
20
130
  MODULE_ID,
21
131
  RESOLVED_MODULE_ID,
22
132
  clientJsTemplate,
23
133
  defineConfig,
24
134
  dtsTemplate,
25
135
  extractSpec,
136
+ findConfigDir,
137
+ findFreePort,
26
138
  generateTypes,
27
139
  loadConfig,
140
+ nextClientJsTemplate,
28
141
  patchTsConfig,
29
142
  regenerateTypes,
30
143
  virtualModuleTemplate,
package/dist/prepare.js CHANGED
@@ -4,10 +4,10 @@ import {
4
4
  generateTypes,
5
5
  patchTsConfig,
6
6
  writeGeneratedFiles
7
- } from "./chunk-M6SOET3R.js";
7
+ } from "./chunk-TE7E7CKV.js";
8
8
  import {
9
9
  loadConfig
10
- } from "./chunk-QBXBYTBE.js";
10
+ } from "./chunk-EQN44FQG.js";
11
11
 
12
12
  // src/prepare.ts
13
13
  import { resolve } from "path";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shiftapi",
3
- "version": "0.0.14",
3
+ "version": "0.0.16",
4
4
  "description": "CLI and codegen for shiftapi – fully-typed TypeScript clients from Go servers",
5
5
  "author": "Frank Chiarulli Jr. <frank@frankchiarulli.com>",
6
6
  "license": "MIT",
@@ -32,11 +32,6 @@
32
32
  "files": [
33
33
  "dist"
34
34
  ],
35
- "scripts": {
36
- "build": "tsup src/index.ts src/internal.ts src/prepare.ts --format esm --dts",
37
- "dev": "tsup src/index.ts src/internal.ts src/prepare.ts --format esm --dts --watch",
38
- "test": "vitest run"
39
- },
40
35
  "dependencies": {
41
36
  "comment-json": "^4.5.1",
42
37
  "jiti": "^2.6.1",
@@ -47,5 +42,10 @@
47
42
  "tsup": "^8.0.0",
48
43
  "typescript": "^5.5.0",
49
44
  "vitest": "^2.0.0"
45
+ },
46
+ "scripts": {
47
+ "build": "tsup src/index.ts src/internal.ts src/prepare.ts --format esm --dts",
48
+ "dev": "tsup src/index.ts src/internal.ts src/prepare.ts --format esm --dts --watch",
49
+ "test": "vitest run"
50
50
  }
51
- }
51
+ }
@@ -1,231 +0,0 @@
1
- // src/config.ts
2
- import { existsSync } from "fs";
3
- import { resolve, dirname } from "path";
4
- import { createRequire } from "module";
5
- function defineConfig(config) {
6
- return config;
7
- }
8
- var CONFIG_FILENAMES = [
9
- "shiftapi.config.ts",
10
- "shiftapi.config.js",
11
- "shiftapi.config.mjs"
12
- ];
13
- async function loadConfig(startDir, configPath) {
14
- let resolvedPath;
15
- if (configPath) {
16
- resolvedPath = resolve(startDir, configPath);
17
- if (!existsSync(resolvedPath)) {
18
- throw new Error(`[shiftapi] Config file not found: ${resolvedPath}`);
19
- }
20
- } else {
21
- resolvedPath = findConfigFile(startDir);
22
- if (!resolvedPath) {
23
- throw new Error(
24
- `[shiftapi] Could not find shiftapi.config.ts (searched upward from ${startDir}). Create one with: import { defineConfig } from "shiftapi"`
25
- );
26
- }
27
- }
28
- const require2 = createRequire(import.meta.url);
29
- const { createJiti } = require2("jiti");
30
- const jiti = createJiti(resolvedPath);
31
- const mod = await jiti.import(resolvedPath);
32
- const config = mod.default ?? mod;
33
- if (!config.server) {
34
- throw new Error(
35
- `[shiftapi] Config at ${resolvedPath} must specify a "server" field (path to Go entry point)`
36
- );
37
- }
38
- return { config, configDir: dirname(resolvedPath) };
39
- }
40
- function findConfigFile(startDir) {
41
- let dir = resolve(startDir);
42
- const root = resolve("/");
43
- while (true) {
44
- for (const name of CONFIG_FILENAMES) {
45
- const candidate = resolve(dir, name);
46
- if (existsSync(candidate)) {
47
- return candidate;
48
- }
49
- }
50
- const parent = dirname(dir);
51
- if (parent === dir || dir === root) {
52
- return void 0;
53
- }
54
- dir = parent;
55
- }
56
- }
57
-
58
- // src/extract.ts
59
- import { execFileSync } from "child_process";
60
- import { readFileSync, unlinkSync, rmSync, mkdtempSync } from "fs";
61
- import { join } from "path";
62
- import { tmpdir } from "os";
63
- function extractSpec(serverEntry, goRoot) {
64
- const tempDir = mkdtempSync(join(tmpdir(), "shiftapi-"));
65
- const specPath = join(tempDir, "openapi.json");
66
- try {
67
- execFileSync("go", ["run", "-tags", "shiftapidev", serverEntry], {
68
- cwd: goRoot,
69
- env: {
70
- ...process.env,
71
- SHIFTAPI_EXPORT_SPEC: specPath
72
- },
73
- stdio: ["ignore", "pipe", "pipe"],
74
- timeout: 3e4
75
- });
76
- } catch (err) {
77
- const stderr = err instanceof Error && "stderr" in err ? String(err.stderr) : "";
78
- throw new Error(
79
- `shiftapi: Failed to extract OpenAPI spec.
80
- Command: go run ${serverEntry}
81
- CWD: ${goRoot}
82
- Error: ${stderr || String(err)}`
83
- );
84
- }
85
- let raw;
86
- try {
87
- raw = readFileSync(specPath, "utf-8");
88
- } catch {
89
- throw new Error(
90
- `shiftapi: Spec file was not created at ${specPath}.
91
- Make sure your Go server calls shiftapi.ListenAndServe().`
92
- );
93
- }
94
- try {
95
- unlinkSync(specPath);
96
- rmSync(tempDir, { recursive: true });
97
- } catch {
98
- }
99
- return JSON.parse(raw);
100
- }
101
-
102
- // src/generate.ts
103
- import openapiTS, { astToString } from "openapi-typescript";
104
- async function generateTypes(spec) {
105
- const ast = await openapiTS(spec);
106
- return astToString(ast);
107
- }
108
-
109
- // src/constants.ts
110
- var MODULE_ID = "@shiftapi/client";
111
- var RESOLVED_MODULE_ID = "\0" + MODULE_ID;
112
- var DEV_API_PREFIX = "/__shiftapi";
113
-
114
- // src/templates.ts
115
- function indent(text, spaces = 2) {
116
- const prefix = " ".repeat(spaces);
117
- return text.split("\n").map((line) => line ? prefix + line : line).join("\n");
118
- }
119
- function dtsTemplate(generatedTypes) {
120
- return `// Auto-generated by shiftapi. Do not edit.
121
- declare module "@shiftapi/client" {
122
- ${indent(generatedTypes)}
123
-
124
- import type createClient from "openapi-fetch";
125
-
126
- export const client: ReturnType<typeof createClient<paths>>;
127
- export { createClient };
128
- }
129
- `;
130
- }
131
- function clientJsTemplate(baseUrl) {
132
- return `// Auto-generated by shiftapi. Do not edit.
133
- import createClient from "openapi-fetch";
134
-
135
- /** Pre-configured, fully-typed API client. */
136
- export const client = createClient({
137
- baseUrl: ${JSON.stringify(baseUrl)},
138
- });
139
-
140
- export { createClient };
141
- `;
142
- }
143
- function virtualModuleTemplate(baseUrl, devApiPrefix) {
144
- const baseUrlExpr = devApiPrefix ? `import.meta.env.VITE_SHIFTAPI_BASE_URL || (import.meta.env.DEV ? ${JSON.stringify(devApiPrefix)} : ${JSON.stringify(baseUrl)})` : `import.meta.env.VITE_SHIFTAPI_BASE_URL || ${JSON.stringify(baseUrl)}`;
145
- return `// Auto-generated by @shiftapi/vite-plugin
146
- import createClient from "openapi-fetch";
147
-
148
- /** Pre-configured, fully-typed API client. */
149
- export const client = createClient({
150
- baseUrl: ${baseUrlExpr},
151
- });
152
-
153
- export { createClient };
154
- `;
155
- }
156
-
157
- // src/codegen.ts
158
- import { resolve as resolve2, relative } from "path";
159
- import { writeFileSync, readFileSync as readFileSync2, mkdirSync, existsSync as existsSync2 } from "fs";
160
- import { parse, stringify } from "comment-json";
161
- async function regenerateTypes(serverEntry, goRoot, baseUrl, isDev, previousTypes) {
162
- const spec = extractSpec(serverEntry, resolve2(goRoot));
163
- const types = await generateTypes(spec);
164
- const changed = types !== previousTypes;
165
- const virtualModuleSource = virtualModuleTemplate(
166
- baseUrl,
167
- isDev ? DEV_API_PREFIX : void 0
168
- );
169
- return { types, virtualModuleSource, changed };
170
- }
171
- function writeGeneratedFiles(typesRoot, generatedDts, baseUrl) {
172
- const outDir = resolve2(typesRoot, ".shiftapi");
173
- if (!existsSync2(outDir)) {
174
- mkdirSync(outDir, { recursive: true });
175
- }
176
- writeFileSync(resolve2(outDir, "client.d.ts"), dtsTemplate(generatedDts));
177
- writeFileSync(resolve2(outDir, "client.js"), clientJsTemplate(baseUrl));
178
- writeFileSync(
179
- resolve2(outDir, "tsconfig.json"),
180
- JSON.stringify(
181
- {
182
- compilerOptions: {
183
- paths: {
184
- [MODULE_ID]: ["./client.d.ts"]
185
- }
186
- }
187
- },
188
- null,
189
- 2
190
- ) + "\n"
191
- );
192
- }
193
- function patchTsConfig(tsconfigDir, typesRoot) {
194
- const tsconfigPath = resolve2(tsconfigDir, "tsconfig.json");
195
- if (!existsSync2(tsconfigPath)) return;
196
- const raw = readFileSync2(tsconfigPath, "utf-8");
197
- let tsconfig;
198
- try {
199
- tsconfig = parse(raw);
200
- } catch (err) {
201
- console.warn(
202
- `[shiftapi] Failed to parse tsconfig.json: ${err instanceof Error ? err.message : String(err)}`
203
- );
204
- return;
205
- }
206
- const rel = relative(tsconfigDir, resolve2(typesRoot, ".shiftapi", "tsconfig.json"));
207
- const extendsPath = rel.startsWith("..") ? rel : `./${rel}`;
208
- if (tsconfig?.extends === extendsPath) return;
209
- tsconfig.extends = extendsPath;
210
- const detectedIndent = raw.match(/^[ \t]+/m)?.[0] ?? " ";
211
- writeFileSync(tsconfigPath, stringify(tsconfig, null, detectedIndent) + "\n");
212
- console.log(
213
- "[shiftapi] Updated tsconfig.json to extend .shiftapi/tsconfig.json"
214
- );
215
- }
216
-
217
- export {
218
- defineConfig,
219
- loadConfig,
220
- extractSpec,
221
- generateTypes,
222
- MODULE_ID,
223
- RESOLVED_MODULE_ID,
224
- DEV_API_PREFIX,
225
- dtsTemplate,
226
- clientJsTemplate,
227
- virtualModuleTemplate,
228
- regenerateTypes,
229
- writeGeneratedFiles,
230
- patchTsConfig
231
- };
@@ -1,231 +0,0 @@
1
- // src/config.ts
2
- import { existsSync } from "fs";
3
- import { resolve, dirname } from "path";
4
- import { createRequire } from "module";
5
- function defineConfig(config) {
6
- return config;
7
- }
8
- var CONFIG_FILENAMES = [
9
- "shiftapi.config.ts",
10
- "shiftapi.config.js",
11
- "shiftapi.config.mjs"
12
- ];
13
- async function loadConfig(startDir, configPath) {
14
- let resolvedPath;
15
- if (configPath) {
16
- resolvedPath = resolve(startDir, configPath);
17
- if (!existsSync(resolvedPath)) {
18
- throw new Error(`[shiftapi] Config file not found: ${resolvedPath}`);
19
- }
20
- } else {
21
- resolvedPath = findConfigFile(startDir);
22
- if (!resolvedPath) {
23
- throw new Error(
24
- `[shiftapi] Could not find shiftapi.config.ts (searched upward from ${startDir}). Create one with: import { defineConfig } from "@shiftapi/cli"`
25
- );
26
- }
27
- }
28
- const require2 = createRequire(import.meta.url);
29
- const { createJiti } = require2("jiti");
30
- const jiti = createJiti(resolvedPath);
31
- const mod = await jiti.import(resolvedPath);
32
- const config = mod.default ?? mod;
33
- if (!config.server) {
34
- throw new Error(
35
- `[shiftapi] Config at ${resolvedPath} must specify a "server" field (path to Go entry point)`
36
- );
37
- }
38
- return { config, configDir: dirname(resolvedPath) };
39
- }
40
- function findConfigFile(startDir) {
41
- let dir = resolve(startDir);
42
- const root = resolve("/");
43
- while (true) {
44
- for (const name of CONFIG_FILENAMES) {
45
- const candidate = resolve(dir, name);
46
- if (existsSync(candidate)) {
47
- return candidate;
48
- }
49
- }
50
- const parent = dirname(dir);
51
- if (parent === dir || dir === root) {
52
- return void 0;
53
- }
54
- dir = parent;
55
- }
56
- }
57
-
58
- // src/extract.ts
59
- import { execFileSync } from "child_process";
60
- import { readFileSync, unlinkSync, rmSync, mkdtempSync } from "fs";
61
- import { join } from "path";
62
- import { tmpdir } from "os";
63
- function extractSpec(serverEntry, goRoot) {
64
- const tempDir = mkdtempSync(join(tmpdir(), "shiftapi-"));
65
- const specPath = join(tempDir, "openapi.json");
66
- try {
67
- execFileSync("go", ["run", "-tags", "shiftapidev", serverEntry], {
68
- cwd: goRoot,
69
- env: {
70
- ...process.env,
71
- SHIFTAPI_EXPORT_SPEC: specPath
72
- },
73
- stdio: ["ignore", "pipe", "pipe"],
74
- timeout: 3e4
75
- });
76
- } catch (err) {
77
- const stderr = err instanceof Error && "stderr" in err ? String(err.stderr) : "";
78
- throw new Error(
79
- `@shiftapi/cli: Failed to extract OpenAPI spec.
80
- Command: go run ${serverEntry}
81
- CWD: ${goRoot}
82
- Error: ${stderr || String(err)}`
83
- );
84
- }
85
- let raw;
86
- try {
87
- raw = readFileSync(specPath, "utf-8");
88
- } catch {
89
- throw new Error(
90
- `@shiftapi/cli: Spec file was not created at ${specPath}.
91
- Make sure your Go server calls shiftapi.ListenAndServe().`
92
- );
93
- }
94
- try {
95
- unlinkSync(specPath);
96
- rmSync(tempDir, { recursive: true });
97
- } catch {
98
- }
99
- return JSON.parse(raw);
100
- }
101
-
102
- // src/generate.ts
103
- import openapiTS, { astToString } from "openapi-typescript";
104
- async function generateTypes(spec) {
105
- const ast = await openapiTS(spec);
106
- return astToString(ast);
107
- }
108
-
109
- // src/constants.ts
110
- var MODULE_ID = "@shiftapi/client";
111
- var RESOLVED_MODULE_ID = "\0" + MODULE_ID;
112
- var DEV_API_PREFIX = "/__shiftapi";
113
-
114
- // src/templates.ts
115
- function indent(text, spaces = 2) {
116
- const prefix = " ".repeat(spaces);
117
- return text.split("\n").map((line) => line ? prefix + line : line).join("\n");
118
- }
119
- function dtsTemplate(generatedTypes) {
120
- return `// Auto-generated by @shiftapi/cli. Do not edit.
121
- declare module "@shiftapi/client" {
122
- ${indent(generatedTypes)}
123
-
124
- import type createClient from "openapi-fetch";
125
-
126
- export const client: ReturnType<typeof createClient<paths>>;
127
- export { createClient };
128
- }
129
- `;
130
- }
131
- function clientJsTemplate(baseUrl) {
132
- return `// Auto-generated by @shiftapi/cli. Do not edit.
133
- import createClient from "openapi-fetch";
134
-
135
- /** Pre-configured, fully-typed API client. */
136
- export const client = createClient({
137
- baseUrl: ${JSON.stringify(baseUrl)},
138
- });
139
-
140
- export { createClient };
141
- `;
142
- }
143
- function virtualModuleTemplate(baseUrl, devApiPrefix) {
144
- const baseUrlExpr = devApiPrefix ? `import.meta.env.VITE_SHIFTAPI_BASE_URL || (import.meta.env.DEV ? ${JSON.stringify(devApiPrefix)} : ${JSON.stringify(baseUrl)})` : `import.meta.env.VITE_SHIFTAPI_BASE_URL || ${JSON.stringify(baseUrl)}`;
145
- return `// Auto-generated by @shiftapi/vite-plugin
146
- import createClient from "openapi-fetch";
147
-
148
- /** Pre-configured, fully-typed API client. */
149
- export const client = createClient({
150
- baseUrl: ${baseUrlExpr},
151
- });
152
-
153
- export { createClient };
154
- `;
155
- }
156
-
157
- // src/codegen.ts
158
- import { resolve as resolve2, relative } from "path";
159
- import { writeFileSync, readFileSync as readFileSync2, mkdirSync, existsSync as existsSync2 } from "fs";
160
- import { parse, stringify } from "comment-json";
161
- async function regenerateTypes(serverEntry, goRoot, baseUrl, isDev, previousTypes) {
162
- const spec = extractSpec(serverEntry, resolve2(goRoot));
163
- const types = await generateTypes(spec);
164
- const changed = types !== previousTypes;
165
- const virtualModuleSource = virtualModuleTemplate(
166
- baseUrl,
167
- isDev ? DEV_API_PREFIX : void 0
168
- );
169
- return { types, virtualModuleSource, changed };
170
- }
171
- function writeGeneratedFiles(typesRoot, generatedDts, baseUrl) {
172
- const outDir = resolve2(typesRoot, ".shiftapi");
173
- if (!existsSync2(outDir)) {
174
- mkdirSync(outDir, { recursive: true });
175
- }
176
- writeFileSync(resolve2(outDir, "client.d.ts"), dtsTemplate(generatedDts));
177
- writeFileSync(resolve2(outDir, "client.js"), clientJsTemplate(baseUrl));
178
- writeFileSync(
179
- resolve2(outDir, "tsconfig.json"),
180
- JSON.stringify(
181
- {
182
- compilerOptions: {
183
- paths: {
184
- [MODULE_ID]: ["./client.d.ts"]
185
- }
186
- }
187
- },
188
- null,
189
- 2
190
- ) + "\n"
191
- );
192
- }
193
- function patchTsConfig(tsconfigDir, typesRoot) {
194
- const tsconfigPath = resolve2(tsconfigDir, "tsconfig.json");
195
- if (!existsSync2(tsconfigPath)) return;
196
- const raw = readFileSync2(tsconfigPath, "utf-8");
197
- let tsconfig;
198
- try {
199
- tsconfig = parse(raw);
200
- } catch (err) {
201
- console.warn(
202
- `[shiftapi] Failed to parse tsconfig.json: ${err instanceof Error ? err.message : String(err)}`
203
- );
204
- return;
205
- }
206
- const rel = relative(tsconfigDir, resolve2(typesRoot, ".shiftapi", "tsconfig.json"));
207
- const extendsPath = rel.startsWith("..") ? rel : `./${rel}`;
208
- if (tsconfig?.extends === extendsPath) return;
209
- tsconfig.extends = extendsPath;
210
- const detectedIndent = raw.match(/^[ \t]+/m)?.[0] ?? " ";
211
- writeFileSync(tsconfigPath, stringify(tsconfig, null, detectedIndent) + "\n");
212
- console.log(
213
- "[shiftapi] Updated tsconfig.json to extend .shiftapi/tsconfig.json"
214
- );
215
- }
216
-
217
- export {
218
- defineConfig,
219
- loadConfig,
220
- extractSpec,
221
- generateTypes,
222
- MODULE_ID,
223
- RESOLVED_MODULE_ID,
224
- DEV_API_PREFIX,
225
- dtsTemplate,
226
- clientJsTemplate,
227
- virtualModuleTemplate,
228
- regenerateTypes,
229
- writeGeneratedFiles,
230
- patchTsConfig
231
- };
@@ -1,61 +0,0 @@
1
- // src/config.ts
2
- import { existsSync } from "fs";
3
- import { resolve, dirname } from "path";
4
- import { createRequire } from "module";
5
- function defineConfig(config) {
6
- return config;
7
- }
8
- var CONFIG_FILENAMES = [
9
- "shiftapi.config.ts",
10
- "shiftapi.config.js",
11
- "shiftapi.config.mjs"
12
- ];
13
- async function loadConfig(startDir, configPath) {
14
- let resolvedPath;
15
- if (configPath) {
16
- resolvedPath = resolve(startDir, configPath);
17
- if (!existsSync(resolvedPath)) {
18
- throw new Error(`[shiftapi] Config file not found: ${resolvedPath}`);
19
- }
20
- } else {
21
- resolvedPath = findConfigFile(startDir);
22
- if (!resolvedPath) {
23
- throw new Error(
24
- `[shiftapi] Could not find shiftapi.config.ts (searched upward from ${startDir}). Create one with: import { defineConfig } from "@shiftapi/core"`
25
- );
26
- }
27
- }
28
- const require2 = createRequire(import.meta.url);
29
- const { createJiti } = require2("jiti");
30
- const jiti = createJiti(resolvedPath);
31
- const mod = await jiti.import(resolvedPath);
32
- const config = mod.default ?? mod;
33
- if (!config.server) {
34
- throw new Error(
35
- `[shiftapi] Config at ${resolvedPath} must specify a "server" field (path to Go entry point)`
36
- );
37
- }
38
- return { config, configDir: dirname(resolvedPath) };
39
- }
40
- function findConfigFile(startDir) {
41
- let dir = resolve(startDir);
42
- const root = resolve("/");
43
- while (true) {
44
- for (const name of CONFIG_FILENAMES) {
45
- const candidate = resolve(dir, name);
46
- if (existsSync(candidate)) {
47
- return candidate;
48
- }
49
- }
50
- const parent = dirname(dir);
51
- if (parent === dir || dir === root) {
52
- return void 0;
53
- }
54
- dir = parent;
55
- }
56
- }
57
-
58
- export {
59
- defineConfig,
60
- loadConfig
61
- };