swallowkit 1.0.0-beta.2 → 1.0.0-beta.21

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 (191) hide show
  1. package/LICENSE +21 -21
  2. package/README.ja.md +312 -215
  3. package/README.md +369 -216
  4. package/dist/__tests__/fixtures.d.ts +22 -0
  5. package/dist/__tests__/fixtures.d.ts.map +1 -0
  6. package/dist/__tests__/fixtures.js +146 -0
  7. package/dist/__tests__/fixtures.js.map +1 -0
  8. package/dist/cli/commands/add-auth.d.ts +10 -0
  9. package/dist/cli/commands/add-auth.d.ts.map +1 -0
  10. package/dist/cli/commands/add-auth.js +444 -0
  11. package/dist/cli/commands/add-auth.js.map +1 -0
  12. package/dist/cli/commands/add-connector.d.ts +20 -0
  13. package/dist/cli/commands/add-connector.d.ts.map +1 -0
  14. package/dist/cli/commands/add-connector.js +163 -0
  15. package/dist/cli/commands/add-connector.js.map +1 -0
  16. package/dist/cli/commands/create-model.d.ts +1 -4
  17. package/dist/cli/commands/create-model.d.ts.map +1 -1
  18. package/dist/cli/commands/create-model.js +21 -82
  19. package/dist/cli/commands/create-model.js.map +1 -1
  20. package/dist/cli/commands/dev-seeds.d.ts +35 -0
  21. package/dist/cli/commands/dev-seeds.d.ts.map +1 -0
  22. package/dist/cli/commands/dev-seeds.js +292 -0
  23. package/dist/cli/commands/dev-seeds.js.map +1 -0
  24. package/dist/cli/commands/dev.d.ts +19 -0
  25. package/dist/cli/commands/dev.d.ts.map +1 -1
  26. package/dist/cli/commands/dev.js +476 -117
  27. package/dist/cli/commands/dev.js.map +1 -1
  28. package/dist/cli/commands/index.d.ts +1 -0
  29. package/dist/cli/commands/index.d.ts.map +1 -1
  30. package/dist/cli/commands/index.js +3 -1
  31. package/dist/cli/commands/index.js.map +1 -1
  32. package/dist/cli/commands/init.d.ts +13 -0
  33. package/dist/cli/commands/init.d.ts.map +1 -1
  34. package/dist/cli/commands/init.js +2627 -1708
  35. package/dist/cli/commands/init.js.map +1 -1
  36. package/dist/cli/commands/scaffold.d.ts +3 -0
  37. package/dist/cli/commands/scaffold.d.ts.map +1 -1
  38. package/dist/cli/commands/scaffold.js +617 -129
  39. package/dist/cli/commands/scaffold.js.map +1 -1
  40. package/dist/cli/index.d.ts +5 -1
  41. package/dist/cli/index.d.ts.map +1 -1
  42. package/dist/cli/index.js +164 -42
  43. package/dist/cli/index.js.map +1 -1
  44. package/dist/core/config.d.ts +8 -2
  45. package/dist/core/config.d.ts.map +1 -1
  46. package/dist/core/config.js +90 -4
  47. package/dist/core/config.js.map +1 -1
  48. package/dist/core/mock/connector-mock-server.d.ts +101 -0
  49. package/dist/core/mock/connector-mock-server.d.ts.map +1 -0
  50. package/dist/core/mock/connector-mock-server.js +480 -0
  51. package/dist/core/mock/connector-mock-server.js.map +1 -0
  52. package/dist/core/mock/zod-mock-generator.d.ts +14 -0
  53. package/dist/core/mock/zod-mock-generator.d.ts.map +1 -0
  54. package/dist/core/mock/zod-mock-generator.js +163 -0
  55. package/dist/core/mock/zod-mock-generator.js.map +1 -0
  56. package/dist/core/operations/create-model.d.ts +15 -0
  57. package/dist/core/operations/create-model.d.ts.map +1 -0
  58. package/dist/core/operations/create-model.js +171 -0
  59. package/dist/core/operations/create-model.js.map +1 -0
  60. package/dist/core/operations/runtime.d.ts +32 -0
  61. package/dist/core/operations/runtime.d.ts.map +1 -0
  62. package/dist/core/operations/runtime.js +225 -0
  63. package/dist/core/operations/runtime.js.map +1 -0
  64. package/dist/core/operations/scaffold-machine.d.ts +16 -0
  65. package/dist/core/operations/scaffold-machine.d.ts.map +1 -0
  66. package/dist/core/operations/scaffold-machine.js +63 -0
  67. package/dist/core/operations/scaffold-machine.js.map +1 -0
  68. package/dist/core/project/manifest.d.ts +92 -0
  69. package/dist/core/project/manifest.d.ts.map +1 -0
  70. package/dist/core/project/manifest.js +321 -0
  71. package/dist/core/project/manifest.js.map +1 -0
  72. package/dist/core/project/validation.d.ts +20 -0
  73. package/dist/core/project/validation.d.ts.map +1 -0
  74. package/dist/core/project/validation.js +204 -0
  75. package/dist/core/project/validation.js.map +1 -0
  76. package/dist/core/scaffold/auth-generator.d.ts +38 -0
  77. package/dist/core/scaffold/auth-generator.d.ts.map +1 -0
  78. package/dist/core/scaffold/auth-generator.js +1244 -0
  79. package/dist/core/scaffold/auth-generator.js.map +1 -0
  80. package/dist/core/scaffold/connector-functions-generator.d.ts +41 -0
  81. package/dist/core/scaffold/connector-functions-generator.d.ts.map +1 -0
  82. package/dist/core/scaffold/connector-functions-generator.js +1027 -0
  83. package/dist/core/scaffold/connector-functions-generator.js.map +1 -0
  84. package/dist/core/scaffold/functions-generator.d.ts +7 -1
  85. package/dist/core/scaffold/functions-generator.d.ts.map +1 -1
  86. package/dist/core/scaffold/functions-generator.js +920 -213
  87. package/dist/core/scaffold/functions-generator.js.map +1 -1
  88. package/dist/core/scaffold/model-parser.d.ts +20 -1
  89. package/dist/core/scaffold/model-parser.d.ts.map +1 -1
  90. package/dist/core/scaffold/model-parser.js +329 -135
  91. package/dist/core/scaffold/model-parser.js.map +1 -1
  92. package/dist/core/scaffold/nextjs-generator.d.ts +8 -0
  93. package/dist/core/scaffold/nextjs-generator.d.ts.map +1 -1
  94. package/dist/core/scaffold/nextjs-generator.js +314 -182
  95. package/dist/core/scaffold/nextjs-generator.js.map +1 -1
  96. package/dist/core/scaffold/openapi-generator.d.ts +3 -0
  97. package/dist/core/scaffold/openapi-generator.d.ts.map +1 -0
  98. package/dist/core/scaffold/openapi-generator.js +190 -0
  99. package/dist/core/scaffold/openapi-generator.js.map +1 -0
  100. package/dist/core/scaffold/ui-generator.d.ts +10 -4
  101. package/dist/core/scaffold/ui-generator.d.ts.map +1 -1
  102. package/dist/core/scaffold/ui-generator.js +768 -663
  103. package/dist/core/scaffold/ui-generator.js.map +1 -1
  104. package/dist/database/base-model.d.ts +3 -3
  105. package/dist/database/base-model.js +3 -3
  106. package/dist/index.d.ts +2 -2
  107. package/dist/index.d.ts.map +1 -1
  108. package/dist/index.js +2 -1
  109. package/dist/index.js.map +1 -1
  110. package/dist/machine/contracts.d.ts +16 -0
  111. package/dist/machine/contracts.d.ts.map +1 -0
  112. package/dist/machine/contracts.js +3 -0
  113. package/dist/machine/contracts.js.map +1 -0
  114. package/dist/machine/errors.d.ts +11 -0
  115. package/dist/machine/errors.d.ts.map +1 -0
  116. package/dist/machine/errors.js +34 -0
  117. package/dist/machine/errors.js.map +1 -0
  118. package/dist/machine/index.d.ts +3 -0
  119. package/dist/machine/index.d.ts.map +1 -0
  120. package/dist/machine/index.js +156 -0
  121. package/dist/machine/index.js.map +1 -0
  122. package/dist/mcp/index.d.ts +25 -0
  123. package/dist/mcp/index.d.ts.map +1 -0
  124. package/dist/mcp/index.js +184 -0
  125. package/dist/mcp/index.js.map +1 -0
  126. package/dist/types/index.d.ts +65 -0
  127. package/dist/types/index.d.ts.map +1 -1
  128. package/dist/utils/package-manager.d.ts +109 -0
  129. package/dist/utils/package-manager.d.ts.map +1 -0
  130. package/dist/utils/package-manager.js +215 -0
  131. package/dist/utils/package-manager.js.map +1 -0
  132. package/package.json +85 -73
  133. package/src/__tests__/__snapshots__/functions-generator.test.ts.snap +1139 -0
  134. package/src/__tests__/__snapshots__/nextjs-generator.test.ts.snap +194 -0
  135. package/src/__tests__/__snapshots__/ui-generator.test.ts.snap +532 -0
  136. package/src/__tests__/auth.test.ts +654 -0
  137. package/src/__tests__/config.test.ts +263 -0
  138. package/src/__tests__/connector-functions-generator.test.ts +288 -0
  139. package/src/__tests__/connector-mock-server.test.ts +439 -0
  140. package/src/__tests__/connector-model-bff.test.ts +162 -0
  141. package/src/__tests__/dev-seeds.test.ts +112 -0
  142. package/src/__tests__/dev.test.ts +154 -0
  143. package/src/__tests__/fixtures.ts +144 -0
  144. package/src/__tests__/functions-generator.test.ts +237 -0
  145. package/src/__tests__/init.test.ts +80 -0
  146. package/src/__tests__/machine.test.ts +212 -0
  147. package/src/__tests__/mcp.test.ts +56 -0
  148. package/src/__tests__/model-parser.test.ts +72 -0
  149. package/src/__tests__/nextjs-generator.test.ts +97 -0
  150. package/src/__tests__/openapi-generator.test.ts +43 -0
  151. package/src/__tests__/package-manager.test.ts +189 -0
  152. package/src/__tests__/scaffold.test.ts +39 -0
  153. package/src/__tests__/string-utils.test.ts +75 -0
  154. package/src/__tests__/ui-generator.test.ts +144 -0
  155. package/src/__tests__/zod-mock-generator.test.ts +132 -0
  156. package/src/cli/commands/add-auth.ts +500 -0
  157. package/src/cli/commands/add-connector.ts +158 -0
  158. package/src/cli/commands/create-model.ts +62 -0
  159. package/src/cli/commands/dev-seeds.ts +358 -0
  160. package/src/cli/commands/dev.ts +962 -0
  161. package/src/cli/commands/index.ts +9 -0
  162. package/src/cli/commands/init.ts +3371 -0
  163. package/src/cli/commands/provision.ts +193 -0
  164. package/src/cli/commands/scaffold.ts +1211 -0
  165. package/src/cli/index.ts +193 -0
  166. package/src/core/config.ts +308 -0
  167. package/src/core/mock/connector-mock-server.ts +555 -0
  168. package/src/core/mock/zod-mock-generator.ts +205 -0
  169. package/src/core/operations/create-model.ts +174 -0
  170. package/src/core/operations/runtime.ts +235 -0
  171. package/src/core/operations/scaffold-machine.ts +91 -0
  172. package/src/core/project/manifest.ts +402 -0
  173. package/src/core/project/validation.ts +221 -0
  174. package/src/core/scaffold/auth-generator.ts +1284 -0
  175. package/src/core/scaffold/connector-functions-generator.ts +1128 -0
  176. package/src/core/scaffold/functions-generator.ts +970 -0
  177. package/src/core/scaffold/model-parser.ts +841 -0
  178. package/src/core/scaffold/nextjs-generator.ts +370 -0
  179. package/src/core/scaffold/openapi-generator.ts +212 -0
  180. package/src/core/scaffold/ui-generator.ts +1061 -0
  181. package/src/database/base-model.ts +184 -0
  182. package/src/database/client.ts +140 -0
  183. package/src/database/repository.ts +104 -0
  184. package/src/database/runtime-check.ts +25 -0
  185. package/src/index.ts +27 -0
  186. package/src/machine/contracts.ts +17 -0
  187. package/src/machine/errors.ts +34 -0
  188. package/src/machine/index.ts +173 -0
  189. package/src/mcp/index.ts +185 -0
  190. package/src/types/index.ts +134 -0
  191. package/src/utils/package-manager.ts +229 -0
@@ -0,0 +1,185 @@
1
+ #!/usr/bin/env node
2
+
3
+ import * as path from "path";
4
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp";
5
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio";
6
+ import * as z from "zod/v4";
7
+ import type { MachineResponse } from "../machine/contracts";
8
+
9
+ interface MachineSuccessPayload<TData> {
10
+ ok: true;
11
+ command: string;
12
+ data: TData;
13
+ }
14
+
15
+ type MachineCliRunner = (args: string[]) => Promise<{ stdout: string; stderr: string; exitCode: number }>;
16
+ type ToolContentResult = { content: Array<{ type: "text"; text: string }> };
17
+ type ToolDefinition = {
18
+ name: string;
19
+ description: string;
20
+ inputSchema: z.ZodTypeAny;
21
+ handler: (input: any) => Promise<ToolContentResult>;
22
+ };
23
+
24
+ function resolveMachineCliEntrypoint(): string {
25
+ return path.resolve(__dirname, "..", "cli", "index.js");
26
+ }
27
+
28
+ async function defaultMachineCliRunner(args: string[]): Promise<{ stdout: string; stderr: string; exitCode: number }> {
29
+ const { execa } = await import("execa");
30
+ const result = await execa(process.execPath, [resolveMachineCliEntrypoint(), "machine", ...args], {
31
+ reject: false,
32
+ });
33
+
34
+ return {
35
+ stdout: result.stdout,
36
+ stderr: result.stderr,
37
+ exitCode: result.exitCode ?? 0,
38
+ };
39
+ }
40
+
41
+ async function executeMachineCommand<TData>(
42
+ args: string[],
43
+ runMachineCli: MachineCliRunner
44
+ ): Promise<MachineSuccessPayload<TData>> {
45
+ const result = await runMachineCli(args);
46
+
47
+ let parsed: MachineResponse<TData>;
48
+ try {
49
+ parsed = JSON.parse(result.stdout) as MachineResponse<TData>;
50
+ } catch {
51
+ throw new Error(result.stderr || result.stdout || "Machine CLI returned invalid JSON.");
52
+ }
53
+
54
+ if (!parsed.ok) {
55
+ throw new Error(parsed.error.message);
56
+ }
57
+
58
+ return parsed;
59
+ }
60
+
61
+ function jsonTextContent(value: unknown): { content: Array<{ type: "text"; text: string }> } {
62
+ return {
63
+ content: [
64
+ {
65
+ type: "text",
66
+ text: JSON.stringify(value, null, 2),
67
+ },
68
+ ],
69
+ };
70
+ }
71
+
72
+ export function buildSwallowKitToolDefinitions(
73
+ runMachineCli: MachineCliRunner = defaultMachineCliRunner
74
+ ): ToolDefinition[] {
75
+ return [
76
+ {
77
+ name: "swallowkit_inspect_project",
78
+ description: "Return framework-owned SwallowKit project metadata.",
79
+ inputSchema: z.object({}),
80
+ handler: async () => {
81
+ const response = await executeMachineCommand(["inspect", "project"], runMachineCli);
82
+ return jsonTextContent(response.data);
83
+ },
84
+ },
85
+ {
86
+ name: "swallowkit_inspect_entities",
87
+ description: "Return SwallowKit entities, schema metadata, and connector/auth annotations.",
88
+ inputSchema: z.object({}),
89
+ handler: async () => {
90
+ const response = await executeMachineCommand(["inspect", "entities"], runMachineCli);
91
+ return jsonTextContent(response.data);
92
+ },
93
+ },
94
+ {
95
+ name: "swallowkit_inspect_routes",
96
+ description: "Return BFF and Functions route metadata understood by SwallowKit.",
97
+ inputSchema: z.object({}),
98
+ handler: async () => {
99
+ const response = await executeMachineCommand(["inspect", "routes"], runMachineCli);
100
+ return jsonTextContent(response.data);
101
+ },
102
+ },
103
+ {
104
+ name: "swallowkit_validate_project",
105
+ description: "Validate project metadata, generated artifacts, and framework conventions.",
106
+ inputSchema: z.object({}),
107
+ handler: async () => {
108
+ const response = await executeMachineCommand(["validate", "project"], runMachineCli);
109
+ return jsonTextContent(response.data);
110
+ },
111
+ },
112
+ {
113
+ name: "swallowkit_generate_model",
114
+ description: "Generate SwallowKit model templates through the official generator.",
115
+ inputSchema: z.object({
116
+ names: z.array(z.string()).min(1),
117
+ modelsDir: z.string().optional(),
118
+ connector: z.string().optional(),
119
+ overwrite: z.enum(["always", "never"]).optional(),
120
+ }),
121
+ handler: async ({ names, modelsDir, connector, overwrite }: { names: string[]; modelsDir?: string; connector?: string; overwrite?: "always" | "never" }) => {
122
+ const response = await executeMachineCommand(["generate", "model", ...names, ...(modelsDir ? ["--models-dir", modelsDir] : []), ...(connector ? ["--connector", connector] : []), "--overwrite", overwrite || "never"], runMachineCli);
123
+ return jsonTextContent(response.data);
124
+ },
125
+ },
126
+ {
127
+ name: "swallowkit_scaffold_model",
128
+ description: "Generate SwallowKit scaffold artifacts through the official generator.",
129
+ inputSchema: z.object({
130
+ model: z.string(),
131
+ functionsDir: z.string().optional(),
132
+ apiDir: z.string().optional(),
133
+ apiOnly: z.boolean().optional(),
134
+ }),
135
+ handler: async ({ model, functionsDir, apiDir, apiOnly }: { model: string; functionsDir?: string; apiDir?: string; apiOnly?: boolean }) => {
136
+ const args = ["generate", "scaffold", model];
137
+ if (functionsDir) {
138
+ args.push("--functions-dir", functionsDir);
139
+ }
140
+ if (apiDir) {
141
+ args.push("--api-dir", apiDir);
142
+ }
143
+ if (apiOnly) {
144
+ args.push("--api-only");
145
+ }
146
+
147
+ const response = await executeMachineCommand(args, runMachineCli);
148
+ return jsonTextContent(response.data);
149
+ },
150
+ },
151
+ ];
152
+ }
153
+
154
+ export function createSwallowKitMcpServer(runMachineCli: MachineCliRunner = defaultMachineCliRunner): McpServer {
155
+ const server = new McpServer({
156
+ name: "swallowkit-mcp",
157
+ version: process.env.npm_package_version || "0.0.0",
158
+ });
159
+
160
+ for (const tool of buildSwallowKitToolDefinitions(runMachineCli)) {
161
+ server.registerTool(
162
+ tool.name,
163
+ {
164
+ description: tool.description,
165
+ inputSchema: tool.inputSchema,
166
+ },
167
+ tool.handler
168
+ );
169
+ }
170
+
171
+ return server;
172
+ }
173
+
174
+ export async function runMcpServer(): Promise<void> {
175
+ const server = createSwallowKitMcpServer();
176
+ const transport = new StdioServerTransport();
177
+ await server.connect(transport);
178
+ }
179
+
180
+ if (require.main === module) {
181
+ void runMcpServer().catch((error) => {
182
+ console.error(error);
183
+ process.exitCode = 1;
184
+ });
185
+ }
@@ -0,0 +1,134 @@
1
+ /**
2
+ * SwallowKit の基本型定義
3
+ */
4
+
5
+ // サーバー関数の実行モード
6
+ export type ServerFnMode = "auto" | "force-server" | "force-client";
7
+
8
+ export type BackendLanguage = "typescript" | "csharp" | "python";
9
+
10
+ // useServerFn のオプション
11
+ export interface UseServerFnOptions {
12
+ mode?: ServerFnMode;
13
+ refetchOnMount?: boolean;
14
+ enabled?: boolean;
15
+ // Next.js キャッシュオプション(内部で使用)
16
+ cache?: boolean;
17
+ revalidate?: number | false;
18
+ tags?: string[];
19
+ }
20
+
21
+ // useServerFn の戻り値
22
+ export interface UseServerFnResult<TResult> {
23
+ data: TResult | null;
24
+ loading: boolean;
25
+ error: any;
26
+ refetch: () => void;
27
+ }
28
+
29
+ // コネクタの操作種別
30
+ export type ConnectorOperation = "getAll" | "getById" | "create" | "update" | "delete";
31
+
32
+ // RDB コネクタ設定
33
+ export interface RdbConnectorConfig {
34
+ type: "rdb";
35
+ provider: "mysql" | "postgres" | "sqlserver";
36
+ connectionEnvVar: string;
37
+ }
38
+
39
+ // API コネクタの認証設定
40
+ export interface ApiConnectorAuth {
41
+ type: "apiKey" | "bearer" | "oauth2";
42
+ envVar: string;
43
+ placement?: "query" | "header";
44
+ paramName?: string;
45
+ }
46
+
47
+ // API コネクタ設定
48
+ export interface ApiConnectorConfig {
49
+ type: "api";
50
+ baseUrlEnvVar: string;
51
+ auth?: ApiConnectorAuth;
52
+ }
53
+
54
+ // コネクタ設定の共用型
55
+ export type ConnectorDefinition = RdbConnectorConfig | ApiConnectorConfig;
56
+
57
+ // モデルに付与するコネクタメタデータ(RDB用)
58
+ export interface RdbModelConnectorConfig {
59
+ connector: string;
60
+ operations: readonly ConnectorOperation[];
61
+ table: string;
62
+ idColumn?: string;
63
+ }
64
+
65
+ // モデルに付与するコネクタメタデータ(API用)
66
+ export interface ApiModelConnectorConfig {
67
+ connector: string;
68
+ operations: readonly ConnectorOperation[];
69
+ endpoints?: Partial<Record<ConnectorOperation, string>>;
70
+ }
71
+
72
+ // モデルに付与するコネクタメタデータの共用型
73
+ export type ModelConnectorConfig = RdbModelConnectorConfig | ApiModelConnectorConfig;
74
+
75
+ // 認証プロバイダー種別
76
+ export type AuthProvider = "custom-jwt" | "swa" | "swa-custom" | "none";
77
+
78
+ // custom-jwt プロバイダー設定
79
+ export interface CustomJwtConfig {
80
+ userConnector: string;
81
+ userTable: string;
82
+ loginIdColumn: string;
83
+ passwordHashColumn: string;
84
+ rolesColumn: string;
85
+ jwtSecretEnv?: string;
86
+ tokenExpiry?: string;
87
+ }
88
+
89
+ // SWA 認証設定(将来用)
90
+ export interface SwaAuthConfig {
91
+ allowedProviders?: string[];
92
+ roleSource?: "swa-roles" | "connector";
93
+ roleConnector?: string;
94
+ }
95
+
96
+ // 認可設定
97
+ export interface AuthorizationConfig {
98
+ defaultPolicy?: "authenticated" | "anonymous";
99
+ }
100
+
101
+ // 認証設定
102
+ export interface AuthConfig {
103
+ provider: AuthProvider;
104
+ customJwt?: CustomJwtConfig;
105
+ swa?: SwaAuthConfig;
106
+ authorization?: AuthorizationConfig;
107
+ }
108
+
109
+ // モデルに付与する認可ポリシー
110
+ export interface ModelAuthPolicy {
111
+ roles?: string[];
112
+ read?: string[];
113
+ write?: string[];
114
+ }
115
+
116
+ // CLI設定の型
117
+ export interface SwallowKitConfig {
118
+ database?: {
119
+ connectionString?: string;
120
+ databaseName?: string;
121
+ };
122
+ backend?: {
123
+ language?: BackendLanguage;
124
+ };
125
+ api?: {
126
+ endpoint?: string;
127
+ cors?: {
128
+ origin?: string | string[];
129
+ credentials?: boolean;
130
+ };
131
+ };
132
+ connectors?: Record<string, ConnectorDefinition>;
133
+ auth?: AuthConfig;
134
+ }
@@ -0,0 +1,229 @@
1
+ import * as fs from "fs";
2
+ import * as path from "path";
3
+ import { execSync } from "child_process";
4
+ import { BackendLanguage } from "../types";
5
+
6
+ /**
7
+ * Supported package managers
8
+ */
9
+ export type PackageManager = "npm" | "pnpm";
10
+
11
+ /**
12
+ * Package manager command mappings
13
+ */
14
+ export interface PackageManagerCommands {
15
+ /** The binary name: "npm" or "pnpm" */
16
+ name: PackageManager;
17
+ /** Install all dependencies: "pnpm install" / "npm install" */
18
+ install: string;
19
+ /** Install with lockfile: "pnpm install --frozen-lockfile" / "npm ci" */
20
+ ci: string;
21
+ /** Add a dependency: "pnpm add" / "npm install" */
22
+ add: string;
23
+ /** Add a dev dependency: "pnpm add -D" / "npm install -D" */
24
+ addDev: string;
25
+ /** Add a global dependency: "pnpm add -g" / "npm install -g" */
26
+ addGlobal: string;
27
+ /** Execute a package binary: "pnpm exec" / "npx" */
28
+ exec: string;
29
+ /** Download & execute: "pnpm dlx" / "npx" */
30
+ dlx: string;
31
+ /** Run a script: "pnpm run" / "npm run" */
32
+ run: string;
33
+ /** Run a script with filter: "pnpm run --filter <ws>" / "npm run --workspace=<ws>" */
34
+ runFilter: (workspace: string) => string;
35
+ /** Start script: "pnpm start" / "npm start" */
36
+ start: string;
37
+ /** Install production only (in temp dir for CI): "pnpm install --prod" / "npm install --omit=dev" */
38
+ installProd: string;
39
+ /** create-next-app flag: "--use-pnpm" / (none for npm) */
40
+ createNextAppFlag: string | null;
41
+ }
42
+
43
+ /**
44
+ * Get the full command mapping for the given package manager
45
+ */
46
+ export function getCommands(pm: PackageManager): PackageManagerCommands {
47
+ if (pm === "pnpm") {
48
+ return {
49
+ name: "pnpm",
50
+ install: "pnpm install",
51
+ ci: "pnpm install --frozen-lockfile",
52
+ add: "pnpm add",
53
+ addDev: "pnpm add -D",
54
+ addGlobal: "pnpm add -g",
55
+ exec: "pnpm exec",
56
+ dlx: "pnpm dlx",
57
+ run: "pnpm run",
58
+ runFilter: (ws) => `pnpm run --filter ${ws}`,
59
+ start: "pnpm start",
60
+ installProd: "pnpm install --prod",
61
+ createNextAppFlag: "--use-pnpm",
62
+ };
63
+ }
64
+
65
+ // npm
66
+ return {
67
+ name: "npm",
68
+ install: "npm install",
69
+ ci: "npm ci",
70
+ add: "npm install",
71
+ addDev: "npm install -D",
72
+ addGlobal: "npm install -g",
73
+ exec: "npx",
74
+ dlx: "npx",
75
+ run: "npm run",
76
+ runFilter: (ws) => `npm run --workspace=${ws}`,
77
+ start: "npm start",
78
+ installProd: "npm install --omit=dev",
79
+ createNextAppFlag: null,
80
+ };
81
+ }
82
+
83
+ /**
84
+ * Detect the preferred package manager for new project initialisation.
85
+ *
86
+ * Strategy (pnpm-preferred):
87
+ * 1. If pnpm is installed on the system → always use pnpm
88
+ * (even when invoked via `npx`, since `npx` is often used out of habit)
89
+ * 2. Otherwise → npm
90
+ *
91
+ * This is intentionally independent of `npm_config_user_agent` so that
92
+ * `npx swallowkit init` still creates a pnpm project when pnpm is available.
93
+ */
94
+ export function detectFromUserAgent(): PackageManager {
95
+ return isPnpmInstalled() ? "pnpm" : "npm";
96
+ }
97
+
98
+ /**
99
+ * Check whether pnpm is available on the system PATH.
100
+ */
101
+ function isPnpmInstalled(): boolean {
102
+ try {
103
+ execSync("pnpm --version", { stdio: "ignore" });
104
+ return true;
105
+ } catch {
106
+ return false;
107
+ }
108
+ }
109
+
110
+ /**
111
+ * Detect the package manager used in an existing project directory
112
+ * by checking for lockfiles.
113
+ *
114
+ * Priority: pnpm-lock.yaml > package-lock.json > fallback to detectFromUserAgent()
115
+ */
116
+ export function detectFromProject(projectDir?: string): PackageManager {
117
+ const dir = projectDir || process.cwd();
118
+
119
+ if (fs.existsSync(path.join(dir, "pnpm-lock.yaml"))) {
120
+ return "pnpm";
121
+ }
122
+ if (fs.existsSync(path.join(dir, "package-lock.json"))) {
123
+ return "npm";
124
+ }
125
+
126
+ // No lockfile found — fall back to user agent detection
127
+ return detectFromUserAgent();
128
+ }
129
+
130
+ /**
131
+ * Get spawn arguments (command + args array) for the package manager.
132
+ * Useful when calling spawn() directly.
133
+ *
134
+ * Examples:
135
+ * spawnArgs("pnpm", ["add", "next@latest"]) => { cmd: "pnpm", args: ["add", "next@latest"] }
136
+ * spawnArgs("npm", ["add", "next@latest"]) => { cmd: "npm", args: ["install", "next@latest"] }
137
+ */
138
+ export function spawnArgs(
139
+ pm: PackageManager,
140
+ args: string[]
141
+ ): { cmd: string; args: string[] } {
142
+ return { cmd: pm, args };
143
+ }
144
+
145
+ /**
146
+ * Workspace configuration helpers
147
+ */
148
+ export function getWorkspaceConfig(pm: PackageManager, workspaces: string[]) {
149
+ if (pm === "pnpm") {
150
+ return {
151
+ /** pnpm uses pnpm-workspace.yaml */
152
+ type: "file" as const,
153
+ filename: "pnpm-workspace.yaml",
154
+ content: `packages:\n${workspaces.map((w) => ` - ${w}`).join("\n")}\n`,
155
+ };
156
+ }
157
+
158
+ // npm uses "workspaces" field in package.json
159
+ return {
160
+ type: "packageJson" as const,
161
+ field: "workspaces",
162
+ value: workspaces,
163
+ };
164
+ }
165
+
166
+ /**
167
+ * CI/CD setup step for GitHub Actions
168
+ */
169
+ export function getCiSetupStep(pm: PackageManager): string {
170
+ if (pm === "pnpm") {
171
+ return ` - name: Setup pnpm
172
+ uses: pnpm/action-setup@v4`;
173
+ }
174
+ // npm: no extra setup needed (comes with Node.js)
175
+ return "";
176
+ }
177
+
178
+ /**
179
+ * CI/CD setup steps for Azure Pipelines
180
+ */
181
+ export function getAzurePipelinesSetup(pm: PackageManager): string {
182
+ if (pm === "pnpm") {
183
+ return ` - script: |
184
+ corepack enable
185
+ corepack prepare pnpm@latest --activate
186
+ displayName: 'Setup pnpm'`;
187
+ }
188
+ // npm: no extra step needed
189
+ return "";
190
+ }
191
+
192
+ /**
193
+ * Build script for generated package.json (depends on workspace command syntax)
194
+ */
195
+ export function getBuildScript(pm: PackageManager): string {
196
+ const copyStandaloneAssets = `node -e "const fs=require('fs');fs.mkdirSync('.next/standalone/.next',{recursive:true});if(fs.existsSync('.next/static'))fs.cpSync('.next/static','.next/standalone/.next/static',{recursive:true});if(fs.existsSync('public'))fs.cpSync('public','.next/standalone/public',{recursive:true});"`;
197
+
198
+ if (pm === "pnpm") {
199
+ return `pnpm run --filter shared build && next build --webpack && ${copyStandaloneAssets}`;
200
+ }
201
+ return `npm run --workspace=shared build && next build --webpack && ${copyStandaloneAssets}`;
202
+ }
203
+
204
+ /**
205
+ * Functions prestart script
206
+ */
207
+ export function getFunctionsPrestart(pm: PackageManager): string {
208
+ if (pm === "pnpm") {
209
+ return "pnpm run build";
210
+ }
211
+ return "npm run build";
212
+ }
213
+
214
+ /**
215
+ * Functions:start script for root package.json
216
+ */
217
+ export function getFunctionsStartScript(
218
+ pm: PackageManager,
219
+ backendLanguage: BackendLanguage = "typescript"
220
+ ): string {
221
+ if (backendLanguage === "typescript") {
222
+ if (pm === "pnpm") {
223
+ return "cd functions && pnpm start";
224
+ }
225
+ return "cd functions && npm start";
226
+ }
227
+
228
+ return "cd functions && func start";
229
+ }