sdnext 0.0.23 → 0.0.25
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 +111 -1
- package/dist/index.js +145 -14
- package/dist/utils/build.js +7 -8
- package/dist/utils/createAction.js +10 -17
- package/dist/utils/createRoute.d.ts +5 -0
- package/dist/utils/createRoute.js +31 -0
- package/dist/utils/dev.js +24 -9
- package/dist/utils/excludeGeneratedFiles.d.ts +1 -0
- package/dist/utils/{excludeActions.js → excludeGeneratedFiles.js} +4 -3
- package/dist/utils/hook.d.ts +5 -1
- package/dist/utils/hook.js +42 -35
- package/dist/utils/readSdNextSetting.d.ts +3 -0
- package/dist/utils/readSdNextSetting.js +8 -3
- package/dist/utils/runCommand.d.ts +7 -0
- package/dist/utils/runCommand.js +25 -0
- package/dist/utils/sharedArtifact.d.ts +23 -0
- package/dist/utils/sharedArtifact.js +67 -0
- package/dist/utils/syncSharedArtifacts.d.ts +3 -0
- package/dist/utils/syncSharedArtifacts.js +34 -0
- package/dist/utils/watch.js +5 -21
- package/dist/utils/writeVsCodeSetting.d.ts +7 -1
- package/dist/utils/writeVsCodeSetting.js +4 -0
- package/package.json +2 -2
- package/src/index.ts +5 -4
- package/src/utils/build.ts +6 -9
- package/src/utils/createAction.ts +12 -19
- package/src/utils/createRoute.ts +44 -0
- package/src/utils/dev.ts +26 -9
- package/src/utils/{excludeActions.ts → excludeGeneratedFiles.ts} +2 -1
- package/src/utils/hook.ts +49 -44
- package/src/utils/readSdNextSetting.ts +14 -4
- package/src/utils/runCommand.ts +35 -0
- package/src/utils/sharedArtifact.ts +90 -0
- package/src/utils/syncSharedArtifacts.ts +44 -0
- package/src/utils/watch.ts +5 -16
- package/src/utils/writeVsCodeSetting.ts +20 -4
- package/dist/utils/excludeActions.d.ts +0 -1
package/README.md
CHANGED
|
@@ -1,3 +1,113 @@
|
|
|
1
1
|
# sdnext
|
|
2
2
|
|
|
3
|
-
|
|
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
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import
|
|
6
|
-
import
|
|
7
|
-
import
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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");
|
package/dist/utils/build.js
CHANGED
|
@@ -1,24 +1,23 @@
|
|
|
1
|
-
import { spawn } from "child_process";
|
|
2
1
|
import { readdir, stat } from "fs/promises";
|
|
3
2
|
import { join } from "path";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
3
|
+
import { excludeGeneratedFiles } from "./excludeGeneratedFiles.js";
|
|
4
|
+
import { runCommand } from "./runCommand.js";
|
|
5
|
+
import { syncSharedArtifacts } from "./syncSharedArtifacts.js";
|
|
6
6
|
async function buildFolder(dir) {
|
|
7
7
|
const content = await readdir(dir);
|
|
8
8
|
for (const item of content){
|
|
9
9
|
const path = join(dir, item);
|
|
10
10
|
const stats = await stat(path);
|
|
11
11
|
if (stats.isDirectory()) await buildFolder(path);
|
|
12
|
-
else await
|
|
12
|
+
else await syncSharedArtifacts(path);
|
|
13
13
|
}
|
|
14
14
|
}
|
|
15
15
|
async function build(options, { args }) {
|
|
16
|
-
await
|
|
16
|
+
await excludeGeneratedFiles();
|
|
17
17
|
await buildFolder("shared");
|
|
18
18
|
if (0 === args.length) return;
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
shell: true
|
|
19
|
+
process.exitCode = await runCommand({
|
|
20
|
+
args
|
|
22
21
|
});
|
|
23
22
|
}
|
|
24
23
|
export { build, buildFolder };
|
|
@@ -1,25 +1,18 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { join } from "path";
|
|
2
|
+
import { getSharedModuleInfo, isScriptModule, writeGeneratedFile } from "./sharedArtifact.js";
|
|
3
3
|
async function createAction(path) {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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/${
|
|
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,31 @@
|
|
|
1
|
+
import { readFile } from "fs/promises";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
import { getSharedModuleInfo, isScriptModule, removeGeneratedFile, toKebabCase, writeGeneratedFile } from "./sharedArtifact.js";
|
|
4
|
+
async function createRoute(path) {
|
|
5
|
+
const info = getSharedModuleInfo(path);
|
|
6
|
+
if (!isScriptModule(info.relativePath)) return;
|
|
7
|
+
const routeDirPath = join("app", "api", "actions", info.dir, toKebabCase(info.name));
|
|
8
|
+
const routePath = join(routeDirPath, "route.ts");
|
|
9
|
+
const content = await readFile(join("shared", info.relativePath), "utf-8");
|
|
10
|
+
if (!isRouteEnabled({
|
|
11
|
+
content,
|
|
12
|
+
name: info.name
|
|
13
|
+
})) return void await removeGeneratedFile({
|
|
14
|
+
path: routeDirPath,
|
|
15
|
+
stopPath: join("app", "api", "actions")
|
|
16
|
+
});
|
|
17
|
+
await writeGeneratedFile({
|
|
18
|
+
path: routePath,
|
|
19
|
+
content: `import { createRoute } from "@/server/createResponseFn"
|
|
20
|
+
|
|
21
|
+
import { ${info.name} } from "@/shared/${info.importPath}"
|
|
22
|
+
|
|
23
|
+
export const { POST } = createRoute(${info.name})
|
|
24
|
+
`
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
function isRouteEnabled({ content, name }) {
|
|
28
|
+
const routeRegExp = new RegExp(`\\b${name}\\.route\\s*=\\s*(true\\b|\\{)`);
|
|
29
|
+
return routeRegExp.test(content);
|
|
30
|
+
}
|
|
31
|
+
export { createRoute };
|
package/dist/utils/dev.js
CHANGED
|
@@ -1,19 +1,34 @@
|
|
|
1
1
|
import { spawn } from "child_process";
|
|
2
|
+
import { fileURLToPath } from "url";
|
|
2
3
|
import { buildFolder } from "./build.js";
|
|
3
|
-
import {
|
|
4
|
+
import { excludeGeneratedFiles } from "./excludeGeneratedFiles.js";
|
|
5
|
+
import { spawnCommand } from "./runCommand.js";
|
|
4
6
|
async function dev(options, { args }) {
|
|
5
|
-
await
|
|
7
|
+
await excludeGeneratedFiles();
|
|
6
8
|
await buildFolder("shared");
|
|
7
9
|
if (0 === args.length) return;
|
|
8
|
-
const watchPath =
|
|
10
|
+
const watchPath = fileURLToPath(new URL("./watch.js", import.meta.url));
|
|
9
11
|
const child = spawn(process.execPath, [
|
|
10
12
|
watchPath
|
|
11
|
-
]
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
], {
|
|
14
|
+
stdio: "inherit"
|
|
15
|
+
});
|
|
16
|
+
const child2 = spawnCommand({
|
|
17
|
+
args
|
|
18
|
+
});
|
|
19
|
+
process.exitCode = await new Promise((resolve, reject)=>{
|
|
20
|
+
let settled = false;
|
|
21
|
+
function onClose(code) {
|
|
22
|
+
if (settled) return;
|
|
23
|
+
settled = true;
|
|
24
|
+
child.kill();
|
|
25
|
+
child2.kill();
|
|
26
|
+
resolve(code);
|
|
27
|
+
}
|
|
28
|
+
child.once("error", reject);
|
|
29
|
+
child2.once("error", reject);
|
|
30
|
+
child.once("close", (code, signal)=>onClose("number" == typeof code ? code : signal ? 1 : 0));
|
|
31
|
+
child2.once("close", (code, signal)=>onClose("number" == typeof code ? code : signal ? 1 : 0));
|
|
15
32
|
});
|
|
16
|
-
child.on("close", ()=>child2.kill());
|
|
17
|
-
child2.on("close", ()=>child.kill());
|
|
18
33
|
}
|
|
19
34
|
export { dev };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function excludeGeneratedFiles(): Promise<import("./writeVsCodeSetting").VsCodeSetting>;
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { writeVsCodeSetting } from "./writeVsCodeSetting.js";
|
|
2
|
-
function
|
|
2
|
+
function excludeGeneratedFiles() {
|
|
3
3
|
return writeVsCodeSetting({
|
|
4
4
|
"files.exclude": {
|
|
5
|
-
"actions/**": true
|
|
5
|
+
"actions/**": true,
|
|
6
|
+
"app/api/actions/**": true
|
|
6
7
|
}
|
|
7
8
|
});
|
|
8
9
|
}
|
|
9
|
-
export {
|
|
10
|
+
export { excludeGeneratedFiles };
|
package/dist/utils/hook.d.ts
CHANGED
|
@@ -3,9 +3,13 @@ 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;
|
|
6
7
|
overwrite: boolean;
|
|
7
8
|
type: HookType;
|
|
8
9
|
}
|
|
9
10
|
export declare function createHook(path: string, hookMap: Record<string, HookData>): Promise<void>;
|
|
10
|
-
export declare function
|
|
11
|
+
export declare function createHookFromFolder(): Promise<Record<string, HookData>>;
|
|
11
12
|
export declare function hook(options: Record<string, string>, { args }: Command): Promise<void>;
|
|
13
|
+
export interface NodeError {
|
|
14
|
+
code?: string;
|
|
15
|
+
}
|
package/dist/utils/hook.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import {
|
|
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,29 +20,32 @@ 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("
|
|
23
|
+
if (content.includes("ClientOptional")) return "get";
|
|
23
24
|
if (content.includes("useQuery")) return "query";
|
|
24
25
|
}
|
|
25
26
|
async function createHook(path, hookMap) {
|
|
26
27
|
path = relative("actions", path).replace(/\\/g, "/");
|
|
27
|
-
const { dir, name,
|
|
28
|
-
if (
|
|
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;
|
|
28
|
+
const { dir, name, base } = parse(path);
|
|
29
|
+
if (!isScriptModule(path)) return;
|
|
32
30
|
const upName = name.replace(/^./, (char)=>char.toUpperCase());
|
|
33
31
|
const key = name.replace(/[A-Z]/g, (char)=>`-${char.toLowerCase()}`);
|
|
32
|
+
const actionImportPath = normalizePathSeparator(join(dir, name));
|
|
33
|
+
const hookName = base.replace(/^./, (char)=>`use${char.toUpperCase()}`);
|
|
34
|
+
const hookPath = join("hooks", dir, hookName);
|
|
35
|
+
const clientInputType = `${upName}ClientInput`;
|
|
34
36
|
const mutationHook = `import { useId } from "react"
|
|
35
37
|
|
|
36
38
|
import { useMutation, UseMutationOptions } from "@tanstack/react-query"
|
|
37
39
|
import { createRequestFn } from "deepsea-tools"
|
|
38
40
|
|
|
39
|
-
import { ${name}Action } from "@/actions/${
|
|
41
|
+
import { ${name}Action } from "@/actions/${actionImportPath}"
|
|
40
42
|
|
|
41
43
|
export const ${name}Client = createRequestFn(${name}Action)
|
|
42
44
|
|
|
45
|
+
export type ${clientInputType} = Parameters<typeof ${name}Client> extends [] ? void : Parameters<typeof ${name}Client>[0]
|
|
46
|
+
|
|
43
47
|
export interface Use${upName}Params<TOnMutateResult = unknown> extends Omit<
|
|
44
|
-
UseMutationOptions<Awaited<ReturnType<typeof ${name}Client>>, Error,
|
|
48
|
+
UseMutationOptions<Awaited<ReturnType<typeof ${name}Client>>, Error, ${clientInputType}, TOnMutateResult>,
|
|
45
49
|
"mutationFn"
|
|
46
50
|
> {}
|
|
47
51
|
|
|
@@ -87,11 +91,13 @@ export function use${upName}<TOnMutateResult = unknown>({ onMutate, onSuccess, o
|
|
|
87
91
|
const getHook = `import { createRequestFn, isNonNullable } from "deepsea-tools"
|
|
88
92
|
import { createUseQuery } from "soda-tanstack-query"
|
|
89
93
|
|
|
90
|
-
import { ${name}Action } from "@/actions/${
|
|
94
|
+
import { ${name}Action } from "@/actions/${actionImportPath}"
|
|
91
95
|
|
|
92
96
|
export const ${name}Client = createRequestFn(${name}Action)
|
|
93
97
|
|
|
94
|
-
export
|
|
98
|
+
export type ${clientInputType} = Parameters<typeof ${name}Client> extends [] ? undefined : Parameters<typeof ${name}Client>[0]
|
|
99
|
+
|
|
100
|
+
export function ${name}ClientOptional(id?: ${clientInputType} | null) {
|
|
95
101
|
return isNonNullable(id) ? ${name}Client(id) : null
|
|
96
102
|
}
|
|
97
103
|
|
|
@@ -103,7 +109,7 @@ export const use${upName} = createUseQuery({
|
|
|
103
109
|
const queryHook = `import { createRequestFn } from "deepsea-tools"
|
|
104
110
|
import { createUseQuery } from "soda-tanstack-query"
|
|
105
111
|
|
|
106
|
-
import { ${name}Action } from "@/actions/${
|
|
112
|
+
import { ${name}Action } from "@/actions/${actionImportPath}"
|
|
107
113
|
|
|
108
114
|
export const ${name}Client = createRequestFn(${name}Action)
|
|
109
115
|
|
|
@@ -117,8 +123,6 @@ export const use${upName} = createUseQuery({
|
|
|
117
123
|
query: queryHook,
|
|
118
124
|
mutation: mutationHook
|
|
119
125
|
};
|
|
120
|
-
const hookName = base.replace(/^./, (char)=>`use${char.toUpperCase()}`);
|
|
121
|
-
const hookPath = join("hooks", dir, hookName);
|
|
122
126
|
let hookType = getHookTypeFromName(name);
|
|
123
127
|
let overwrite = true;
|
|
124
128
|
try {
|
|
@@ -131,35 +135,41 @@ export const use${upName} = createUseQuery({
|
|
|
131
135
|
overwrite = true;
|
|
132
136
|
}
|
|
133
137
|
hookMap[path] = {
|
|
138
|
+
hookPath,
|
|
134
139
|
overwrite,
|
|
135
140
|
type: hookType,
|
|
136
141
|
...map
|
|
137
142
|
};
|
|
138
143
|
}
|
|
139
|
-
async function
|
|
144
|
+
async function createHookFromFolder() {
|
|
140
145
|
const map = {};
|
|
141
|
-
async function
|
|
146
|
+
async function _createHookFromFolder(dir) {
|
|
142
147
|
const content = await readdir(dir);
|
|
143
148
|
for (const item of content){
|
|
144
149
|
const path = join(dir, item);
|
|
145
150
|
const stats = await stat(path);
|
|
146
|
-
if (stats.isDirectory()) await
|
|
151
|
+
if (stats.isDirectory()) await _createHookFromFolder(path);
|
|
147
152
|
if (stats.isFile()) await createHook(path, map);
|
|
148
153
|
}
|
|
149
154
|
}
|
|
150
|
-
|
|
155
|
+
try {
|
|
156
|
+
await _createHookFromFolder("actions");
|
|
157
|
+
} catch (error) {
|
|
158
|
+
if (isNodeError(error) && "ENOENT" === error.code) return map;
|
|
159
|
+
throw error;
|
|
160
|
+
}
|
|
151
161
|
return map;
|
|
152
162
|
}
|
|
153
163
|
async function hook(options, { args }) {
|
|
154
|
-
const map = await
|
|
164
|
+
const map = await createHookFromFolder();
|
|
155
165
|
const entires = Object.entries(map);
|
|
156
166
|
if (0 === entires.length) return void console.log("All hooks are the latest.");
|
|
157
167
|
const newEntires = entires.filter(([path, { overwrite }])=>overwrite);
|
|
158
168
|
const oldEntires = entires.filter(([path, { overwrite }])=>!overwrite);
|
|
159
169
|
const root = cwd();
|
|
160
170
|
const setting = await getSetting();
|
|
161
|
-
for
|
|
162
|
-
const resolved = join(root,
|
|
171
|
+
for (const [path, { hookPath, overwrite, type, ...map }] of newEntires){
|
|
172
|
+
const resolved = join(root, hookPath);
|
|
163
173
|
const answer = await prompts_select({
|
|
164
174
|
message: path,
|
|
165
175
|
choices: [
|
|
@@ -172,25 +182,22 @@ async function hook(options, { args }) {
|
|
|
172
182
|
});
|
|
173
183
|
setting.hook ??= {};
|
|
174
184
|
setting.hook[resolved] = answer;
|
|
175
|
-
if ("skip"
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
recursive: true
|
|
185
|
+
if ("skip" !== answer) await writeGeneratedFile({
|
|
186
|
+
path: hookPath,
|
|
187
|
+
content: map[answer]
|
|
179
188
|
});
|
|
180
|
-
await writeFile(join("hooks", dir, base.replace(/^./, (char)=>`use${char.toUpperCase()}`)), map[answer]);
|
|
181
189
|
}
|
|
182
190
|
await writeSdNextSetting(setting);
|
|
183
191
|
const overwrites = await prompts_checkbox({
|
|
184
192
|
message: "Please check the hooks you want to overwrite",
|
|
185
193
|
choices: oldEntires.map(([key])=>key)
|
|
186
194
|
});
|
|
187
|
-
for (const [path, { overwrite, type, ...map }] of oldEntires){
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
}
|
|
195
|
+
for (const [path, { hookPath, overwrite, type, ...map }] of oldEntires)if (overwrites.includes(path)) await writeGeneratedFile({
|
|
196
|
+
path: hookPath,
|
|
197
|
+
content: map[type]
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
function isNodeError(error) {
|
|
201
|
+
return "object" == typeof error && null !== error;
|
|
195
202
|
}
|
|
196
|
-
export {
|
|
203
|
+
export { createHook, createHookFromFolder, hook };
|
|
@@ -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
|
-
|
|
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
|
-
|
|
15
|
+
}
|
|
16
|
+
function isNodeError(error) {
|
|
17
|
+
return "object" == typeof error && null !== error;
|
|
13
18
|
}
|
|
14
19
|
export { readSdNextSetting };
|