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.
package/README.md CHANGED
@@ -1,3 +1,113 @@
1
1
  # sdnext
2
2
 
3
- to do.
3
+ `sdnext` 是一个面向 `Next.js` 项目的轻量生成工具,用来基于 `shared/**` 自动生成 `actions/**`、`app/api/actions/**/route.ts`,以及基于 `actions/**` 生成 `hooks/**`。
4
+
5
+ ## 约定
6
+
7
+ ### shared -> action
8
+
9
+ `shared/addUser.ts`
10
+
11
+ ```ts
12
+ export async function addUser() {}
13
+ ```
14
+
15
+ 会生成:
16
+
17
+ `actions/addUser.ts`
18
+
19
+ ```ts
20
+ "use server"
21
+
22
+ import { createResponseFn } from "@/server/createResponseFn"
23
+
24
+ import { addUser } from "@/shared/addUser"
25
+
26
+ export const addUserAction = createResponseFn(addUser)
27
+ ```
28
+
29
+ ### shared -> route
30
+
31
+ 如果源函数显式声明了 `route` 元数据:
32
+
33
+ ```ts
34
+ export async function addUser() {}
35
+
36
+ addUser.route = true
37
+ ```
38
+
39
+ 或:
40
+
41
+ ```ts
42
+ export async function addUser() {}
43
+
44
+ addUser.route = {}
45
+ ```
46
+
47
+ 会额外生成:
48
+
49
+ `app/api/actions/add-user/route.ts`
50
+
51
+ ```ts
52
+ import { createRoute } from "@/server/createResponseFn"
53
+
54
+ import { addUser } from "@/shared/addUser"
55
+
56
+ export const { POST } = createRoute(addUser)
57
+ ```
58
+
59
+ 默认情况下,`fn.route` 视为 `false`,不会生成 route。
60
+
61
+ ### actions -> hook
62
+
63
+ 执行 `sdnext hook` 后,会根据 `actions/**` 生成 `hooks/**`。
64
+
65
+ 命名规则:
66
+
67
+ - `getUser` 默认识别为 `get`
68
+ - `queryUser` 默认识别为 `query`
69
+ - 其他函数默认识别为 `mutation`
70
+
71
+ ## 命令
72
+
73
+ ### build
74
+
75
+ ```bash
76
+ sdnext build next build
77
+ ```
78
+
79
+ 执行顺序:
80
+
81
+ 1. 同步生成 `actions/**`
82
+ 2. 根据 `fn.route` 同步生成 `app/api/actions/**`
83
+ 3. 再执行后续命令
84
+
85
+ ### dev
86
+
87
+ ```bash
88
+ sdnext dev next dev
89
+ ```
90
+
91
+ 行为与 `build` 类似,但会额外监听 `shared/**` 的新增、修改、删除,并实时同步生成物。
92
+
93
+ ### hook
94
+
95
+ ```bash
96
+ sdnext hook
97
+ ```
98
+
99
+ 扫描 `actions/**` 并交互式生成或覆盖 `hooks/**`。
100
+
101
+ ## 路径映射
102
+
103
+ - `shared/addUser.ts` -> `actions/addUser.ts`
104
+ - `shared/addUser.ts` -> `app/api/actions/add-user/route.ts`
105
+ - `shared/admin/addUser.ts` -> `actions/admin/addUser.ts`
106
+ - `shared/admin/addUser.ts` -> `app/api/actions/admin/add-user/route.ts`
107
+
108
+ ## 说明
109
+
110
+ - 只处理 `.ts`、`.tsx`、`.js`、`.jsx`
111
+ - 生成文件会做幂等比较,内容未变化时不会重复写入
112
+ - 删除 `shared` 文件或移除 `fn.route` 后,对应生成物会自动清理
113
+ - `sdnext build` 和 `sdnext dev` 会自动把 `actions/**` 与 `app/api/actions/**` 写入 `.vscode/settings.json` 的 `files.exclude`
package/dist/index.js CHANGED
@@ -1,15 +1,146 @@
1
1
  #!/usr/bin/env node
2
- import { readFileSync } from "fs";
3
- import { join } from "path";
4
- import { Command } from "commander";
5
- import { build } from "./utils/build.js";
6
- import { dev } from "./utils/dev.js";
7
- import { hook } from "./utils/hook.js";
8
- const program = new Command();
9
- const path = "win32" === process.platform ? import.meta.resolve("../").replace(/^file:\/\/\//, "") : import.meta.resolve("../").replace(/^file:\/\//, "");
10
- const packgeJson = JSON.parse(readFileSync(join(path, "package.json"), "utf-8"));
11
- program.name("soda next").version(packgeJson.version);
12
- program.command("build").allowUnknownOption(true).allowExcessArguments(true).action(build);
13
- program.command("dev").allowUnknownOption(true).allowExcessArguments(true).action(dev);
14
- program.command("hook").action(hook);
15
- program.parse();
2
+ import * as __WEBPACK_EXTERNAL_MODULE__utils_build_js_f9ba07bf__ from "./utils/build.js";
3
+ import * as __WEBPACK_EXTERNAL_MODULE__utils_dev_js_df994271__ from "./utils/dev.js";
4
+ import * as __WEBPACK_EXTERNAL_MODULE__utils_hook_js_8cbfab27__ from "./utils/hook.js";
5
+ import * as __WEBPACK_EXTERNAL_MODULE_commander__ from "commander";
6
+ import * as __WEBPACK_EXTERNAL_MODULE_fs_promises_400951f8__ from "fs/promises";
7
+ import * as __WEBPACK_EXTERNAL_MODULE_path__ from "path";
8
+ import * as __WEBPACK_EXTERNAL_MODULE_url__ from "url";
9
+ var __webpack_modules__ = {
10
+ "./src/index.ts": function(module, __webpack_exports__, __webpack_require__) {
11
+ __webpack_require__.a(module, async function(__webpack_handle_async_dependencies__, __webpack_async_result__) {
12
+ try {
13
+ __webpack_require__.r(__webpack_exports__);
14
+ var fs_promises__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__("fs/promises");
15
+ var path__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__("path");
16
+ var url__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__("url");
17
+ var commander__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__("commander");
18
+ var _utils_build__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__("./utils/build");
19
+ var _utils_dev__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__("./utils/dev");
20
+ var _utils_hook__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__("./utils/hook");
21
+ const program = new commander__WEBPACK_IMPORTED_MODULE_3__.Command();
22
+ const path = (0, url__WEBPACK_IMPORTED_MODULE_2__.fileURLToPath)(new URL("../", import.meta.url));
23
+ const packageJson = JSON.parse(await (0, fs_promises__WEBPACK_IMPORTED_MODULE_0__.readFile)((0, path__WEBPACK_IMPORTED_MODULE_1__.join)(path, "package.json"), "utf-8"));
24
+ program.name("soda next").version(packageJson.version);
25
+ program.command("build").allowUnknownOption(true).allowExcessArguments(true).action(_utils_build__WEBPACK_IMPORTED_MODULE_4__.build);
26
+ program.command("dev").allowUnknownOption(true).allowExcessArguments(true).action(_utils_dev__WEBPACK_IMPORTED_MODULE_5__.dev);
27
+ program.command("hook").action(_utils_hook__WEBPACK_IMPORTED_MODULE_6__.hook);
28
+ program.parse();
29
+ __webpack_async_result__();
30
+ } catch (e) {
31
+ __webpack_async_result__(e);
32
+ }
33
+ }, 1);
34
+ },
35
+ "./utils/build": function(module) {
36
+ module.exports = __WEBPACK_EXTERNAL_MODULE__utils_build_js_f9ba07bf__;
37
+ },
38
+ "./utils/dev": function(module) {
39
+ module.exports = __WEBPACK_EXTERNAL_MODULE__utils_dev_js_df994271__;
40
+ },
41
+ "./utils/hook": function(module) {
42
+ module.exports = __WEBPACK_EXTERNAL_MODULE__utils_hook_js_8cbfab27__;
43
+ },
44
+ commander: function(module) {
45
+ module.exports = __WEBPACK_EXTERNAL_MODULE_commander__;
46
+ },
47
+ "fs/promises": function(module) {
48
+ module.exports = __WEBPACK_EXTERNAL_MODULE_fs_promises_400951f8__;
49
+ },
50
+ path: function(module) {
51
+ module.exports = __WEBPACK_EXTERNAL_MODULE_path__;
52
+ },
53
+ url: function(module) {
54
+ module.exports = __WEBPACK_EXTERNAL_MODULE_url__;
55
+ }
56
+ };
57
+ var __webpack_module_cache__ = {};
58
+ function __webpack_require__(moduleId) {
59
+ var cachedModule = __webpack_module_cache__[moduleId];
60
+ if (void 0 !== cachedModule) return cachedModule.exports;
61
+ var module = __webpack_module_cache__[moduleId] = {
62
+ exports: {}
63
+ };
64
+ __webpack_modules__[moduleId](module, module.exports, __webpack_require__);
65
+ return module.exports;
66
+ }
67
+ (()=>{
68
+ var webpackQueues = "function" == typeof Symbol ? Symbol("webpack queues") : "__webpack_queues__";
69
+ var webpackExports = "function" == typeof Symbol ? Symbol("webpack exports") : "__webpack_exports__";
70
+ var webpackError = "function" == typeof Symbol ? Symbol("webpack error") : "__webpack_error__";
71
+ var resolveQueue = (queue)=>{
72
+ if (queue && queue.d < 1) {
73
+ queue.d = 1;
74
+ queue.forEach((fn)=>fn.r--);
75
+ queue.forEach((fn)=>fn.r-- ? fn.r++ : fn());
76
+ }
77
+ };
78
+ var wrapDeps = (deps)=>deps.map((dep)=>{
79
+ if (null !== dep && "object" == typeof dep) {
80
+ if (dep[webpackQueues]) return dep;
81
+ if (dep.then) {
82
+ var queue = [];
83
+ queue.d = 0;
84
+ dep.then((r)=>{
85
+ obj[webpackExports] = r;
86
+ resolveQueue(queue);
87
+ }, (e)=>{
88
+ obj[webpackError] = e;
89
+ resolveQueue(queue);
90
+ });
91
+ var obj = {};
92
+ obj[webpackQueues] = (fn)=>fn(queue);
93
+ return obj;
94
+ }
95
+ }
96
+ var ret = {};
97
+ ret[webpackQueues] = function() {};
98
+ ret[webpackExports] = dep;
99
+ return ret;
100
+ });
101
+ __webpack_require__.a = (module, body, hasAwait)=>{
102
+ var queue;
103
+ hasAwait && ((queue = []).d = -1);
104
+ var depQueues = new Set();
105
+ var exports = module.exports;
106
+ var currentDeps;
107
+ var outerResolve;
108
+ var reject;
109
+ var promise = new Promise((resolve, rej)=>{
110
+ reject = rej;
111
+ outerResolve = resolve;
112
+ });
113
+ promise[webpackExports] = exports;
114
+ promise[webpackQueues] = (fn)=>{
115
+ queue && fn(queue), depQueues.forEach(fn), promise["catch"](function() {});
116
+ };
117
+ module.exports = promise;
118
+ body((deps)=>{
119
+ currentDeps = wrapDeps(deps);
120
+ var fn;
121
+ var getResult = ()=>currentDeps.map((d)=>{
122
+ if (d[webpackError]) throw d[webpackError];
123
+ return d[webpackExports];
124
+ });
125
+ var promise = new Promise((resolve)=>{
126
+ fn = ()=>resolve(getResult);
127
+ fn.r = 0;
128
+ var fnQueue = (q)=>q !== queue && !depQueues.has(q) && (depQueues.add(q), q && !q.d && (fn.r++, q.push(fn)));
129
+ currentDeps.map((dep)=>dep[webpackQueues](fnQueue));
130
+ });
131
+ return fn.r ? promise : getResult();
132
+ }, (err)=>(err ? reject(promise[webpackError] = err) : outerResolve(exports), resolveQueue(queue)));
133
+ queue && queue.d < 0 && (queue.d = 0);
134
+ };
135
+ })();
136
+ (()=>{
137
+ __webpack_require__.r = (exports)=>{
138
+ if ('undefined' != typeof Symbol && Symbol.toStringTag) Object.defineProperty(exports, Symbol.toStringTag, {
139
+ value: 'Module'
140
+ });
141
+ Object.defineProperty(exports, '__esModule', {
142
+ value: true
143
+ });
144
+ };
145
+ })();
146
+ __webpack_require__("./src/index.ts");
@@ -1,24 +1,21 @@
1
- import { spawn } from "child_process";
2
1
  import { readdir, stat } from "fs/promises";
3
2
  import { join } from "path";
4
- import { createAction } from "./createAction.js";
5
- import { excludeActions } from "./excludeActions.js";
3
+ import { runCommand } from "./runCommand.js";
4
+ import { syncSharedArtifacts } from "./syncSharedArtifacts.js";
6
5
  async function buildFolder(dir) {
7
6
  const content = await readdir(dir);
8
7
  for (const item of content){
9
8
  const path = join(dir, item);
10
9
  const stats = await stat(path);
11
10
  if (stats.isDirectory()) await buildFolder(path);
12
- else await createAction(path);
11
+ else await syncSharedArtifacts(path);
13
12
  }
14
13
  }
15
14
  async function build(options, { args }) {
16
- await excludeActions();
17
15
  await buildFolder("shared");
18
16
  if (0 === args.length) return;
19
- spawn(args.join(" "), {
20
- stdio: "inherit",
21
- shell: true
17
+ process.exitCode = await runCommand({
18
+ args
22
19
  });
23
20
  }
24
21
  export { build, buildFolder };
@@ -1,25 +1,18 @@
1
- import { mkdir, readFile, writeFile } from "fs/promises";
2
- import { join, parse, relative } from "path";
1
+ import { join } from "path";
2
+ import { getSharedModuleInfo, isScriptModule, writeGeneratedFile } from "./sharedArtifact.js";
3
3
  async function createAction(path) {
4
- path = relative("shared", path).replace(/\\/g, "/");
5
- const { dir, name, ext } = parse(path);
6
- if (".ts" !== ext && ".tsx" !== ext && ".js" !== ext && ".jsx" !== ext) return;
7
- const content = `"use server"
4
+ const info = getSharedModuleInfo(path);
5
+ if (!isScriptModule(info.relativePath)) return;
6
+ await writeGeneratedFile({
7
+ path: join("actions", info.relativePath),
8
+ content: `"use server"
8
9
 
9
10
  import { createResponseFn } from "@/server/createResponseFn"
10
11
 
11
- import { ${name} } from "@/shared/${join(dir, name)}"
12
+ import { ${info.name} } from "@/shared/${info.importPath}"
12
13
 
13
- export const ${name}Action = createResponseFn(${name})
14
- `;
15
- const actionPath = join("actions", path);
16
- try {
17
- const current = await readFile(actionPath, "utf-8");
18
- if (current === content) return;
19
- } catch (error) {}
20
- await mkdir(join("actions", dir), {
21
- recursive: true
14
+ export const ${info.name}Action = createResponseFn(${info.name})
15
+ `
22
16
  });
23
- await writeFile(actionPath, content);
24
17
  }
25
18
  export { createAction };
@@ -0,0 +1,9 @@
1
+ export declare function createRoute(path?: string): Promise<void>;
2
+ export interface SharedRouteModule {
3
+ importPath: string;
4
+ name: string;
5
+ }
6
+ export interface GetRouteFileContentParamsItem {
7
+ importPath: string;
8
+ name: string;
9
+ }
@@ -0,0 +1,64 @@
1
+ import { readdir } from "fs/promises";
2
+ import { join } from "path";
3
+ import { getSharedModuleInfo, isScriptModule, writeGeneratedFile } from "./sharedArtifact.js";
4
+ async function createRoute(path) {
5
+ if (path) {
6
+ const info = getSharedModuleInfo(path);
7
+ if (!isScriptModule(info.relativePath)) return;
8
+ }
9
+ const modules = await getSharedModules("shared");
10
+ await writeGeneratedFile({
11
+ path: join("app", "api", "action", "[...action]", "route.ts"),
12
+ content: getRouteFileContent(modules)
13
+ });
14
+ }
15
+ async function getSharedModules(dir) {
16
+ const entries = await readdir(dir, {
17
+ withFileTypes: true
18
+ });
19
+ const modules = [];
20
+ for (const entry of entries){
21
+ const itemPath = join(dir, entry.name);
22
+ if (entry.isDirectory()) {
23
+ modules.push(...await getSharedModules(itemPath));
24
+ continue;
25
+ }
26
+ if (!entry.isFile() || !isScriptModule(entry.name)) continue;
27
+ const info = getSharedModuleInfo(itemPath);
28
+ modules.push({
29
+ importPath: info.importPath,
30
+ name: info.name
31
+ });
32
+ }
33
+ modules.sort((a, b)=>a.importPath.localeCompare(b.importPath));
34
+ return modules;
35
+ }
36
+ function getRouteFileContent(items) {
37
+ const importLines = items.map((item)=>`import { ${item.name} } from "@/shared/${item.importPath}"`).join("\n");
38
+ const registerLines = items.map((item)=>`registerRoute(${item.name})`).join("\n");
39
+ return `import { NextRequest, NextResponse } from "next/server"
40
+
41
+ import { createRouteFn, OriginalResponseFn, RouteBodyType, RouteHandler } from "@/server/createResponseFn"
42
+ ${importLines ? `\n${importLines}\n` : ""}
43
+ const routeMap = new Map<string, RouteHandler>()
44
+
45
+ function registerRoute<TParams extends [arg?: unknown], TData, TPathname extends string, TRouteBodyType extends RouteBodyType = "json">(
46
+ fn: OriginalResponseFn<TParams, TData, TPathname, TRouteBodyType>,
47
+ ) {
48
+ if (!fn.route) return
49
+ const pathname = fn.route.pathname.replace(/(^\\/|\\/$)/g, "")
50
+ if (routeMap.has(pathname)) throw new Error(\`pathname \${pathname} is duplicate\`)
51
+ routeMap.set(pathname, createRouteFn(fn))
52
+ }
53
+
54
+ ${registerLines ? `${registerLines}\n\n` : ""}export function POST(request: NextRequest) {
55
+ const { pathname } = new URL(request.url)
56
+ const routeHandler = routeMap.get(pathname.replace(/(^\\/api\\/action\\/|\\/$)/g, ""))
57
+
58
+ if (!routeHandler) return NextResponse.json({ success: false, data: undefined, message: "Not Found", code: 404 }, { status: 404 })
59
+
60
+ return routeHandler(request)
61
+ }
62
+ `;
63
+ }
64
+ export { createRoute };
package/dist/utils/dev.js CHANGED
@@ -1,19 +1,32 @@
1
1
  import { spawn } from "child_process";
2
+ import { fileURLToPath } from "url";
2
3
  import { buildFolder } from "./build.js";
3
- import { excludeActions } from "./excludeActions.js";
4
+ import { spawnCommand } from "./runCommand.js";
4
5
  async function dev(options, { args }) {
5
- await excludeActions();
6
6
  await buildFolder("shared");
7
7
  if (0 === args.length) return;
8
- const watchPath = import.meta.resolve("./watch.js").replace("win32" === process.platform ? /^file:\/\/\// : /^file:\/\//, "");
8
+ const watchPath = fileURLToPath(new URL("./watch.js", import.meta.url));
9
9
  const child = spawn(process.execPath, [
10
10
  watchPath
11
- ]);
12
- const child2 = spawn(args.join(" "), {
13
- stdio: "inherit",
14
- shell: true
11
+ ], {
12
+ stdio: "inherit"
13
+ });
14
+ const child2 = spawnCommand({
15
+ args
16
+ });
17
+ process.exitCode = await new Promise((resolve, reject)=>{
18
+ let settled = false;
19
+ function onClose(code) {
20
+ if (settled) return;
21
+ settled = true;
22
+ child.kill();
23
+ child2.kill();
24
+ resolve(code);
25
+ }
26
+ child.once("error", reject);
27
+ child2.once("error", reject);
28
+ child.once("close", (code, signal)=>onClose("number" == typeof code ? code : signal ? 1 : 0));
29
+ child2.once("close", (code, signal)=>onClose("number" == typeof code ? code : signal ? 1 : 0));
15
30
  });
16
- child.on("close", ()=>child2.kill());
17
- child2.on("close", ()=>child.kill());
18
31
  }
19
32
  export { dev };
@@ -3,9 +3,19 @@ export type HookType = "get" | "query" | "mutation";
3
3
  export type OperationType = HookType | "skip";
4
4
  export type HookContentMap = Record<HookType, string>;
5
5
  export interface HookData extends HookContentMap {
6
+ hookPath: string;
7
+ mutationPreset: string;
8
+ mutationPresetPath: string;
6
9
  overwrite: boolean;
7
10
  type: HookType;
8
11
  }
12
+ export interface GeneratedFileState {
13
+ content: string;
14
+ overwrite: boolean;
15
+ }
9
16
  export declare function createHook(path: string, hookMap: Record<string, HookData>): Promise<void>;
10
- export declare function createActionFromFolder(): Promise<Record<string, HookData>>;
17
+ export declare function createHookFromFolder(): Promise<Record<string, HookData>>;
11
18
  export declare function hook(options: Record<string, string>, { args }: Command): Promise<void>;
19
+ export interface NodeError {
20
+ code?: string;
21
+ }