sdnext 0.0.24 → 0.0.26

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.
@@ -1,8 +1,9 @@
1
- import { mkdir, readFile, readdir, stat, writeFile } from "fs/promises";
1
+ import { readFile, readdir, stat } from "fs/promises";
2
2
  import { join, parse, relative } from "path";
3
3
  import { cwd } from "process";
4
4
  import { checkbox as prompts_checkbox, select as prompts_select } from "@inquirer/prompts";
5
5
  import { readSdNextSetting } from "./readSdNextSetting.js";
6
+ import { isScriptModule, normalizePathSeparator, writeGeneratedFile } from "./sharedArtifact.js";
6
7
  import { writeSdNextSetting } from "./writeSdNextSetting.js";
7
8
  function getHookTypeFromName(name) {
8
9
  if (/^get[^a-z]/.test(name)) return "get";
@@ -19,37 +20,57 @@ async function getHookTypeFromContent(path, content) {
19
20
  const type = setting.hook?.[path];
20
21
  if (void 0 !== type && "skip" !== type) return type;
21
22
  if (content.includes("useMutation")) return "mutation";
22
- if (content.includes("IdOrParams")) return "get";
23
+ if (content.includes("createUse") && content.includes("@/presets/")) return "mutation";
24
+ if (content.includes("ClientOptional")) return "get";
23
25
  if (content.includes("useQuery")) return "query";
24
26
  }
27
+ async function getGeneratedFileState(path) {
28
+ try {
29
+ const content = await readFile(path, "utf-8");
30
+ return {
31
+ content,
32
+ overwrite: !content.trim()
33
+ };
34
+ } catch (error) {
35
+ return {
36
+ content: "",
37
+ overwrite: true
38
+ };
39
+ }
40
+ }
25
41
  async function createHook(path, hookMap) {
26
42
  path = relative("actions", path).replace(/\\/g, "/");
27
- const { dir, name, ext, base } = parse(path);
28
- if (".ts" !== ext && ".tsx" !== ext && ".js" !== ext && ".jsx" !== ext) return;
29
- const serverContent = await readFile(join("shared", path), "utf-8");
30
- const match = serverContent.match(new RegExp(`export async function ${name}\\(.+?: (.+?)Params\\)`, "s"));
31
- const hasSchema = !!match;
43
+ const { dir, name, base } = parse(path);
44
+ if (!isScriptModule(path)) return;
32
45
  const upName = name.replace(/^./, (char)=>char.toUpperCase());
33
46
  const key = name.replace(/[A-Z]/g, (char)=>`-${char.toLowerCase()}`);
34
- const mutationHook = `import { useId } from "react"
47
+ const actionImportPath = normalizePathSeparator(join(dir, name));
48
+ const hookName = base.replace(/^./, (char)=>`use${char.toUpperCase()}`);
49
+ const hookPath = join("hooks", dir, hookName);
50
+ const mutationPresetName = `createUse${upName}.ts`;
51
+ const mutationPresetPath = join("presets", dir, mutationPresetName);
52
+ const mutationPresetImportPath = normalizePathSeparator(join(dir, `createUse${upName}`));
53
+ const clientInputType = `${upName}ClientInput`;
54
+ const mutationHook = `import { createRequestFn } from "deepsea-tools"
35
55
 
36
- import { useMutation, UseMutationOptions } from "@tanstack/react-query"
37
- import { createRequestFn } from "deepsea-tools"
56
+ import { ${name}Action } from "@/actions/${actionImportPath}"
38
57
 
39
- import { ${name}Action } from "@/actions/${join(dir, name)}"
58
+ import { createUse${upName} } from "@/presets/${mutationPresetImportPath}"
40
59
 
41
60
  export const ${name}Client = createRequestFn(${name}Action)
42
61
 
43
- export interface Use${upName}Params<TOnMutateResult = unknown> extends Omit<
44
- UseMutationOptions<Awaited<ReturnType<typeof ${name}Client>>, Error, Parameters<typeof ${name}Client>[0], TOnMutateResult>,
45
- "mutationFn"
46
- > {}
62
+ export const use${upName} = createUse${upName}(${name}Client)
63
+ `;
64
+ const mutationPreset = `import { useId } from "react"
65
+
66
+ import { withUseMutationDefaults } from "soda-tanstack-query"
67
+
68
+ import { ${name} } from "@/shared/${actionImportPath}"
47
69
 
48
- export function use${upName}<TOnMutateResult = unknown>({ onMutate, onSuccess, onError, onSettled, ...rest }: Use${upName}Params<TOnMutateResult> = {}) {
70
+ export const createUse${upName} = withUseMutationDefaults<typeof ${name}>(() => {
49
71
  const key = useId()
50
72
 
51
- return useMutation({
52
- mutationFn: ${name}Client,
73
+ return {
53
74
  onMutate(variables, context) {
54
75
  message.open({
55
76
  key,
@@ -57,8 +78,6 @@ export function use${upName}<TOnMutateResult = unknown>({ onMutate, onSuccess, o
57
78
  content: "中...",
58
79
  duration: 0,
59
80
  })
60
-
61
- return onMutate?.(variables, context) as TOnMutateResult | Promise<TOnMutateResult>
62
81
  },
63
82
  onSuccess(data, variables, onMutateResult, context) {
64
83
  context.client.invalidateQueries({ queryKey: ["query-${key.replace(/^.+?-/, "")}"] })
@@ -69,29 +88,24 @@ export function use${upName}<TOnMutateResult = unknown>({ onMutate, onSuccess, o
69
88
  type: "success",
70
89
  content: "成功",
71
90
  })
72
-
73
- return onSuccess?.(data, variables, onMutateResult, context)
74
91
  },
75
92
  onError(error, variables, onMutateResult, context) {
76
93
  message.destroy(key)
77
-
78
- return onError?.(error, variables, onMutateResult, context)
79
- },
80
- onSettled(data, error, variables, onMutateResult, context) {
81
- return onSettled?.(data, error, variables, onMutateResult, context)
82
94
  },
83
- ...rest,
84
- })
85
- }
95
+ onSettled(data, error, variables, onMutateResult, context) {},
96
+ }
97
+ })
86
98
  `;
87
99
  const getHook = `import { createRequestFn, isNonNullable } from "deepsea-tools"
88
100
  import { createUseQuery } from "soda-tanstack-query"
89
101
 
90
- import { ${name}Action } from "@/actions/${join(dir, name)}"
102
+ import { ${name}Action } from "@/actions/${actionImportPath}"
91
103
 
92
104
  export const ${name}Client = createRequestFn(${name}Action)
93
105
 
94
- export function ${name}ClientOptional(id?: ${hasSchema ? `${match[1].replace(/Schema$/, "Params").replace(/^./, (char)=>char.toUpperCase())} | ` : ""}undefined | null) {
106
+ export type ${clientInputType} = Parameters<typeof ${name}Client> extends [] ? undefined : Parameters<typeof ${name}Client>[0]
107
+
108
+ export function ${name}ClientOptional(id?: ${clientInputType} | null) {
95
109
  return isNonNullable(id) ? ${name}Client(id) : null
96
110
  }
97
111
 
@@ -103,7 +117,7 @@ export const use${upName} = createUseQuery({
103
117
  const queryHook = `import { createRequestFn } from "deepsea-tools"
104
118
  import { createUseQuery } from "soda-tanstack-query"
105
119
 
106
- import { ${name}Action } from "@/actions/${join(dir, name)}"
120
+ import { ${name}Action } from "@/actions/${actionImportPath}"
107
121
 
108
122
  export const ${name}Client = createRequestFn(${name}Action)
109
123
 
@@ -117,49 +131,62 @@ export const use${upName} = createUseQuery({
117
131
  query: queryHook,
118
132
  mutation: mutationHook
119
133
  };
120
- const hookName = base.replace(/^./, (char)=>`use${char.toUpperCase()}`);
121
- const hookPath = join("hooks", dir, hookName);
122
134
  let hookType = getHookTypeFromName(name);
123
- let overwrite = true;
124
- try {
125
- const current = await readFile(hookPath, "utf-8");
126
- if (current.trim()) overwrite = false;
127
- const contentType = await getHookTypeFromContent(join(cwd(), hookPath), current);
128
- if (contentType) hookType = contentType;
129
- if (map[hookType] === current) return;
130
- } catch (error) {
131
- overwrite = true;
135
+ const hookState = await getGeneratedFileState(hookPath);
136
+ const contentType = await getHookTypeFromContent(join(cwd(), hookPath), hookState.content);
137
+ if (contentType) hookType = contentType;
138
+ if ("mutation" === hookType) {
139
+ const mutationPresetState = await getGeneratedFileState(mutationPresetPath);
140
+ if (map[hookType] === hookState.content && mutationPreset === mutationPresetState.content) return;
141
+ hookMap[path] = {
142
+ hookPath,
143
+ mutationPreset,
144
+ mutationPresetPath,
145
+ overwrite: hookState.overwrite && mutationPresetState.overwrite,
146
+ type: hookType,
147
+ ...map
148
+ };
149
+ return;
132
150
  }
151
+ if (map[hookType] === hookState.content) return;
133
152
  hookMap[path] = {
134
- overwrite,
153
+ hookPath,
154
+ mutationPreset,
155
+ mutationPresetPath,
156
+ overwrite: hookState.overwrite,
135
157
  type: hookType,
136
158
  ...map
137
159
  };
138
160
  }
139
- async function createActionFromFolder() {
161
+ async function createHookFromFolder() {
140
162
  const map = {};
141
- async function _createActionFromFolder(dir) {
163
+ async function _createHookFromFolder(dir) {
142
164
  const content = await readdir(dir);
143
165
  for (const item of content){
144
166
  const path = join(dir, item);
145
167
  const stats = await stat(path);
146
- if (stats.isDirectory()) await _createActionFromFolder(path);
168
+ if (stats.isDirectory()) await _createHookFromFolder(path);
147
169
  if (stats.isFile()) await createHook(path, map);
148
170
  }
149
171
  }
150
- await _createActionFromFolder("actions");
172
+ try {
173
+ await _createHookFromFolder("actions");
174
+ } catch (error) {
175
+ if (isNodeError(error) && "ENOENT" === error.code) return map;
176
+ throw error;
177
+ }
151
178
  return map;
152
179
  }
153
180
  async function hook(options, { args }) {
154
- const map = await createActionFromFolder();
181
+ const map = await createHookFromFolder();
155
182
  const entires = Object.entries(map);
156
183
  if (0 === entires.length) return void console.log("All hooks are the latest.");
157
184
  const newEntires = entires.filter(([path, { overwrite }])=>overwrite);
158
185
  const oldEntires = entires.filter(([path, { overwrite }])=>!overwrite);
159
186
  const root = cwd();
160
187
  const setting = await getSetting();
161
- for await (const [path, { overwrite, type, ...map }] of newEntires){
162
- const resolved = join(root, "hooks", path);
188
+ for (const [path, { hookPath, mutationPresetPath, mutationPreset, overwrite, type, ...map }] of newEntires){
189
+ const resolved = join(root, hookPath);
163
190
  const answer = await prompts_select({
164
191
  message: path,
165
192
  choices: [
@@ -172,25 +199,34 @@ async function hook(options, { args }) {
172
199
  });
173
200
  setting.hook ??= {};
174
201
  setting.hook[resolved] = answer;
175
- if ("skip" === answer) continue;
176
- const { dir, base } = parse(path);
177
- await mkdir(join("hooks", dir), {
178
- recursive: true
179
- });
180
- await writeFile(join("hooks", dir, base.replace(/^./, (char)=>`use${char.toUpperCase()}`)), map[answer]);
202
+ if ("skip" !== answer) {
203
+ await writeGeneratedFile({
204
+ path: hookPath,
205
+ content: map[answer]
206
+ });
207
+ if ("mutation" === answer) await writeGeneratedFile({
208
+ path: mutationPresetPath,
209
+ content: mutationPreset
210
+ });
211
+ }
181
212
  }
182
213
  await writeSdNextSetting(setting);
183
214
  const overwrites = await prompts_checkbox({
184
215
  message: "Please check the hooks you want to overwrite",
185
216
  choices: oldEntires.map(([key])=>key)
186
217
  });
187
- for (const [path, { overwrite, type, ...map }] of oldEntires){
188
- if (!overwrites.includes(path)) continue;
189
- const { dir, base } = parse(path);
190
- await mkdir(join("hooks", dir), {
191
- recursive: true
218
+ for (const [path, { hookPath, mutationPresetPath, mutationPreset, overwrite, type, ...map }] of oldEntires)if (overwrites.includes(path)) {
219
+ await writeGeneratedFile({
220
+ path: hookPath,
221
+ content: map[type]
222
+ });
223
+ if ("mutation" === type) await writeGeneratedFile({
224
+ path: mutationPresetPath,
225
+ content: mutationPreset
192
226
  });
193
- await writeFile(join("hooks", dir, base.replace(/^./, (char)=>`use${char.toUpperCase()}`)), map[type]);
194
227
  }
195
228
  }
196
- export { createActionFromFolder, createHook, hook };
229
+ function isNodeError(error) {
230
+ return "object" == typeof error && null !== error;
231
+ }
232
+ export { createHook, createHookFromFolder, hook };
@@ -3,3 +3,6 @@ export interface SdNextSetting {
3
3
  hook?: Record<string, OperationType>;
4
4
  }
5
5
  export declare function readSdNextSetting(): Promise<SdNextSetting>;
6
+ export interface NodeError {
7
+ code?: string;
8
+ }
@@ -1,14 +1,19 @@
1
- import { existsSync } from "node:fs";
2
1
  import { readFile } from "node:fs/promises";
3
2
  import { homedir } from "node:os";
4
3
  import { join } from "node:path";
5
4
  async function readSdNextSetting() {
6
5
  const userDir = homedir();
7
6
  const settingPath = join(userDir, ".sdnext.json");
8
- if (existsSync(settingPath)) {
7
+ try {
9
8
  const setting = JSON.parse(await readFile(settingPath, "utf-8"));
10
9
  return setting;
10
+ } catch (error) {
11
+ if (isNodeError(error) && "ENOENT" === error.code) return {};
12
+ console.warn(`Failed to read ${settingPath}, fallback to default setting.`);
13
+ return {};
11
14
  }
12
- return {};
15
+ }
16
+ function isNodeError(error) {
17
+ return "object" == typeof error && null !== error;
13
18
  }
14
19
  export { readSdNextSetting };
@@ -0,0 +1,7 @@
1
+ import { ChildProcess } from "child_process";
2
+ export interface SpawnCommandParams {
3
+ args: string[];
4
+ }
5
+ export declare function spawnCommand({ args }: SpawnCommandParams): ChildProcess;
6
+ export declare function runCommand({ args }: SpawnCommandParams): Promise<number>;
7
+ export declare function waitForChild(child: ChildProcess): Promise<number>;
@@ -0,0 +1,25 @@
1
+ import { spawn } from "child_process";
2
+ function spawnCommand({ args }) {
3
+ const [command, ...commandArgs] = args;
4
+ return spawn(command, commandArgs, {
5
+ shell: true,
6
+ stdio: "inherit"
7
+ });
8
+ }
9
+ async function runCommand({ args }) {
10
+ if (0 === args.length) return 0;
11
+ const child = spawnCommand({
12
+ args
13
+ });
14
+ return waitForChild(child);
15
+ }
16
+ async function waitForChild(child) {
17
+ return await new Promise((resolve, reject)=>{
18
+ child.once("error", reject);
19
+ child.once("close", (code, signal)=>{
20
+ if ("number" == typeof code) return void resolve(code);
21
+ resolve(signal ? 1 : 0);
22
+ });
23
+ });
24
+ }
25
+ export { runCommand, spawnCommand, waitForChild };
@@ -0,0 +1,23 @@
1
+ export interface SharedModuleInfo {
2
+ dir: string;
3
+ ext: string;
4
+ importPath: string;
5
+ name: string;
6
+ relativePath: string;
7
+ }
8
+ export interface GeneratedFileParams {
9
+ content: string;
10
+ path: string;
11
+ }
12
+ export interface RemoveGeneratedFileParams {
13
+ path: string;
14
+ stopPath?: string;
15
+ }
16
+ export declare const scriptModuleExtensions: string[];
17
+ export declare function normalizePathSeparator(path: string): string;
18
+ export declare function isScriptModule(path: string): boolean;
19
+ export declare function normalizeSharedPath(path: string): string;
20
+ export declare function getSharedModuleInfo(path: string): SharedModuleInfo;
21
+ export declare function toKebabCase(name: string): string;
22
+ export declare function writeGeneratedFile({ content, path }: GeneratedFileParams): Promise<void>;
23
+ export declare function removeGeneratedFile({ path, stopPath }: RemoveGeneratedFileParams): Promise<void>;
@@ -0,0 +1,67 @@
1
+ import { mkdir, readFile, readdir, rm, writeFile } from "fs/promises";
2
+ import { dirname, join, parse, relative } from "path";
3
+ const scriptModuleExtensions = [
4
+ ".ts",
5
+ ".tsx",
6
+ ".js",
7
+ ".jsx"
8
+ ];
9
+ function normalizePathSeparator(path) {
10
+ return path.replace(/\\/g, "/");
11
+ }
12
+ function isScriptModule(path) {
13
+ const { ext } = parse(path);
14
+ return scriptModuleExtensions.includes(ext);
15
+ }
16
+ function normalizeSharedPath(path) {
17
+ return normalizePathSeparator(relative("shared", path));
18
+ }
19
+ function getSharedModuleInfo(path) {
20
+ const relativePath = normalizeSharedPath(path);
21
+ const { dir, name, ext } = parse(relativePath);
22
+ return {
23
+ dir,
24
+ ext,
25
+ importPath: normalizePathSeparator(join(dir, name)),
26
+ name,
27
+ relativePath
28
+ };
29
+ }
30
+ function toKebabCase(name) {
31
+ return name.replace(/([a-z0-9])([A-Z])/g, "$1-$2").replace(/([A-Z])([A-Z][a-z])/g, "$1-$2").toLowerCase();
32
+ }
33
+ async function writeGeneratedFile({ content, path }) {
34
+ try {
35
+ const current = await readFile(path, "utf-8");
36
+ if (current === content) return;
37
+ } catch (error) {}
38
+ await mkdir(dirname(path), {
39
+ recursive: true
40
+ });
41
+ await writeFile(path, content);
42
+ }
43
+ async function removeGeneratedFile({ path, stopPath }) {
44
+ await rm(path, {
45
+ force: true,
46
+ recursive: true
47
+ });
48
+ if (!stopPath) return;
49
+ await removeEmptyDirectories(dirname(path), stopPath);
50
+ }
51
+ async function removeEmptyDirectories(path, stopPath) {
52
+ let currentPath = path;
53
+ while(currentPath !== stopPath && "." !== currentPath && currentPath !== dirname(currentPath)){
54
+ try {
55
+ const content = await readdir(currentPath);
56
+ if (content.length > 0) return;
57
+ } catch (error) {
58
+ return;
59
+ }
60
+ await rm(currentPath, {
61
+ force: true,
62
+ recursive: true
63
+ });
64
+ currentPath = dirname(currentPath);
65
+ }
66
+ }
67
+ export { getSharedModuleInfo, isScriptModule, normalizePathSeparator, normalizeSharedPath, removeGeneratedFile, scriptModuleExtensions, toKebabCase, writeGeneratedFile };
@@ -0,0 +1,3 @@
1
+ export declare function syncSharedArtifacts(path: string): Promise<void>;
2
+ export declare function removeSharedArtifacts(path: string): Promise<void>;
3
+ export declare function removeSharedArtifactDirectory(path: string): Promise<void>;
@@ -0,0 +1,28 @@
1
+ import { join } from "path";
2
+ import { createAction } from "./createAction.js";
3
+ import { createRoute } from "./createRoute.js";
4
+ import { getSharedModuleInfo, isScriptModule, normalizeSharedPath, removeGeneratedFile } from "./sharedArtifact.js";
5
+ async function syncSharedArtifacts(path) {
6
+ const info = getSharedModuleInfo(path);
7
+ if (!isScriptModule(info.relativePath)) return;
8
+ await createAction(path);
9
+ await createRoute(path);
10
+ }
11
+ async function removeSharedArtifacts(path) {
12
+ const info = getSharedModuleInfo(path);
13
+ if (!isScriptModule(info.relativePath)) return;
14
+ await removeGeneratedFile({
15
+ path: join("actions", info.relativePath),
16
+ stopPath: "actions"
17
+ });
18
+ await createRoute();
19
+ }
20
+ async function removeSharedArtifactDirectory(path) {
21
+ const relativePath = normalizeSharedPath(path);
22
+ await removeGeneratedFile({
23
+ path: join("actions", relativePath),
24
+ stopPath: "actions"
25
+ });
26
+ await createRoute();
27
+ }
28
+ export { removeSharedArtifactDirectory, removeSharedArtifacts, syncSharedArtifacts };
@@ -1,26 +1,10 @@
1
- import { rm } from "fs/promises";
2
- import { join, relative } from "path";
3
1
  import { watch } from "chokidar";
4
- import { createAction } from "./createAction.js";
2
+ import { removeSharedArtifactDirectory, removeSharedArtifacts, syncSharedArtifacts } from "./syncSharedArtifacts.js";
5
3
  const watcher = watch("shared", {
6
4
  awaitWriteFinish: true,
7
5
  persistent: true
8
6
  });
9
- watcher.on("add", createAction);
10
- watcher.on("change", createAction);
11
- watcher.on("unlink", async (path)=>{
12
- path = relative("shared", path).replace(/\\/g, "/");
13
- const actionPath = join("actions", path);
14
- await rm(actionPath, {
15
- recursive: true,
16
- force: true
17
- });
18
- });
19
- watcher.on("unlinkDir", async (path)=>{
20
- path = relative("shared", path).replace(/\\/g, "/");
21
- const actionPath = join("actions", path);
22
- await rm(actionPath, {
23
- recursive: true,
24
- force: true
25
- });
26
- });
7
+ watcher.on("add", syncSharedArtifacts);
8
+ watcher.on("change", syncSharedArtifacts);
9
+ watcher.on("unlink", removeSharedArtifacts);
10
+ watcher.on("unlinkDir", removeSharedArtifactDirectory);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sdnext",
3
- "version": "0.0.24",
3
+ "version": "0.0.26",
4
4
  "description": "",
5
5
  "type": "module",
6
6
  "exports": {
package/src/index.ts CHANGED
@@ -1,7 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import { readFileSync } from "fs"
3
+ import { readFile } from "fs/promises"
4
4
  import { join } from "path"
5
+ import { fileURLToPath } from "url"
5
6
 
6
7
  import { Command } from "commander"
7
8
 
@@ -11,11 +12,11 @@ import { hook } from "./utils/hook"
11
12
 
12
13
  const program = new Command()
13
14
 
14
- const path = process.platform === "win32" ? import.meta.resolve("../").replace(/^file:\/\/\//, "") : import.meta.resolve("../").replace(/^file:\/\//, "")
15
+ const path = fileURLToPath(new URL("../", import.meta.url))
15
16
 
16
- const packgeJson = JSON.parse(readFileSync(join(path, "package.json"), "utf-8"))
17
+ const packageJson = JSON.parse(await readFile(join(path, "package.json"), "utf-8"))
17
18
 
18
- program.name("soda next").version(packgeJson.version)
19
+ program.name("soda next").version(packageJson.version)
19
20
 
20
21
  program.command("build").allowUnknownOption(true).allowExcessArguments(true).action(build)
21
22
 
@@ -1,11 +1,10 @@
1
- import { spawn } from "child_process"
2
1
  import { readdir, stat } from "fs/promises"
3
2
  import { join } from "path"
4
3
 
5
4
  import { Command } from "commander"
6
5
 
7
- import { createAction } from "./createAction"
8
- import { excludeActions } from "./excludeActions"
6
+ import { runCommand } from "./runCommand"
7
+ import { syncSharedArtifacts } from "./syncSharedArtifacts"
9
8
 
10
9
  export async function buildFolder(dir: string) {
11
10
  const content = await readdir(dir)
@@ -15,19 +14,14 @@ export async function buildFolder(dir: string) {
15
14
  const stats = await stat(path)
16
15
 
17
16
  if (stats.isDirectory()) await buildFolder(path)
18
- else await createAction(path)
17
+ else await syncSharedArtifacts(path)
19
18
  }
20
19
  }
21
20
 
22
21
  export async function build(options: Record<string, string>, { args }: Command) {
23
- await excludeActions()
24
-
25
22
  await buildFolder("shared")
26
23
 
27
24
  if (args.length === 0) return
28
25
 
29
- spawn(args.join(" "), {
30
- stdio: "inherit",
31
- shell: true,
32
- })
26
+ process.exitCode = await runCommand({ args })
33
27
  }
@@ -1,28 +1,21 @@
1
- import { mkdir, readFile, writeFile } from "fs/promises"
2
- import { join, parse, relative } from "path"
1
+ import { join } from "path"
2
+
3
+ import { getSharedModuleInfo, isScriptModule, writeGeneratedFile } from "./sharedArtifact"
3
4
 
4
5
  export async function createAction(path: string) {
5
- path = relative("shared", path).replace(/\\/g, "/")
6
- const { dir, name, ext } = parse(path)
7
- if (ext !== ".ts" && ext !== ".tsx" && ext !== ".js" && ext !== ".jsx") return
6
+ const info = getSharedModuleInfo(path)
7
+
8
+ if (!isScriptModule(info.relativePath)) return
8
9
 
9
- const content = `"use server"
10
+ await writeGeneratedFile({
11
+ path: join("actions", info.relativePath),
12
+ content: `"use server"
10
13
 
11
14
  import { createResponseFn } from "@/server/createResponseFn"
12
15
 
13
- import { ${name} } from "@/shared/${join(dir, name)}"
16
+ import { ${info.name} } from "@/shared/${info.importPath}"
14
17
 
15
- export const ${name}Action = createResponseFn(${name})
18
+ export const ${info.name}Action = createResponseFn(${info.name})
16
19
  `
17
-
18
- const actionPath = join("actions", path)
19
-
20
- try {
21
- const current = await readFile(actionPath, "utf-8")
22
- if (current === content) return
23
- } catch (error) {}
24
-
25
- await mkdir(join("actions", dir), { recursive: true })
26
-
27
- await writeFile(actionPath, content)
20
+ })
28
21
  }