@sealcode/sealgen 0.1.0
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/.arcconfig +12 -0
- package/.arclint +18 -0
- package/.eslintrc.js +37 -0
- package/.log/ti-10372.log +479 -0
- package/.log/ti-10727.log +479 -0
- package/.log/tsserver.log +3811 -0
- package/.prettierrc +14 -0
- package/@types/add-collection.d.ts +1 -0
- package/@types/add-route.d.ts +1 -0
- package/@types/back/collections/delete-me.d.ts +7 -0
- package/@types/generate-collections.d.ts +1 -0
- package/@types/generate-routes.d.ts +1 -0
- package/@types/index.d.ts +2 -0
- package/@types/templates/collection.d.ts +1 -0
- package/@types/templates/form.d.ts +1 -0
- package/@types/templates/page.d.ts +1 -0
- package/@types/templates/route-test.d.ts +1 -0
- package/@types/utils/escape-url-params.d.ts +2 -0
- package/@types/utils/exec.d.ts +5 -0
- package/@types/utils/import-path.d.ts +2 -0
- package/@types/utils/question.d.ts +1 -0
- package/@types/utils/walk.d.ts +1 -0
- package/jenkins.sanity.sh +3 -0
- package/lib/add-collection.js +41 -0
- package/lib/add-collection.js.map +1 -0
- package/lib/add-route.js +52 -0
- package/lib/add-route.js.map +1 -0
- package/lib/back/collections/delete-me.js +14 -0
- package/lib/back/collections/delete-me.js.map +1 -0
- package/lib/generate-collections.js +67 -0
- package/lib/generate-collections.js.map +1 -0
- package/lib/generate-routes.js +71 -0
- package/lib/generate-routes.js.map +1 -0
- package/lib/index.js +39 -0
- package/lib/index.js.map +1 -0
- package/lib/templates/collection.js +16 -0
- package/lib/templates/collection.js.map +1 -0
- package/lib/templates/form.js +46 -0
- package/lib/templates/form.js.map +1 -0
- package/lib/templates/page.js +24 -0
- package/lib/templates/page.js.map +1 -0
- package/lib/templates/route-test.js +27 -0
- package/lib/templates/route-test.js.map +1 -0
- package/lib/utils/escape-url-params.js +18 -0
- package/lib/utils/escape-url-params.js.map +1 -0
- package/lib/utils/exec.js +37 -0
- package/lib/utils/exec.js.map +1 -0
- package/lib/utils/import-path.js +24 -0
- package/lib/utils/import-path.js.map +1 -0
- package/lib/utils/question.js +34 -0
- package/lib/utils/question.js.map +1 -0
- package/lib/utils/walk.js +35 -0
- package/lib/utils/walk.js.map +1 -0
- package/package.json +38 -0
- package/src/add-collection.ts +30 -0
- package/src/add-route.ts +49 -0
- package/src/example.test.ts +11 -0
- package/src/generate-collections.ts +72 -0
- package/src/generate-routes.ts +78 -0
- package/src/index.ts +28 -0
- package/src/templates/collection.ts +11 -0
- package/src/templates/form.ts +42 -0
- package/src/templates/page.ts +23 -0
- package/src/templates/route-test.ts +23 -0
- package/src/tsconfig.json +18 -0
- package/src/utils/escape-url-params.ts +13 -0
- package/src/utils/exec.ts +24 -0
- package/src/utils/import-path.ts +25 -0
- package/src/utils/question.ts +20 -0
- package/src/utils/walk.ts +20 -0
- package/test.js +68 -0
- package/tsconfig.json +26 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"import-path.js","sourceRoot":"","sources":["../../src/utils/import-path.ts"],"names":[],"mappings":";;;;;;AAAA,+BAAyC;AACzC,oDAA6B;AAC7B,MAAM,MAAM,GAAG,gBAAO,CAAC,SAAS,CAAC,CAAC;AAElC,SAAgB,UAAU,CACzB,mBAA2B,EAC3B,mBAA2B;IAE3B,IAAI,MAAM,GAAG,eAAQ,CACpB,cAAO,CAAC,MAAM,CAAC,OAAO,CAAC,mBAAmB,CAAC,CAAC,EAC5C,MAAM,CAAC,OAAO,CAAC,mBAAmB,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CACvD,CAAC;IACF,IAAI,MAAM,CAAC,CAAC,CAAC,IAAI,GAAG,EAAE;QACrB,MAAM,GAAG,IAAI,GAAG,MAAM,CAAC;KACvB;IACD,OAAO,MAAM,CAAC;AACf,CAAC;AAZD,gCAYC;AAED,SAAgB,eAAe,CAC9B,mBAA2B;IAE3B,OAAO,UAAU,mBAA2B;QAC3C,OAAO,UAAU,CAAC,mBAAmB,EAAE,mBAAmB,CAAC,CAAC;IAC7D,CAAC,CAAC;AACH,CAAC;AAND,0CAMC"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.question = void 0;
|
|
13
|
+
const readline_1 = require("readline");
|
|
14
|
+
function question(text, validator) {
|
|
15
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
16
|
+
const rl = readline_1.createInterface({
|
|
17
|
+
input: process.stdin,
|
|
18
|
+
output: process.stdout,
|
|
19
|
+
});
|
|
20
|
+
let answer = "";
|
|
21
|
+
while (answer == "" || !validator(answer)) {
|
|
22
|
+
// eslint-disable-next-line no-await-in-loop
|
|
23
|
+
answer = yield new Promise((resolve) => rl.question(text, resolve));
|
|
24
|
+
if (!validator(answer)) {
|
|
25
|
+
// eslint-disable-next-line no-console
|
|
26
|
+
console.log("Try again");
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
rl.close();
|
|
30
|
+
return answer;
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
exports.question = question;
|
|
34
|
+
//# sourceMappingURL=question.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"question.js","sourceRoot":"","sources":["../../src/utils/question.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,uCAA2C;AAE3C,SAAsB,QAAQ,CAAC,IAAY,EAAE,SAAsC;;QAClF,MAAM,EAAE,GAAG,0BAAe,CAAC;YAC1B,KAAK,EAAE,OAAO,CAAC,KAAK;YACpB,MAAM,EAAE,OAAO,CAAC,MAAM;SACtB,CAAC,CAAC;QACH,IAAI,MAAM,GAAG,EAAE,CAAC;QAChB,OAAO,MAAM,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE;YAC1C,4CAA4C;YAC5C,MAAM,GAAG,MAAM,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,EAAE,CAAC,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC;YACpE,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE;gBACvB,sCAAsC;gBACtC,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;aACzB;SACD;QAED,EAAE,CAAC,KAAK,EAAE,CAAC;QACX,OAAO,MAAM,CAAC;IACf,CAAC;CAAA;AAjBD,4BAiBC"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.walk = void 0;
|
|
13
|
+
const fs_1 = require("fs");
|
|
14
|
+
const path_1 = require("path");
|
|
15
|
+
function walk(dir) {
|
|
16
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
17
|
+
const entries = yield fs_1.promises.readdir(dir);
|
|
18
|
+
let ret = [];
|
|
19
|
+
for (const entry of entries) {
|
|
20
|
+
const fullpath = path_1.resolve(dir, entry);
|
|
21
|
+
// eslint-disable-next-line no-await-in-loop
|
|
22
|
+
const info = yield fs_1.promises.stat(fullpath);
|
|
23
|
+
if (info.isDirectory()) {
|
|
24
|
+
// eslint-disable-next-line no-await-in-loop
|
|
25
|
+
ret = [...ret, ...(yield walk(fullpath))];
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
ret = [...ret, fullpath];
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return ret;
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
exports.walk = walk;
|
|
35
|
+
//# sourceMappingURL=walk.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"walk.js","sourceRoot":"","sources":["../../src/utils/walk.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,2BAAoC;AACpC,+BAA+B;AAE/B,SAAsB,IAAI,CAAC,GAAW;;QACrC,MAAM,OAAO,GAAG,MAAM,aAAE,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QACtC,IAAI,GAAG,GAAa,EAAE,CAAC;QACvB,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE;YAC5B,MAAM,QAAQ,GAAG,cAAO,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;YACrC,4CAA4C;YAC5C,MAAM,IAAI,GAAG,MAAM,aAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YAErC,IAAI,IAAI,CAAC,WAAW,EAAE,EAAE;gBACvB,4CAA4C;gBAC5C,GAAG,GAAG,CAAC,GAAG,GAAG,EAAE,GAAG,CAAC,MAAM,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;aAC1C;iBAAM;gBACN,GAAG,GAAG,CAAC,GAAG,GAAG,EAAE,QAAQ,CAAC,CAAC;aACzB;SACD;QACD,OAAO,GAAG,CAAC;IACZ,CAAC;CAAA;AAhBD,oBAgBC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@sealcode/sealgen",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Module to automate adding routes and collections to a sealious application",
|
|
5
|
+
"main": "lib/index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "node test.js",
|
|
8
|
+
"build": "tsc",
|
|
9
|
+
"prepare": "npm run build",
|
|
10
|
+
"test-reports": "npm run build && rm -fr .xunit coverage && npm run test -- --cover --test-report"
|
|
11
|
+
},
|
|
12
|
+
"bin": {
|
|
13
|
+
"sealgen": "./lib/index.js"
|
|
14
|
+
},
|
|
15
|
+
"author": "",
|
|
16
|
+
"license": "ISC",
|
|
17
|
+
"devDependencies": {
|
|
18
|
+
"@types/mocha": "^9.0.0",
|
|
19
|
+
"@typescript-eslint/eslint-plugin": "^4.29.3",
|
|
20
|
+
"@typescript-eslint/parser": "^4.29.3",
|
|
21
|
+
"eslint": "^7.32.0",
|
|
22
|
+
"eslint-config-prettier": "^8.3.0",
|
|
23
|
+
"eslint-plugin-prettier": "^3.4.1",
|
|
24
|
+
"eslint-plugin-with-tsc-error": "^0.0.7",
|
|
25
|
+
"mocha": "^9.1.1",
|
|
26
|
+
"mri": "^1.1.6",
|
|
27
|
+
"nyc": "^15.1.0",
|
|
28
|
+
"prettier": "^2.3.2",
|
|
29
|
+
"source-map-support": "^0.5.19",
|
|
30
|
+
"ts-node": "^10.2.1",
|
|
31
|
+
"typescript": "^4.3.5"
|
|
32
|
+
},
|
|
33
|
+
"types": "./@types/index.d.ts",
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"@sealcode/ts-predicates": "^0.2.1",
|
|
36
|
+
"locreq": "^2.0.2"
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { resolve } from "path";
|
|
2
|
+
import _locreq from "locreq";
|
|
3
|
+
import { promises as fs, existsSync } from "fs";
|
|
4
|
+
import { question } from "./utils/question";
|
|
5
|
+
import { collectionTemplate } from "./templates/collection";
|
|
6
|
+
import { generateCollections } from "./generate-collections";
|
|
7
|
+
const target_locreq = _locreq(process.cwd());
|
|
8
|
+
|
|
9
|
+
export async function addCollection(): Promise<void> {
|
|
10
|
+
const collection_name = await question(
|
|
11
|
+
'What\'s the name of the collection class (e.g. "SecretDocuments", must start with a capital letter): ',
|
|
12
|
+
(s: string) => s.length > 3 && s[0].toUpperCase() == s[0]
|
|
13
|
+
);
|
|
14
|
+
const url = await question(
|
|
15
|
+
"Enter the url-friendly name for the collection (e.g. secret-documents): ",
|
|
16
|
+
(s: string) => !s.includes("/") && !s.includes(" ")
|
|
17
|
+
);
|
|
18
|
+
const file_path = target_locreq.resolve(`src/back/collections/${url}.ts`);
|
|
19
|
+
|
|
20
|
+
if (existsSync(file_path)) {
|
|
21
|
+
// eslint-disable-next-line no-console
|
|
22
|
+
console.error(`ERROR: File ${file_path} already exists.`);
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
await fs.mkdir(resolve(file_path, "../"), { recursive: true });
|
|
26
|
+
await fs.writeFile(file_path, collectionTemplate(collection_name));
|
|
27
|
+
// eslint-disable-next-line no-console
|
|
28
|
+
console.log(`${file_path} created`);
|
|
29
|
+
await generateCollections();
|
|
30
|
+
}
|
package/src/add-route.ts
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { resolve } from "path";
|
|
2
|
+
import { promises as fs, existsSync } from "fs";
|
|
3
|
+
import { question } from "./utils/question";
|
|
4
|
+
import { pageTemplate } from "./templates/page";
|
|
5
|
+
import { formTemplate } from "./templates/form";
|
|
6
|
+
import { testTemplate } from "./templates/route-test";
|
|
7
|
+
import { escape_url_params } from "./utils/escape-url-params";
|
|
8
|
+
|
|
9
|
+
import _locreq from "locreq";
|
|
10
|
+
import { generateRoutes } from "./generate-routes";
|
|
11
|
+
const target_locreq = _locreq(process.cwd());
|
|
12
|
+
|
|
13
|
+
export async function addRoute(): Promise<void> {
|
|
14
|
+
const action = await question(
|
|
15
|
+
'What\'s the name of the action (e.g. "AddUser"): ',
|
|
16
|
+
(s: string) => s.length > 3
|
|
17
|
+
);
|
|
18
|
+
const url = await question(
|
|
19
|
+
"Enter a full absolute answer for the new route (e.g. /admin/users/add): ",
|
|
20
|
+
(s: string) => s.trim()[0] == "/"
|
|
21
|
+
);
|
|
22
|
+
const mode =
|
|
23
|
+
(await question("Is this a page (p) or a form (f)? ", (a: string) =>
|
|
24
|
+
["p", "f"].includes(a.toLowerCase())
|
|
25
|
+
)) == "p"
|
|
26
|
+
? "page"
|
|
27
|
+
: "form";
|
|
28
|
+
const file_path = target_locreq.resolve(
|
|
29
|
+
`src/back/routes/${escape_url_params(url)}.${mode}.ts`
|
|
30
|
+
);
|
|
31
|
+
const test_file_path = target_locreq.resolve(
|
|
32
|
+
`src/back/routes/${escape_url_params(url)}.test.ts`
|
|
33
|
+
);
|
|
34
|
+
const template = mode == "page" ? pageTemplate : formTemplate;
|
|
35
|
+
|
|
36
|
+
if (existsSync(file_path)) {
|
|
37
|
+
// eslint-disable-next-line no-console
|
|
38
|
+
console.error(`ERROR: File ${file_path} already exists.`);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
await fs.mkdir(resolve(file_path, "../"), { recursive: true });
|
|
42
|
+
await fs.writeFile(file_path, template(action, file_path));
|
|
43
|
+
await fs.writeFile(test_file_path, testTemplate(action, file_path));
|
|
44
|
+
// eslint-disable-next-line no-console
|
|
45
|
+
console.log(`${file_path} created`);
|
|
46
|
+
// eslint-disable-next-line no-console
|
|
47
|
+
console.log(`${test_file_path} created`);
|
|
48
|
+
await generateRoutes();
|
|
49
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Example } from "./index";
|
|
2
|
+
import * as assert from "assert";
|
|
3
|
+
|
|
4
|
+
describe("Example", () => {
|
|
5
|
+
describe("example", () => {
|
|
6
|
+
it("should equal 'example'", () => {
|
|
7
|
+
const example = new Example();
|
|
8
|
+
assert.equal(example.example(), "example");
|
|
9
|
+
});
|
|
10
|
+
});
|
|
11
|
+
});
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import _locreq from "locreq";
|
|
2
|
+
import { promises as fs } from "fs";
|
|
3
|
+
import { assertType, predicates } from "@sealcode/ts-predicates";
|
|
4
|
+
|
|
5
|
+
const target_locreq = _locreq(process.cwd());
|
|
6
|
+
|
|
7
|
+
async function extractCollectionClassname(
|
|
8
|
+
full_file_path: string
|
|
9
|
+
): Promise<string> {
|
|
10
|
+
const file_content = await fs.readFile(full_file_path, "utf-8");
|
|
11
|
+
const result = file_content.match(/export default class (\w+)/);
|
|
12
|
+
if (result === null) {
|
|
13
|
+
throw new Error("Missing 'export default class' statement?");
|
|
14
|
+
}
|
|
15
|
+
return assertType(
|
|
16
|
+
result[1],
|
|
17
|
+
predicates.string,
|
|
18
|
+
"Missing export default class?"
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export async function generateCollections(): Promise<void> {
|
|
23
|
+
const collections_dir = target_locreq.resolve("src/back/collections");
|
|
24
|
+
const files = (await fs.readdir(collections_dir)).filter(
|
|
25
|
+
(f) => !f.match(/\.(sub)?test\./) && f != "collections.ts"
|
|
26
|
+
);
|
|
27
|
+
const file_data = await Promise.all(
|
|
28
|
+
files.map(async (file) => {
|
|
29
|
+
const full_path = target_locreq.resolve(
|
|
30
|
+
"src/back/collections/" + file
|
|
31
|
+
);
|
|
32
|
+
const class_name = await extractCollectionClassname(full_path);
|
|
33
|
+
return {
|
|
34
|
+
class_name,
|
|
35
|
+
file,
|
|
36
|
+
full_path,
|
|
37
|
+
urlname: file.replace(".ts", ""),
|
|
38
|
+
};
|
|
39
|
+
})
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
const content = `// DO NOT EDIT! This file is generated automaticaly with 'npm run generate-collections'
|
|
43
|
+
import { App } from "sealious";
|
|
44
|
+
|
|
45
|
+
${file_data
|
|
46
|
+
.map(
|
|
47
|
+
({ class_name, urlname }) =>
|
|
48
|
+
`import _${class_name} from "./${urlname}";`
|
|
49
|
+
)
|
|
50
|
+
.join("\n")}
|
|
51
|
+
|
|
52
|
+
${file_data
|
|
53
|
+
.map(
|
|
54
|
+
({ class_name }) => `export const ${class_name} = new _${class_name}();`
|
|
55
|
+
)
|
|
56
|
+
.join("\n")}
|
|
57
|
+
|
|
58
|
+
export const collections = {
|
|
59
|
+
...App.BaseCollections,
|
|
60
|
+
${file_data
|
|
61
|
+
.map(({ class_name, urlname }) => ` "${urlname}": ${class_name},`)
|
|
62
|
+
.join("\n")}
|
|
63
|
+
};`;
|
|
64
|
+
await fs.writeFile(
|
|
65
|
+
target_locreq.resolve("src/back/collections/collections.ts"),
|
|
66
|
+
content
|
|
67
|
+
);
|
|
68
|
+
// eslint-disable-next-line no-console
|
|
69
|
+
console.log(
|
|
70
|
+
"Successfuly generated new src/back/collections/collections.ts file"
|
|
71
|
+
);
|
|
72
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { promises as fs } from "fs";
|
|
2
|
+
import { relative } from "path";
|
|
3
|
+
import _locreq from "locreq";
|
|
4
|
+
import { walk } from "./utils/walk";
|
|
5
|
+
import { importPath } from "./utils/import-path";
|
|
6
|
+
import { assertType, predicates } from "@sealcode/ts-predicates";
|
|
7
|
+
import { unescape_url_params } from "./utils/escape-url-params";
|
|
8
|
+
|
|
9
|
+
const locreq = _locreq(__dirname);
|
|
10
|
+
|
|
11
|
+
async function extractActionName(full_file_path: string): Promise<string> {
|
|
12
|
+
const file_content = await fs.readFile(full_file_path, "utf-8");
|
|
13
|
+
const result = file_content.match(/export const actionName = "(\w+)"/);
|
|
14
|
+
if (result === null) {
|
|
15
|
+
throw new Error("Missing 'export const actionName' statement?");
|
|
16
|
+
}
|
|
17
|
+
return assertType(
|
|
18
|
+
result[1],
|
|
19
|
+
predicates.string,
|
|
20
|
+
"Missing export default class?"
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export async function generateRoutes(): Promise<void> {
|
|
25
|
+
const files = await Promise.all(
|
|
26
|
+
(
|
|
27
|
+
await walk(locreq.resolve("src/back/routes"))
|
|
28
|
+
)
|
|
29
|
+
.filter((f) => f.endsWith(".page.ts") || f.endsWith(".form.ts"))
|
|
30
|
+
.map(async (fullpath) => ({
|
|
31
|
+
fullpath,
|
|
32
|
+
actionName: await extractActionName(fullpath),
|
|
33
|
+
url: unescape_url_params(
|
|
34
|
+
"/" +
|
|
35
|
+
relative(
|
|
36
|
+
locreq.resolve("src/back/routes"),
|
|
37
|
+
fullpath
|
|
38
|
+
).replace(/\..+/, "")
|
|
39
|
+
),
|
|
40
|
+
}))
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
await fs.writeFile(
|
|
44
|
+
locreq.resolve("src/back/routes/routes.ts"),
|
|
45
|
+
`// DO NOT EDIT! This file is generated automaticaly with npm run generate-routes
|
|
46
|
+
|
|
47
|
+
import Router from "@koa/router";
|
|
48
|
+
import { Middlewares } from "sealious";
|
|
49
|
+
|
|
50
|
+
${files
|
|
51
|
+
.map(
|
|
52
|
+
({ actionName, fullpath }) =>
|
|
53
|
+
`import { default as ${actionName} } from "${importPath(
|
|
54
|
+
locreq.resolve("src/back/routes/routes.ts"),
|
|
55
|
+
fullpath
|
|
56
|
+
)}";`
|
|
57
|
+
)
|
|
58
|
+
.join("\n")}
|
|
59
|
+
|
|
60
|
+
${files
|
|
61
|
+
.map(({ actionName, url }) => `export const ${actionName}URL = "${url}";`)
|
|
62
|
+
.join("\n")}
|
|
63
|
+
|
|
64
|
+
export default function mountAutoRoutes(router: Router) {
|
|
65
|
+
${files
|
|
66
|
+
.map(
|
|
67
|
+
({
|
|
68
|
+
actionName,
|
|
69
|
+
}) => ` router.use(${actionName}URL, Middlewares.extractContext(), Middlewares.parseBody());
|
|
70
|
+
${actionName}.mount(router, ${actionName}URL);`
|
|
71
|
+
)
|
|
72
|
+
.join("\n\n")}
|
|
73
|
+
}
|
|
74
|
+
`
|
|
75
|
+
);
|
|
76
|
+
// eslint-disable-next-line no-console
|
|
77
|
+
console.log("Successfuly generated new src/back/routes/routes.ts file");
|
|
78
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { addCollection } from "./add-collection";
|
|
4
|
+
import { addRoute } from "./add-route";
|
|
5
|
+
import { generateCollections } from "./generate-collections";
|
|
6
|
+
import { generateRoutes } from "./generate-routes";
|
|
7
|
+
|
|
8
|
+
const actions: Record<string, () => Promise<void> | undefined> = {
|
|
9
|
+
"add-collection": addCollection,
|
|
10
|
+
"add-route": addRoute,
|
|
11
|
+
"generate-collections": generateCollections,
|
|
12
|
+
"generate-routes": generateRoutes,
|
|
13
|
+
default: async function () {
|
|
14
|
+
console.log("Usage: `sealscript <action>`");
|
|
15
|
+
console.log(
|
|
16
|
+
`Available actions: ${Object.keys(actions)
|
|
17
|
+
.filter((e) => e != "default")
|
|
18
|
+
.join(", ")}`
|
|
19
|
+
);
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
void (async function () {
|
|
24
|
+
const action = process.argv.at(-1);
|
|
25
|
+
actions;
|
|
26
|
+
const fn = actions[action || "default"] || actions.default;
|
|
27
|
+
await fn();
|
|
28
|
+
})();
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export function collectionTemplate(collection_name: string) {
|
|
2
|
+
return `import { Collection, FieldTypes, Policies } from "sealious";
|
|
3
|
+
|
|
4
|
+
export default class ${collection_name} extends Collection {
|
|
5
|
+
fields = {
|
|
6
|
+
content: new FieldTypes.Text(),
|
|
7
|
+
};
|
|
8
|
+
defaultPolicy = new Policies.Public();
|
|
9
|
+
}
|
|
10
|
+
`;
|
|
11
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { curryImportPath } from "../utils/import-path";
|
|
2
|
+
|
|
3
|
+
export const formTemplate = (action_name: string, newfilefullpath: string) => {
|
|
4
|
+
const rel = curryImportPath(newfilefullpath);
|
|
5
|
+
return `import { BaseContext } from "koa";
|
|
6
|
+
import Users from "${rel("src/back/collections/users")}";
|
|
7
|
+
import { FormHeader, SimpleInput } from "${rel("src/back/forms/controls")}";
|
|
8
|
+
import { FormField } from "${rel("src/back/forms/field")}";
|
|
9
|
+
import Form, { FormData } from "${rel("src/back/forms/form")}";
|
|
10
|
+
import { collectionFieldValidator } from "${rel("src/back/forms/validator")}";
|
|
11
|
+
import html from "${rel("src/back/html")}";
|
|
12
|
+
|
|
13
|
+
export const actionName = "${action_name}";
|
|
14
|
+
|
|
15
|
+
export default new(class ${action_name}Form extends Form {
|
|
16
|
+
defaultSuccessMessage = "Pomyślnie utworzono użytkownika";
|
|
17
|
+
|
|
18
|
+
fields = [new FormField("email", true, collectionFieldValidator(Users.fields.email))];
|
|
19
|
+
|
|
20
|
+
controls = [
|
|
21
|
+
new FormHeader("Enter your email address"),
|
|
22
|
+
new SimpleInput("email", { label: "Your email", type: "email" }),
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
26
|
+
async canAccess(_: BaseContext) {
|
|
27
|
+
return { canAccess: true, message: "" };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async onSubmit(ctx: BaseContext, values: Record<"email", string>) {
|
|
31
|
+
await ctx.$app.collections.users.create(ctx.$context, {
|
|
32
|
+
username: values.email,
|
|
33
|
+
...values,
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async render(ctx: BaseContext, data: FormData, path: string) {
|
|
38
|
+
return html(ctx, "${action_name}", await super.render(ctx, data, path));
|
|
39
|
+
}
|
|
40
|
+
})();
|
|
41
|
+
`;
|
|
42
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { importPath } from "../utils/import-path";
|
|
2
|
+
|
|
3
|
+
export const pageTemplate = (
|
|
4
|
+
action_name: string,
|
|
5
|
+
fullpath: string
|
|
6
|
+
) => `import { BaseContext } from "koa";
|
|
7
|
+
import { tempstream } from "tempstream";
|
|
8
|
+
import { Page } from "${importPath(fullpath, "src/back/page/page")}";
|
|
9
|
+
|
|
10
|
+
export const actionName = "${action_name}";
|
|
11
|
+
|
|
12
|
+
export default new (class ${action_name}Page extends Page {
|
|
13
|
+
|
|
14
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
15
|
+
async canAccess(_: BaseContext) {
|
|
16
|
+
return { canAccess: true, message: "" };
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async render(ctx: BaseContext) {
|
|
20
|
+
return tempstream/* HTML */ \`<div></div>\`;
|
|
21
|
+
}
|
|
22
|
+
})();
|
|
23
|
+
`;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { curryImportPath } from "../utils/import-path";
|
|
2
|
+
|
|
3
|
+
export function testTemplate(action_name: string, new_file_fullpath: string) {
|
|
4
|
+
const rel = curryImportPath(new_file_fullpath);
|
|
5
|
+
return `import { withProdApp } from "${rel("src/back/test_utils/with-prod-app")}";
|
|
6
|
+
import { LONG_TEST_TIMEOUT, webhintURL } from "${rel("src/back/test_utils/webhint")}";
|
|
7
|
+
import { ${action_name}URL } from "${rel("src/back/routes/routes")}";
|
|
8
|
+
|
|
9
|
+
describe("${action_name}", () => {
|
|
10
|
+
it("doesn't crash", async function () {
|
|
11
|
+
this.timeout(LONG_TEST_TIMEOUT);
|
|
12
|
+
return withProdApp(async ({ base_url, rest_api }) => {
|
|
13
|
+
await rest_api.get(${action_name}URL);
|
|
14
|
+
await webhintURL(base_url + ${action_name}URL);
|
|
15
|
+
// alternatively you can use webhintHTML for faster but less precise scans
|
|
16
|
+
// or for scanning responses of requests that use some form of authorization:
|
|
17
|
+
// const response = await rest_api.get(${action_name}URL);
|
|
18
|
+
// await webhintHTML(response);
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
`;
|
|
23
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"module": "ESNext",
|
|
4
|
+
"moduleResolution": "node",
|
|
5
|
+
"noImplicitAny": true,
|
|
6
|
+
"noImplicitThis": true,
|
|
7
|
+
"strictNullChecks": true,
|
|
8
|
+
"target": "ES2020",
|
|
9
|
+
"lib": ["es6", "esnext"],
|
|
10
|
+
"outDir": "../../dist/scripts",
|
|
11
|
+
"checkJs": true,
|
|
12
|
+
"allowJs": true,
|
|
13
|
+
"resolveJsonModule": true,
|
|
14
|
+
"sourceMap": true,
|
|
15
|
+
"skipLibCheck": true
|
|
16
|
+
},
|
|
17
|
+
"include": ["./**/*", "./*"]
|
|
18
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export function escape_url_params(url: string): string {
|
|
2
|
+
return url
|
|
3
|
+
.split("/")
|
|
4
|
+
.map((e) => (e.startsWith(":") ? `[${e.slice(1)}]` : e))
|
|
5
|
+
.join("/");
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function unescape_url_params(url: string): string {
|
|
9
|
+
return url
|
|
10
|
+
.split("/")
|
|
11
|
+
.map((e) => (e.startsWith("[") ? `:${e.slice(1, e.length - 1)}` : e))
|
|
12
|
+
.join("/");
|
|
13
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/* eslint-disable no-console */
|
|
2
|
+
import { spawn } from "child_process";
|
|
3
|
+
|
|
4
|
+
export async function exec(
|
|
5
|
+
program: string,
|
|
6
|
+
args: string[]
|
|
7
|
+
): Promise<{ stderr: string; stdout: string; exitCode: number }> {
|
|
8
|
+
return new Promise((resolve, reject) => {
|
|
9
|
+
let stdout = "";
|
|
10
|
+
let stderr = "";
|
|
11
|
+
const process = spawn(program, args);
|
|
12
|
+
process.stdout.on("data", (data: Buffer) => (stdout += data.toString()));
|
|
13
|
+
process.stderr.on("data", (data: Buffer) => (stderr += data.toString()));
|
|
14
|
+
process.on("close", (exitCode) => {
|
|
15
|
+
if (exitCode === 0) {
|
|
16
|
+
resolve({ stdout, stderr, exitCode });
|
|
17
|
+
} else {
|
|
18
|
+
console.error([program, ...args].join(" "));
|
|
19
|
+
console.error(stderr);
|
|
20
|
+
reject({ stdout, stderr, exitCode });
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { relative, dirname } from "path";
|
|
2
|
+
import _locreq from "locreq";
|
|
3
|
+
const locreq = _locreq(__dirname);
|
|
4
|
+
|
|
5
|
+
export function importPath(
|
|
6
|
+
project_module_path: string,
|
|
7
|
+
project_target_path: string
|
|
8
|
+
): string {
|
|
9
|
+
let result = relative(
|
|
10
|
+
dirname(locreq.resolve(project_module_path)),
|
|
11
|
+
locreq.resolve(project_target_path).replace(/.ts$/, "")
|
|
12
|
+
);
|
|
13
|
+
if (result[0] != ".") {
|
|
14
|
+
result = "./" + result;
|
|
15
|
+
}
|
|
16
|
+
return result;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function curryImportPath(
|
|
20
|
+
project_module_path: string
|
|
21
|
+
): (project_target_path: string) => string {
|
|
22
|
+
return function (project_target_path: string) {
|
|
23
|
+
return importPath(project_module_path, project_target_path);
|
|
24
|
+
};
|
|
25
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { createInterface } from "readline";
|
|
2
|
+
|
|
3
|
+
export async function question(text: string, validator: (answer: string) => boolean) {
|
|
4
|
+
const rl = createInterface({
|
|
5
|
+
input: process.stdin,
|
|
6
|
+
output: process.stdout,
|
|
7
|
+
});
|
|
8
|
+
let answer = "";
|
|
9
|
+
while (answer == "" || !validator(answer)) {
|
|
10
|
+
// eslint-disable-next-line no-await-in-loop
|
|
11
|
+
answer = await new Promise((resolve) => rl.question(text, resolve));
|
|
12
|
+
if (!validator(answer)) {
|
|
13
|
+
// eslint-disable-next-line no-console
|
|
14
|
+
console.log("Try again");
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
rl.close();
|
|
19
|
+
return answer;
|
|
20
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { promises as fs } from "fs";
|
|
2
|
+
import { resolve } from "path";
|
|
3
|
+
|
|
4
|
+
export async function walk(dir: string): Promise<string[]> {
|
|
5
|
+
const entries = await fs.readdir(dir);
|
|
6
|
+
let ret: string[] = [];
|
|
7
|
+
for (const entry of entries) {
|
|
8
|
+
const fullpath = resolve(dir, entry);
|
|
9
|
+
// eslint-disable-next-line no-await-in-loop
|
|
10
|
+
const info = await fs.stat(fullpath);
|
|
11
|
+
|
|
12
|
+
if (info.isDirectory()) {
|
|
13
|
+
// eslint-disable-next-line no-await-in-loop
|
|
14
|
+
ret = [...ret, ...(await walk(fullpath))];
|
|
15
|
+
} else {
|
|
16
|
+
ret = [...ret, fullpath];
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return ret;
|
|
20
|
+
}
|