From e5d30b30ba4dd38de764b0e5044f836f04a03706 Mon Sep 17 00:00:00 2001 From: chen Date: Sat, 24 Oct 2020 15:34:43 +0800 Subject: [PATCH] first commit --- .env | 1 + .eslintrc | 17 + .fatherrc.ts | 23 + .gitignore | 11 + docker-compose.yml | 38 + dotenv.js | 1 + jest.config.js | 11 + lerna.json | 19 + package.json | 47 + packages/actions/package.json | 24 + packages/actions/src/__tests__/add.test.ts | 43 + packages/actions/src/__tests__/common.test.ts | 130 +++ packages/actions/src/__tests__/create.test.ts | 55 + .../actions/src/__tests__/destroy.test.ts | 95 ++ packages/actions/src/__tests__/get.test.ts | 111 ++ packages/actions/src/__tests__/index.ts | 75 ++ packages/actions/src/__tests__/list.test.ts | 148 +++ .../actions/src/__tests__/middleware.test.ts | 73 ++ packages/actions/src/__tests__/remove.test.ts | 96 ++ packages/actions/src/__tests__/set.test.ts | 61 ++ .../actions/src/__tests__/tables/comments.ts | 31 + .../actions/src/__tests__/tables/posts.ts | 42 + .../src/__tests__/tables/posts_tags.ts | 12 + .../actions/src/__tests__/tables/profiles.ts | 12 + packages/actions/src/__tests__/tables/tags.ts | 27 + .../actions/src/__tests__/tables/users.ts | 16 + packages/actions/src/__tests__/update.test.ts | 106 ++ packages/actions/src/actions/associate.ts | 206 ++++ packages/actions/src/actions/common.ts | 358 +++++++ packages/actions/src/actions/index.ts | 13 + packages/actions/src/index.ts | 5 + packages/actions/src/middleware.ts | 21 + .../actions/src/middlewares/associated.ts | 59 ++ .../actions/src/middlewares/json-reponse.ts | 21 + packages/api/.gitignore | 4 + packages/api/Dockerfile | 15 + packages/api/docker-compose.yml | 41 + packages/api/example/index.ts | 42 + packages/api/nodemon.json | 6 + packages/api/package.json | 38 + packages/api/pm2.json | 14 + packages/api/src/index.ts | 53 + packages/create-nocobase-app/.local | 1 + packages/create-nocobase-app/README.md | 1 + .../bin/create-nocobase-app.js | 3 + packages/create-nocobase-app/package.json | 16 + .../src/AppGenerator/AppGenerator.ts | 15 + packages/create-nocobase-app/src/app.ts | 0 packages/create-nocobase-app/src/cli.ts | 30 + .../create-nocobase-app/src/fixtures/.gitkeep | 0 .../create-nocobase-app/src/index.test.ts | 19 + packages/create-nocobase-app/src/index.ts | 16 + .../templates/AppGenerator/.editorconfig | 16 + .../templates/AppGenerator/.env | 14 + .../templates/AppGenerator/.fatherrc.ts | 10 + .../templates/AppGenerator/.gitignore.tpl | 20 + .../templates/AppGenerator/.prettierignore | 8 + .../templates/AppGenerator/.prettierrc | 11 + .../templates/AppGenerator/.umirc.ts.tpl | 12 + .../templates/AppGenerator/README.md | 15 + .../templates/AppGenerator/mock/.gitkeep | 0 .../templates/AppGenerator/nodemon.json | 5 + .../templates/AppGenerator/package.json.tpl | 38 + .../templates/AppGenerator/server.js | 7 + .../templates/AppGenerator/src/api/index.ts | 42 + .../AppGenerator/src/pages/index.less | 7 + .../AppGenerator/src/pages/index.tsx | 10 + .../templates/AppGenerator/tsconfig.json | 26 + .../templates/AppGenerator/typings.d.ts | 8 + packages/create-nocobase-app/tsconfig.json | 18 + packages/database/examples/index.ts | 125 +++ .../examples/plugins/db-driven/index.ts | 25 + .../plugins/db-driven/tables/fields.ts | 78 ++ .../plugins/db-driven/tables/tables.ts | 53 + packages/database/examples/tables/bar.js | 26 + packages/database/examples/tables/comments.ts | 19 + packages/database/examples/tables/foo.json | 3 + packages/database/examples/tables/posts.ts | 28 + packages/database/examples/tables/profiles.ts | 23 + packages/database/examples/tables/tags.ts | 15 + packages/database/examples/tables/users.ts | 34 + packages/database/package.json | 23 + .../src/__tests__/associations.test.ts | 694 +++++++++++++ packages/database/src/__tests__/index.ts | 42 + packages/database/src/__tests__/model.test.ts | 970 ++++++++++++++++++ packages/database/src/__tests__/modules/fn.js | 3 + .../database/src/__tests__/modules/fnts.ts | 1 + .../database/src/__tests__/modules/json.json | 3 + .../database/src/__tests__/modules/obj.js | 3 + .../database/src/__tests__/modules/objts.ts | 3 + packages/database/src/__tests__/sync.test.ts | 172 ++++ .../database/src/__tests__/tables.test.ts | 300 ++++++ packages/database/src/__tests__/types.test.ts | 330 ++++++ packages/database/src/__tests__/utils.test.ts | 658 ++++++++++++ packages/database/src/database.ts | 245 +++++ packages/database/src/fields/field-types.ts | 691 +++++++++++++ packages/database/src/fields/index.ts | 124 +++ packages/database/src/fields/option-types.ts | 214 ++++ packages/database/src/index.ts | 9 + packages/database/src/model.ts | 418 ++++++++ packages/database/src/table.ts | 345 +++++++ packages/database/src/utils.ts | 315 ++++++ packages/father-build/.local | 1 + packages/father-build/.npmignore | 4 + packages/father-build/README.md | 4 + packages/father-build/bin/father-build.js | 61 ++ packages/father-build/package.json | 80 ++ packages/father-build/src/babel.ts | 238 +++++ packages/father-build/src/build.test.ts | 54 + packages/father-build/src/build.ts | 238 +++++ .../build/babel-browser-files/.fatherrc.js | 8 + .../expected/es/browser.js | 1 + .../babel-browser-files/expected/es/node.js | 1 + .../build/babel-browser-files/src/browser.js | 2 + .../build/babel-browser-files/src/node.js | 2 + .../build/babel-cjs-lazy/.fatherrc.js | 8 + .../fixtures/build/babel-cjs-lazy/src/foo.js | 3 + .../build/babel-cjs-lazy/src/index.js | 3 + .../.fatherrc.js | 10 + .../expected/es/index.js | 2 + .../p1.js | 20 + .../p2.js | 20 + .../preset.js | 8 + .../src/index.js | 3 + .../build/babel-importLibToEs/.fatherrc.js | 5 + .../babel-importLibToEs/expected/es/index.js | 2 + .../build/babel-importLibToEs/src/index.js | 3 + .../babel-less-to-css-forbid/.fatherrc.js | 6 + .../expected/es/foo.js | 6 + .../expected/es/foo.module.less | 3 + .../expected/es/index.less | 6 + .../build/babel-less-to-css-forbid/src/foo.js | 6 + .../src/foo.module.less | 3 + .../babel-less-to-css-forbid/src/index.less | 6 + .../build/babel-less-to-css/.fatherrc.js | 6 + .../babel-less-to-css/expected/es/foo.js | 6 + .../expected/es/foo.module.css | 3 + .../babel-less-to-css/expected/es/index.css | 3 + .../build/babel-less-to-css/src/foo.js | 6 + .../babel-less-to-css/src/foo.module.less | 3 + .../build/babel-less-to-css/src/index.less | 6 + .../build/babel-node-files/.fatherrc.js | 8 + .../babel-node-files/expected/es/browser.js | 1 + .../babel-node-files/expected/es/node.js | 1 + .../build/babel-node-files/src/browser.js | 2 + .../build/babel-node-files/src/node.js | 2 + .../build/babel-node-tsx-jsx/.fatherrc.js | 5 + .../babel-node-tsx-jsx/expected/es/B.d.ts | 1 + .../build/babel-node-tsx-jsx/expected/es/B.js | 1 + .../build/babel-node-tsx-jsx/expected/es/C.js | 1 + .../babel-node-tsx-jsx/expected/es/node.js | 1 + .../build/babel-node-tsx-jsx/src/B.tsx | 2 + .../build/babel-node-tsx-jsx/src/C.jsx | 2 + .../build/babel-node-tsx-jsx/src/node.js | 2 + .../build/babel-node-tsx-jsx/tsconfig.json | 9 + .../fixtures/build/babel-normal/.fatherrc.js | 5 + .../build/babel-normal/expected/es/foo.js | 3 + .../build/babel-normal/expected/es/index.js | 3 + .../build/babel-normal/expected/es/types.d.ts | 0 .../__test__/__snapshots__/index.test.js.snap | 1 + .../babel-normal/src/__test__/index.test.js | 1 + .../fixtures/build/babel-normal/src/bar.md | 1 + .../fixtures/build/babel-normal/src/bar.mdx | 1 + .../build/babel-normal/src/demos/foo.js | 3 + .../src/fixtures/example/package.json | 1 + .../build/babel-normal/src/foo.e2e.ts | 0 .../fixtures/build/babel-normal/src/foo.js | 4 + .../build/babel-normal/src/foo.spec.ts | 0 .../build/babel-normal/src/foo.test.js | 0 .../build/babel-normal/src/foo.test.jsx | 0 .../build/babel-normal/src/foo.test.ts | 0 .../build/babel-normal/src/foo.test.tsx | 0 .../fixtures/build/babel-normal/src/index.js | 4 + .../build/babel-normal/src/types.d.ts | 0 .../build/babel-runtimeHelpers/.fatherrc.js | 6 + .../babel-runtimeHelpers/expected/es/index.js | 21 + .../build/babel-runtimeHelpers/package.json | 5 + .../build/babel-runtimeHelpers/src/index.js | 9 + .../fixtures/build/babel-syntax/.fatherrc.js | 4 + .../build/babel-syntax/expected/index.esm.js | 84 ++ .../fixtures/build/babel-syntax/package.json | 5 + .../src/fixtures/build/babel-syntax/src/a.js | 4 + .../src/fixtures/build/babel-syntax/src/b.js | 8 + .../fixtures/build/babel-syntax/src/index.js | 32 + .../build/babel-ts-3.7-features/.fatherrc.js | 4 + .../expected/es/index.d.ts | 2 + .../expected/es/index.js | 6 + .../build/babel-ts-3.7-features/src/index.ts | 11 + .../.fatherrc.js | 6 + .../expected/es/index.js | 3 + .../src/index.ts | 7 + .../tsconfig.json | 8 + .../babel-typescript-template/.fatherrc.js | 5 + .../expected/es/index.d.ts | 5 + .../expected/es/index.js | 3 + .../babel-typescript-template/src/index.ts | 7 + .../build/babel-typescript/.fatherrc.js | 5 + .../babel-typescript/expected/es/index.d.ts | 5 + .../babel-typescript/expected/es/index.js | 3 + .../build/babel-typescript/src/index.ts | 7 + .../build/babel-typescript/tsconfig.json | 10 + .../fixtures/build/config-array/.fatherrc.js | 13 + .../build/config-array/expected/index.umd.js | 13 + .../fixtures/build/config-array/src/index.js | 2 + .../fixtures/build/config-array/ui/index.js | 1 + .../lerna-root-config-override/.fatherrc.js | 4 + .../expected/bar/index.js | 3 + .../expected/foo/index.js | 3 + .../lerna-root-config-override/lerna.json | 1 + .../packages/bar/package.json | 1 + .../packages/bar/src/index.js | 2 + .../packages/foo/package.json | 1 + .../packages/foo/src/index.js | 2 + .../build/lerna-root-config/.fatherrc.js | 4 + .../expected/bar/index.esm.js | 1 + .../expected/foo/index.esm.js | 1 + .../build/lerna-root-config/lerna.json | 1 + .../packages/bar/package.json | 1 + .../packages/bar/src/index.js | 2 + .../packages/foo/package.json | 1 + .../packages/foo/src/index.js | 2 + .../build/lerna-root-scope/.fatherrc.js | 3 + .../expected/bar/index.esm.js | 1 + .../expected/foo/index.esm.js | 1 + .../build/lerna-root-scope/lerna.json | 1 + .../packages/bar/.fatherrc.js | 3 + .../packages/bar/package.json | 1 + .../packages/bar/src/index.js | 2 + .../packages/foo/.fatherrc.js | 3 + .../packages/foo/package.json | 1 + .../packages/foo/src/index.js | 2 + .../build/lerna-root-tsconfig/.fatherrc.js | 4 + .../expected/bar/es/index.js | 3 + .../expected/foo/es/index.js | 3 + .../build/lerna-root-tsconfig/lerna.json | 1 + .../packages/bar/package.json | 1 + .../packages/bar/src/index.ts | 4 + .../packages/foo/package.json | 1 + .../packages/foo/src/index.ts | 4 + .../build/lerna-root-tsconfig/tsconfig.json | 12 + .../expected/bar/index.esm.js | 1 + .../expected/foo/index.esm.js | 1 + .../build/lerna-scope-packages/lerna.json | 1 + .../packages/@hoo/bar/.fatherrc.js | 4 + .../packages/@hoo/bar/package.json | 1 + .../packages/@hoo/bar/src/index.js | 2 + .../packages/foo/.fatherrc.js | 4 + .../packages/foo/package.json | 1 + .../packages/foo/src/index.js | 2 + .../build/lerna/expected/bar/index.esm.js | 1 + .../build/lerna/expected/foo/index.esm.js | 1 + .../src/fixtures/build/lerna/lerna.json | 1 + .../build/lerna/packages/bar/.fatherrc.js | 4 + .../build/lerna/packages/bar/package.json | 1 + .../build/lerna/packages/bar/src/index.js | 2 + .../build/lerna/packages/foo/.fatherrc.js | 4 + .../build/lerna/packages/foo/package.json | 1 + .../build/lerna/packages/foo/src/index.js | 2 + .../.fatherrc.js | 6 + .../expected/index.umd.js | 33 + .../package.json | 3 + .../packages/fooo/package.json | 9 + .../packages/fooo/src/index.js | 5 + .../src/index.js | 1 + .../.fatherrc.js | 12 + .../expected/index.umd.js | 26 + .../package.json | 5 + .../src/index.js | 4 + .../build/rollup-commonjs-deps/.fatherrc.js | 6 + .../expected/index.umd.js | 20 + .../build/rollup-commonjs-deps/package.json | 5 + .../build/rollup-commonjs-deps/src/index.js | 4 + .../build/rollup-config-alias/.fatherrc.js | 4 + .../rollup-config-alias/expected/index.esm.js | 6 + .../build/rollup-config-alias/src/index.js | 6 + .../rollup-css-autoprefixer/.fatherrc.js | 10 + .../expected/index.esm.js | 31 + .../build/rollup-css-autoprefixer/src/foo.css | 4 + .../rollup-css-autoprefixer/src/index.js | 3 + .../build/rollup-css-extract/.fatherrc.js | 5 + .../rollup-css-extract/expected/index.esm.css | 2 + .../rollup-css-extract/expected/index.esm.js | 1 + .../build/rollup-css-extract/src/foo.css | 2 + .../build/rollup-css-extract/src/index.js | 3 + .../build/rollup-css-inject/.fatherrc.js | 5 + .../rollup-css-inject/expected/index.esm.js | 3 + .../build/rollup-css-inject/src/foo.css | 2 + .../build/rollup-css-inject/src/index.js | 3 + .../build/rollup-css-modules/.fatherrc.js | 5 + .../rollup-css-modules/expected/index.esm.js | 32 + .../build/rollup-css-modules/src/foo.css | 2 + .../build/rollup-css-modules/src/index.js | 3 + .../fixtures/build/rollup-css/.fatherrc.js | 4 + .../build/rollup-css/expected/index.esm.js | 31 + .../src/fixtures/build/rollup-css/src/foo.css | 2 + .../fixtures/build/rollup-css/src/index.js | 3 + .../build/rollup-esm-minify/.fatherrc.js | 4 + .../rollup-esm-minify/expected/index.esm.js | 1 + .../build/rollup-esm-minify/package.json | 5 + .../build/rollup-esm-minify/src/foo.js | 4 + .../build/rollup-esm-minify/src/index.js | 5 + .../build/rollup-esm-mjs/.fatherrc.js | 4 + .../rollup-esm-mjs/expected/index.esm.js | 6 + .../build/rollup-esm-mjs/expected/index.mjs | 1 + .../build/rollup-esm-mjs/package.json | 5 + .../fixtures/build/rollup-esm-mjs/src/foo.js | 4 + .../build/rollup-esm-mjs/src/index.js | 6 + .../rollup-externalsExclude/.fatherrc.js | 11 + .../expected/index.esm.js | 5 + .../rollup-externalsExclude/expected/index.js | 7 + .../rollup-externalsExclude/package.json | 3 + .../rollup-externalsExclude/src/index.js | 2 + .../.fatherrc.js | 10 + .../expected/index.esm.js | 2 + .../p1.js | 20 + .../p2.js | 20 + .../preset.js | 8 + .../src/index.js | 3 + .../build/rollup-extraExternals/.fatherrc.js | 9 + .../expected/index.esm.js | 5 + .../build/rollup-extraExternals/package.json | 3 + .../build/rollup-extraExternals/src/index.js | 6 + .../rollup-extraRollupPlugins/.fatherrc.js | 10 + .../expected/index.esm.js | 1 + .../rollup-extraRollupPlugins/package.json | 3 + .../rollup-extraRollupPlugins/src/index.js | 2 + .../rollup-import-directory/.fatherrc.js | 4 + .../expected/index.esm.js | 1 + .../rollup-import-directory/src/foo/index.js | 2 + .../rollup-import-directory/src/index.js | 1 + .../build/rollup-importLibToEs/.fatherrc.js | 5 + .../expected/index.esm.js | 5 + .../rollup-importLibToEs/expected/index.js | 7 + .../build/rollup-importLibToEs/src/index.js | 3 + .../fixtures/build/rollup-inject/.fatherrc.js | 7 + .../build/rollup-inject/expected/index.esm.js | 3 + .../fixtures/build/rollup-inject/package.json | 5 + .../fixtures/build/rollup-inject/src/index.js | 1 + .../fixtures/build/rollup-json/.fatherrc.js | 4 + .../build/rollup-json/expected/index.esm.js | 6 + .../fixtures/build/rollup-json/src/index.js | 3 + .../fixtures/build/rollup-json/src/pkg.json | 3 + .../build/rollup-less-modules/.fatherrc.js | 5 + .../rollup-less-modules/expected/index.esm.js | 32 + .../build/rollup-less-modules/src/foo.less | 2 + .../build/rollup-less-modules/src/index.js | 3 + .../rollup-less-node-modules/.fatherrc.js | 4 + .../expected/index.esm.js | 31 + .../rollup-less-node-modules/src/foo.less | 3 + .../rollup-less-node-modules/src/index.js | 3 + .../fixtures/build/rollup-less/.fatherrc.js | 4 + .../build/rollup-less/expected/index.esm.js | 31 + .../fixtures/build/rollup-less/src/foo.less | 2 + .../fixtures/build/rollup-less/src/index.js | 3 + .../build/rollup-multiple-entry/.fatherrc.js | 8 + .../rollup-multiple-entry/expected/bar.esm.js | 1 + .../rollup-multiple-entry/expected/foo.esm.js | 1 + .../build/rollup-multiple-entry/src/bar.js | 2 + .../build/rollup-multiple-entry/src/foo.js | 2 + .../build/rollup-node-resolve/.fatherrc.js | 9 + .../rollup-node-resolve/expected/index.umd.js | 10 + .../build/rollup-node-resolve/package.json | 5 + .../build/rollup-node-resolve/src/index.js | 3 + .../fixtures/build/rollup-normal/.fatherrc.js | 6 + .../build/rollup-normal/expected/index.esm.js | 7 + .../build/rollup-normal/expected/index.js | 9 + .../build/rollup-normal/expected/index.umd.js | 12 + .../rollup-normal/expected/index.umd.min.js | 1 + .../fixtures/build/rollup-normal/package.json | 5 + .../fixtures/build/rollup-normal/src/foo.js | 4 + .../fixtures/build/rollup-normal/src/index.js | 4 + .../.fatherrc.js | 29 + .../expected/dva.bar.js | 8 + .../expected/dva.bar.min.js | 1 + .../expected/dva.esm.js | 1 + .../expected/dva.foo.cjs.js | 3 + .../expected/dva.foo.esm.js | 1 + .../expected/dva.foo.umd.js | 8 + .../expected/dva.foo.umd.min.js | 1 + .../expected/dva.js | 3 + .../expected/dva.umd.js | 8 + .../expected/dva.umd.min.js | 1 + .../src/bar.js | 2 + .../src/foo.js | 2 + .../src/index.js | 2 + .../build/rollup-replace/.fatherrc.js | 7 + .../rollup-replace/expected/index.esm.js | 1 + .../build/rollup-replace/package.json | 3 + .../build/rollup-replace/src/index.js | 2 + .../build/rollup-resolve-jsx/.fatherrc.js | 4 + .../rollup-resolve-jsx/expected/index.esm.js | 11 + .../build/rollup-resolve-jsx/package.json | 5 + .../build/rollup-resolve-jsx/src/Foo.jsx | 2 + .../build/rollup-resolve-jsx/src/index.js | 3 + .../build/rollup-runtimeHelpers/.fatherrc.js | 6 + .../expected/index.esm.js | 17 + .../rollup-runtimeHelpers/expected/index.js | 38 + .../build/rollup-runtimeHelpers/package.json | 5 + .../build/rollup-runtimeHelpers/src/index.js | 6 + .../fixtures/build/rollup-sass/.fatherrc.js | 4 + .../build/rollup-sass/expected/index.esm.js | 31 + .../fixtures/build/rollup-sass/src/foo.scss | 2 + .../fixtures/build/rollup-sass/src/index.js | 3 + .../build/rollup-svgr/.umirc.library.js | 3 + .../build/rollup-svgr/dist/index.esm.js | 22 + .../build/rollup-svgr/expected/index.esm.js | 22 + .../fixtures/build/rollup-svgr/package.json | 5 + .../fixtures/build/rollup-svgr/src/index.jsx | 3 + .../fixtures/build/rollup-svgr/src/menu.svg | 12 + .../.fatherrc.ts | 7 + .../expected/index.esm.js | 5 + .../expected/index.js | 7 + .../expected/type/index.d.ts | 5 + .../src/index.ts | 7 + .../tsconfig.json | 9 + .../typings.d.ts | 7 + .../build/rollup-typescript/.fatherrc.ts | 4 + .../rollup-typescript/expected/index.d.ts | 5 + .../rollup-typescript/expected/index.esm.js | 5 + .../build/rollup-typescript/expected/index.js | 7 + .../build/rollup-typescript/src/index.ts | 7 + .../build/rollup-typescript/tsconfig.json | 8 + .../build/rollup-typescript/typings.d.ts | 7 + .../build/rollup-umd-auto-name/.fatherrc.js | 6 + .../expected/index.umd.js | 13 + .../build/rollup-umd-auto-name/package.json | 3 + .../build/rollup-umd-auto-name/src/index.js | 4 + .../.fatherrc.js | 5 + .../expected/index.umd.css | 2 + .../expected/index.umd.js | 8 + .../expected/index.umd.min.css | 1 + .../expected/index.umd.min.js | 1 + .../src/foo.css | 2 + .../src/index.js | 3 + .../build/rollup-umd-globals/.fatherrc.js | 9 + .../rollup-umd-globals/expected/index.umd.js | 11 + .../build/rollup-umd-globals/package.json | 5 + .../build/rollup-umd-globals/src/index.js | 3 + .../build/rollup-umd-name/.fatherrc.js | 7 + .../rollup-umd-name/expected/index.umd.js | 13 + .../build/rollup-umd-name/src/index.js | 4 + .../build/rollup-umd-overrides/.fatherrc.js | 18 + .../expected/fetch.umd.js | 13 + .../expected/index.umd.js | 13 + .../build/rollup-umd-overrides/src/fetch.js | 4 + .../build/rollup-umd-overrides/src/index.js | 4 + .../build/rollup-umd-sourcemap/.fatherrc.js | 8 + .../expected/index.umd.js | 14 + .../expected/index.umd.js.map | 1 + .../build/rollup-umd-sourcemap/src/index.js | 4 + .../build/target-node-version/.fatherrc.js | 7 + .../target-node-version/expected/es/index.js | 4 + .../build/target-node-version/src/index.js | 6 + .../fixtures/build/target-node/.fatherrc.js | 6 + .../build/target-node/expected/index.esm.js | 6 + .../build/target-node/expected/index.js | 8 + .../fixtures/build/target-node/src/index.js | 6 + packages/father-build/src/getBabelConfig.ts | 95 ++ packages/father-build/src/getRollupConfig.ts | 317 ++++++ packages/father-build/src/getUserConfig.ts | 58 ++ packages/father-build/src/importLibToEs.js | 26 + packages/father-build/src/index.ts | 3 + .../src/normalizeBundleOpts.test.ts | 53 + .../father-build/src/normalizeBundleOpts.ts | 24 + packages/father-build/src/randomColor.ts | 34 + packages/father-build/src/registerBabel.ts | 23 + packages/father-build/src/rollup.ts | 62 ++ packages/father-build/src/schema.test.ts | 39 + packages/father-build/src/schema.ts | 170 +++ packages/father-build/src/types.d.ts | 99 ++ packages/father-build/src/utils.ts | 11 + packages/father-build/template/tsconfig.json | 23 + packages/plugin-collections/package.json | 11 + packages/plugin-collections/src/index.ts | 55 + .../src/models/collection.ts | 23 + .../plugin-collections/src/models/field.ts | 7 + .../src/resources/collections.ts | 25 + .../src/tables/collections.ts | 55 + .../plugin-collections/src/tables/fields.ts | 24 + packages/plugin-file-manager/package.json | 6 + packages/plugin-file-manager/src/index.ts | 0 packages/plugin-pages/package.json | 6 + packages/plugin-pages/src/index.ts | 0 packages/plugin-permissions/package.json | 6 + packages/plugin-permissions/src/index.ts | 0 packages/plugin-users/package.json | 6 + packages/plugin-users/src/index.ts | 0 packages/resourcer/package.json | 24 + .../resourcer/src/__tests__/actions/demo0.js | 5 + .../resourcer/src/__tests__/actions/demo1.ts | 5 + packages/resourcer/src/__tests__/koa.test.ts | 699 +++++++++++++ .../src/__tests__/middlewares/demo0.js | 5 + .../src/__tests__/middlewares/demo1.ts | 5 + .../resourcer/src/__tests__/resourcer.test.ts | 549 ++++++++++ .../resourcer/src/__tests__/resources/demo.ts | 10 + .../resourcer/src/__tests__/utils.test.ts | 195 ++++ packages/resourcer/src/action.ts | 324 ++++++ packages/resourcer/src/index.ts | 9 + packages/resourcer/src/middleware.ts | 78 ++ packages/resourcer/src/resource.ts | 105 ++ packages/resourcer/src/resourcer.ts | 293 ++++++ packages/resourcer/src/utils.ts | 256 +++++ packages/ui/package.json | 6 + packages/ui/src/details/index.tsx | 0 packages/ui/src/form/index.tsx | 0 packages/ui/src/index.ts | 0 packages/ui/src/spin/index.tsx | 0 packages/ui/src/table/index.tsx | 0 tsconfig.json | 20 + 509 files changed, 15121 insertions(+) create mode 100644 .env create mode 100755 .eslintrc create mode 100755 .fatherrc.ts create mode 100644 .gitignore create mode 100644 docker-compose.yml create mode 100644 dotenv.js create mode 100755 jest.config.js create mode 100644 lerna.json create mode 100644 package.json create mode 100644 packages/actions/package.json create mode 100644 packages/actions/src/__tests__/add.test.ts create mode 100644 packages/actions/src/__tests__/common.test.ts create mode 100644 packages/actions/src/__tests__/create.test.ts create mode 100644 packages/actions/src/__tests__/destroy.test.ts create mode 100644 packages/actions/src/__tests__/get.test.ts create mode 100644 packages/actions/src/__tests__/index.ts create mode 100644 packages/actions/src/__tests__/list.test.ts create mode 100644 packages/actions/src/__tests__/middleware.test.ts create mode 100644 packages/actions/src/__tests__/remove.test.ts create mode 100644 packages/actions/src/__tests__/set.test.ts create mode 100644 packages/actions/src/__tests__/tables/comments.ts create mode 100644 packages/actions/src/__tests__/tables/posts.ts create mode 100644 packages/actions/src/__tests__/tables/posts_tags.ts create mode 100644 packages/actions/src/__tests__/tables/profiles.ts create mode 100644 packages/actions/src/__tests__/tables/tags.ts create mode 100644 packages/actions/src/__tests__/tables/users.ts create mode 100644 packages/actions/src/__tests__/update.test.ts create mode 100644 packages/actions/src/actions/associate.ts create mode 100644 packages/actions/src/actions/common.ts create mode 100644 packages/actions/src/actions/index.ts create mode 100644 packages/actions/src/index.ts create mode 100644 packages/actions/src/middleware.ts create mode 100644 packages/actions/src/middlewares/associated.ts create mode 100644 packages/actions/src/middlewares/json-reponse.ts create mode 100644 packages/api/.gitignore create mode 100644 packages/api/Dockerfile create mode 100644 packages/api/docker-compose.yml create mode 100644 packages/api/example/index.ts create mode 100644 packages/api/nodemon.json create mode 100644 packages/api/package.json create mode 100755 packages/api/pm2.json create mode 100644 packages/api/src/index.ts create mode 100755 packages/create-nocobase-app/.local create mode 100755 packages/create-nocobase-app/README.md create mode 100755 packages/create-nocobase-app/bin/create-nocobase-app.js create mode 100755 packages/create-nocobase-app/package.json create mode 100755 packages/create-nocobase-app/src/AppGenerator/AppGenerator.ts create mode 100755 packages/create-nocobase-app/src/app.ts create mode 100755 packages/create-nocobase-app/src/cli.ts create mode 100755 packages/create-nocobase-app/src/fixtures/.gitkeep create mode 100755 packages/create-nocobase-app/src/index.test.ts create mode 100755 packages/create-nocobase-app/src/index.ts create mode 100755 packages/create-nocobase-app/templates/AppGenerator/.editorconfig create mode 100644 packages/create-nocobase-app/templates/AppGenerator/.env create mode 100755 packages/create-nocobase-app/templates/AppGenerator/.fatherrc.ts create mode 100755 packages/create-nocobase-app/templates/AppGenerator/.gitignore.tpl create mode 100755 packages/create-nocobase-app/templates/AppGenerator/.prettierignore create mode 100755 packages/create-nocobase-app/templates/AppGenerator/.prettierrc create mode 100755 packages/create-nocobase-app/templates/AppGenerator/.umirc.ts.tpl create mode 100755 packages/create-nocobase-app/templates/AppGenerator/README.md create mode 100755 packages/create-nocobase-app/templates/AppGenerator/mock/.gitkeep create mode 100644 packages/create-nocobase-app/templates/AppGenerator/nodemon.json create mode 100755 packages/create-nocobase-app/templates/AppGenerator/package.json.tpl create mode 100644 packages/create-nocobase-app/templates/AppGenerator/server.js create mode 100644 packages/create-nocobase-app/templates/AppGenerator/src/api/index.ts create mode 100755 packages/create-nocobase-app/templates/AppGenerator/src/pages/index.less create mode 100755 packages/create-nocobase-app/templates/AppGenerator/src/pages/index.tsx create mode 100755 packages/create-nocobase-app/templates/AppGenerator/tsconfig.json create mode 100755 packages/create-nocobase-app/templates/AppGenerator/typings.d.ts create mode 100644 packages/create-nocobase-app/tsconfig.json create mode 100644 packages/database/examples/index.ts create mode 100644 packages/database/examples/plugins/db-driven/index.ts create mode 100644 packages/database/examples/plugins/db-driven/tables/fields.ts create mode 100644 packages/database/examples/plugins/db-driven/tables/tables.ts create mode 100644 packages/database/examples/tables/bar.js create mode 100644 packages/database/examples/tables/comments.ts create mode 100644 packages/database/examples/tables/foo.json create mode 100644 packages/database/examples/tables/posts.ts create mode 100644 packages/database/examples/tables/profiles.ts create mode 100644 packages/database/examples/tables/tags.ts create mode 100644 packages/database/examples/tables/users.ts create mode 100644 packages/database/package.json create mode 100644 packages/database/src/__tests__/associations.test.ts create mode 100644 packages/database/src/__tests__/index.ts create mode 100644 packages/database/src/__tests__/model.test.ts create mode 100644 packages/database/src/__tests__/modules/fn.js create mode 100644 packages/database/src/__tests__/modules/fnts.ts create mode 100644 packages/database/src/__tests__/modules/json.json create mode 100644 packages/database/src/__tests__/modules/obj.js create mode 100644 packages/database/src/__tests__/modules/objts.ts create mode 100644 packages/database/src/__tests__/sync.test.ts create mode 100644 packages/database/src/__tests__/tables.test.ts create mode 100644 packages/database/src/__tests__/types.test.ts create mode 100644 packages/database/src/__tests__/utils.test.ts create mode 100644 packages/database/src/database.ts create mode 100644 packages/database/src/fields/field-types.ts create mode 100644 packages/database/src/fields/index.ts create mode 100644 packages/database/src/fields/option-types.ts create mode 100644 packages/database/src/index.ts create mode 100644 packages/database/src/model.ts create mode 100644 packages/database/src/table.ts create mode 100644 packages/database/src/utils.ts create mode 100644 packages/father-build/.local create mode 100644 packages/father-build/.npmignore create mode 100644 packages/father-build/README.md create mode 100755 packages/father-build/bin/father-build.js create mode 100644 packages/father-build/package.json create mode 100644 packages/father-build/src/babel.ts create mode 100644 packages/father-build/src/build.test.ts create mode 100644 packages/father-build/src/build.ts create mode 100644 packages/father-build/src/fixtures/build/babel-browser-files/.fatherrc.js create mode 100644 packages/father-build/src/fixtures/build/babel-browser-files/expected/es/browser.js create mode 100644 packages/father-build/src/fixtures/build/babel-browser-files/expected/es/node.js create mode 100644 packages/father-build/src/fixtures/build/babel-browser-files/src/browser.js create mode 100644 packages/father-build/src/fixtures/build/babel-browser-files/src/node.js create mode 100644 packages/father-build/src/fixtures/build/babel-cjs-lazy/.fatherrc.js create mode 100644 packages/father-build/src/fixtures/build/babel-cjs-lazy/src/foo.js create mode 100644 packages/father-build/src/fixtures/build/babel-cjs-lazy/src/index.js create mode 100644 packages/father-build/src/fixtures/build/babel-extra-babel-presets-and-plugins/.fatherrc.js create mode 100644 packages/father-build/src/fixtures/build/babel-extra-babel-presets-and-plugins/expected/es/index.js create mode 100644 packages/father-build/src/fixtures/build/babel-extra-babel-presets-and-plugins/p1.js create mode 100644 packages/father-build/src/fixtures/build/babel-extra-babel-presets-and-plugins/p2.js create mode 100644 packages/father-build/src/fixtures/build/babel-extra-babel-presets-and-plugins/preset.js create mode 100644 packages/father-build/src/fixtures/build/babel-extra-babel-presets-and-plugins/src/index.js create mode 100644 packages/father-build/src/fixtures/build/babel-importLibToEs/.fatherrc.js create mode 100644 packages/father-build/src/fixtures/build/babel-importLibToEs/expected/es/index.js create mode 100644 packages/father-build/src/fixtures/build/babel-importLibToEs/src/index.js create mode 100644 packages/father-build/src/fixtures/build/babel-less-to-css-forbid/.fatherrc.js create mode 100644 packages/father-build/src/fixtures/build/babel-less-to-css-forbid/expected/es/foo.js create mode 100644 packages/father-build/src/fixtures/build/babel-less-to-css-forbid/expected/es/foo.module.less create mode 100644 packages/father-build/src/fixtures/build/babel-less-to-css-forbid/expected/es/index.less create mode 100644 packages/father-build/src/fixtures/build/babel-less-to-css-forbid/src/foo.js create mode 100644 packages/father-build/src/fixtures/build/babel-less-to-css-forbid/src/foo.module.less create mode 100644 packages/father-build/src/fixtures/build/babel-less-to-css-forbid/src/index.less create mode 100644 packages/father-build/src/fixtures/build/babel-less-to-css/.fatherrc.js create mode 100644 packages/father-build/src/fixtures/build/babel-less-to-css/expected/es/foo.js create mode 100644 packages/father-build/src/fixtures/build/babel-less-to-css/expected/es/foo.module.css create mode 100644 packages/father-build/src/fixtures/build/babel-less-to-css/expected/es/index.css create mode 100644 packages/father-build/src/fixtures/build/babel-less-to-css/src/foo.js create mode 100644 packages/father-build/src/fixtures/build/babel-less-to-css/src/foo.module.less create mode 100644 packages/father-build/src/fixtures/build/babel-less-to-css/src/index.less create mode 100644 packages/father-build/src/fixtures/build/babel-node-files/.fatherrc.js create mode 100644 packages/father-build/src/fixtures/build/babel-node-files/expected/es/browser.js create mode 100644 packages/father-build/src/fixtures/build/babel-node-files/expected/es/node.js create mode 100644 packages/father-build/src/fixtures/build/babel-node-files/src/browser.js create mode 100644 packages/father-build/src/fixtures/build/babel-node-files/src/node.js create mode 100644 packages/father-build/src/fixtures/build/babel-node-tsx-jsx/.fatherrc.js create mode 100644 packages/father-build/src/fixtures/build/babel-node-tsx-jsx/expected/es/B.d.ts create mode 100644 packages/father-build/src/fixtures/build/babel-node-tsx-jsx/expected/es/B.js create mode 100644 packages/father-build/src/fixtures/build/babel-node-tsx-jsx/expected/es/C.js create mode 100644 packages/father-build/src/fixtures/build/babel-node-tsx-jsx/expected/es/node.js create mode 100644 packages/father-build/src/fixtures/build/babel-node-tsx-jsx/src/B.tsx create mode 100644 packages/father-build/src/fixtures/build/babel-node-tsx-jsx/src/C.jsx create mode 100644 packages/father-build/src/fixtures/build/babel-node-tsx-jsx/src/node.js create mode 100644 packages/father-build/src/fixtures/build/babel-node-tsx-jsx/tsconfig.json create mode 100644 packages/father-build/src/fixtures/build/babel-normal/.fatherrc.js create mode 100644 packages/father-build/src/fixtures/build/babel-normal/expected/es/foo.js create mode 100644 packages/father-build/src/fixtures/build/babel-normal/expected/es/index.js create mode 100644 packages/father-build/src/fixtures/build/babel-normal/expected/es/types.d.ts create mode 100644 packages/father-build/src/fixtures/build/babel-normal/src/__test__/__snapshots__/index.test.js.snap create mode 100644 packages/father-build/src/fixtures/build/babel-normal/src/__test__/index.test.js create mode 100644 packages/father-build/src/fixtures/build/babel-normal/src/bar.md create mode 100644 packages/father-build/src/fixtures/build/babel-normal/src/bar.mdx create mode 100644 packages/father-build/src/fixtures/build/babel-normal/src/demos/foo.js create mode 100644 packages/father-build/src/fixtures/build/babel-normal/src/fixtures/example/package.json create mode 100644 packages/father-build/src/fixtures/build/babel-normal/src/foo.e2e.ts create mode 100644 packages/father-build/src/fixtures/build/babel-normal/src/foo.js create mode 100644 packages/father-build/src/fixtures/build/babel-normal/src/foo.spec.ts create mode 100644 packages/father-build/src/fixtures/build/babel-normal/src/foo.test.js create mode 100644 packages/father-build/src/fixtures/build/babel-normal/src/foo.test.jsx create mode 100644 packages/father-build/src/fixtures/build/babel-normal/src/foo.test.ts create mode 100644 packages/father-build/src/fixtures/build/babel-normal/src/foo.test.tsx create mode 100644 packages/father-build/src/fixtures/build/babel-normal/src/index.js create mode 100644 packages/father-build/src/fixtures/build/babel-normal/src/types.d.ts create mode 100644 packages/father-build/src/fixtures/build/babel-runtimeHelpers/.fatherrc.js create mode 100644 packages/father-build/src/fixtures/build/babel-runtimeHelpers/expected/es/index.js create mode 100644 packages/father-build/src/fixtures/build/babel-runtimeHelpers/package.json create mode 100644 packages/father-build/src/fixtures/build/babel-runtimeHelpers/src/index.js create mode 100644 packages/father-build/src/fixtures/build/babel-syntax/.fatherrc.js create mode 100644 packages/father-build/src/fixtures/build/babel-syntax/expected/index.esm.js create mode 100644 packages/father-build/src/fixtures/build/babel-syntax/package.json create mode 100644 packages/father-build/src/fixtures/build/babel-syntax/src/a.js create mode 100644 packages/father-build/src/fixtures/build/babel-syntax/src/b.js create mode 100644 packages/father-build/src/fixtures/build/babel-syntax/src/index.js create mode 100644 packages/father-build/src/fixtures/build/babel-ts-3.7-features/.fatherrc.js create mode 100644 packages/father-build/src/fixtures/build/babel-ts-3.7-features/expected/es/index.d.ts create mode 100644 packages/father-build/src/fixtures/build/babel-ts-3.7-features/expected/es/index.js create mode 100644 packages/father-build/src/fixtures/build/babel-ts-3.7-features/src/index.ts create mode 100644 packages/father-build/src/fixtures/build/babel-typescript-disable-typecheck/.fatherrc.js create mode 100644 packages/father-build/src/fixtures/build/babel-typescript-disable-typecheck/expected/es/index.js create mode 100644 packages/father-build/src/fixtures/build/babel-typescript-disable-typecheck/src/index.ts create mode 100644 packages/father-build/src/fixtures/build/babel-typescript-disable-typecheck/tsconfig.json create mode 100644 packages/father-build/src/fixtures/build/babel-typescript-template/.fatherrc.js create mode 100644 packages/father-build/src/fixtures/build/babel-typescript-template/expected/es/index.d.ts create mode 100644 packages/father-build/src/fixtures/build/babel-typescript-template/expected/es/index.js create mode 100644 packages/father-build/src/fixtures/build/babel-typescript-template/src/index.ts create mode 100644 packages/father-build/src/fixtures/build/babel-typescript/.fatherrc.js create mode 100644 packages/father-build/src/fixtures/build/babel-typescript/expected/es/index.d.ts create mode 100644 packages/father-build/src/fixtures/build/babel-typescript/expected/es/index.js create mode 100644 packages/father-build/src/fixtures/build/babel-typescript/src/index.ts create mode 100644 packages/father-build/src/fixtures/build/babel-typescript/tsconfig.json create mode 100644 packages/father-build/src/fixtures/build/config-array/.fatherrc.js create mode 100644 packages/father-build/src/fixtures/build/config-array/expected/index.umd.js create mode 100644 packages/father-build/src/fixtures/build/config-array/src/index.js create mode 100644 packages/father-build/src/fixtures/build/config-array/ui/index.js create mode 100644 packages/father-build/src/fixtures/build/lerna-root-config-override/.fatherrc.js create mode 100644 packages/father-build/src/fixtures/build/lerna-root-config-override/expected/bar/index.js create mode 100644 packages/father-build/src/fixtures/build/lerna-root-config-override/expected/foo/index.js create mode 100644 packages/father-build/src/fixtures/build/lerna-root-config-override/lerna.json create mode 100644 packages/father-build/src/fixtures/build/lerna-root-config-override/packages/bar/package.json create mode 100644 packages/father-build/src/fixtures/build/lerna-root-config-override/packages/bar/src/index.js create mode 100644 packages/father-build/src/fixtures/build/lerna-root-config-override/packages/foo/package.json create mode 100644 packages/father-build/src/fixtures/build/lerna-root-config-override/packages/foo/src/index.js create mode 100644 packages/father-build/src/fixtures/build/lerna-root-config/.fatherrc.js create mode 100644 packages/father-build/src/fixtures/build/lerna-root-config/expected/bar/index.esm.js create mode 100644 packages/father-build/src/fixtures/build/lerna-root-config/expected/foo/index.esm.js create mode 100644 packages/father-build/src/fixtures/build/lerna-root-config/lerna.json create mode 100644 packages/father-build/src/fixtures/build/lerna-root-config/packages/bar/package.json create mode 100644 packages/father-build/src/fixtures/build/lerna-root-config/packages/bar/src/index.js create mode 100644 packages/father-build/src/fixtures/build/lerna-root-config/packages/foo/package.json create mode 100644 packages/father-build/src/fixtures/build/lerna-root-config/packages/foo/src/index.js create mode 100644 packages/father-build/src/fixtures/build/lerna-root-scope/.fatherrc.js create mode 100644 packages/father-build/src/fixtures/build/lerna-root-scope/expected/bar/index.esm.js create mode 100644 packages/father-build/src/fixtures/build/lerna-root-scope/expected/foo/index.esm.js create mode 100644 packages/father-build/src/fixtures/build/lerna-root-scope/lerna.json create mode 100644 packages/father-build/src/fixtures/build/lerna-root-scope/packages/bar/.fatherrc.js create mode 100644 packages/father-build/src/fixtures/build/lerna-root-scope/packages/bar/package.json create mode 100644 packages/father-build/src/fixtures/build/lerna-root-scope/packages/bar/src/index.js create mode 100644 packages/father-build/src/fixtures/build/lerna-root-scope/packages/foo/.fatherrc.js create mode 100644 packages/father-build/src/fixtures/build/lerna-root-scope/packages/foo/package.json create mode 100644 packages/father-build/src/fixtures/build/lerna-root-scope/packages/foo/src/index.js create mode 100644 packages/father-build/src/fixtures/build/lerna-root-tsconfig/.fatherrc.js create mode 100644 packages/father-build/src/fixtures/build/lerna-root-tsconfig/expected/bar/es/index.js create mode 100644 packages/father-build/src/fixtures/build/lerna-root-tsconfig/expected/foo/es/index.js create mode 100644 packages/father-build/src/fixtures/build/lerna-root-tsconfig/lerna.json create mode 100644 packages/father-build/src/fixtures/build/lerna-root-tsconfig/packages/bar/package.json create mode 100644 packages/father-build/src/fixtures/build/lerna-root-tsconfig/packages/bar/src/index.ts create mode 100644 packages/father-build/src/fixtures/build/lerna-root-tsconfig/packages/foo/package.json create mode 100644 packages/father-build/src/fixtures/build/lerna-root-tsconfig/packages/foo/src/index.ts create mode 100644 packages/father-build/src/fixtures/build/lerna-root-tsconfig/tsconfig.json create mode 100644 packages/father-build/src/fixtures/build/lerna-scope-packages/expected/bar/index.esm.js create mode 100644 packages/father-build/src/fixtures/build/lerna-scope-packages/expected/foo/index.esm.js create mode 100644 packages/father-build/src/fixtures/build/lerna-scope-packages/lerna.json create mode 100644 packages/father-build/src/fixtures/build/lerna-scope-packages/packages/@hoo/bar/.fatherrc.js create mode 100644 packages/father-build/src/fixtures/build/lerna-scope-packages/packages/@hoo/bar/package.json create mode 100644 packages/father-build/src/fixtures/build/lerna-scope-packages/packages/@hoo/bar/src/index.js create mode 100644 packages/father-build/src/fixtures/build/lerna-scope-packages/packages/foo/.fatherrc.js create mode 100644 packages/father-build/src/fixtures/build/lerna-scope-packages/packages/foo/package.json create mode 100644 packages/father-build/src/fixtures/build/lerna-scope-packages/packages/foo/src/index.js create mode 100644 packages/father-build/src/fixtures/build/lerna/expected/bar/index.esm.js create mode 100644 packages/father-build/src/fixtures/build/lerna/expected/foo/index.esm.js create mode 100644 packages/father-build/src/fixtures/build/lerna/lerna.json create mode 100644 packages/father-build/src/fixtures/build/lerna/packages/bar/.fatherrc.js create mode 100644 packages/father-build/src/fixtures/build/lerna/packages/bar/package.json create mode 100644 packages/father-build/src/fixtures/build/lerna/packages/bar/src/index.js create mode 100644 packages/father-build/src/fixtures/build/lerna/packages/foo/.fatherrc.js create mode 100644 packages/father-build/src/fixtures/build/lerna/packages/foo/package.json create mode 100644 packages/father-build/src/fixtures/build/lerna/packages/foo/src/index.js create mode 100644 packages/father-build/src/fixtures/build/require-node_modules-in-sub-directory/.fatherrc.js create mode 100644 packages/father-build/src/fixtures/build/require-node_modules-in-sub-directory/expected/index.umd.js create mode 100644 packages/father-build/src/fixtures/build/require-node_modules-in-sub-directory/package.json create mode 100644 packages/father-build/src/fixtures/build/require-node_modules-in-sub-directory/packages/fooo/package.json create mode 100644 packages/father-build/src/fixtures/build/require-node_modules-in-sub-directory/packages/fooo/src/index.js create mode 100644 packages/father-build/src/fixtures/build/require-node_modules-in-sub-directory/src/index.js create mode 100644 packages/father-build/src/fixtures/build/rollup-commonjs-deps-named-exports/.fatherrc.js create mode 100644 packages/father-build/src/fixtures/build/rollup-commonjs-deps-named-exports/expected/index.umd.js create mode 100644 packages/father-build/src/fixtures/build/rollup-commonjs-deps-named-exports/package.json create mode 100644 packages/father-build/src/fixtures/build/rollup-commonjs-deps-named-exports/src/index.js create mode 100644 packages/father-build/src/fixtures/build/rollup-commonjs-deps/.fatherrc.js create mode 100644 packages/father-build/src/fixtures/build/rollup-commonjs-deps/expected/index.umd.js create mode 100644 packages/father-build/src/fixtures/build/rollup-commonjs-deps/package.json create mode 100644 packages/father-build/src/fixtures/build/rollup-commonjs-deps/src/index.js create mode 100644 packages/father-build/src/fixtures/build/rollup-config-alias/.fatherrc.js create mode 100644 packages/father-build/src/fixtures/build/rollup-config-alias/expected/index.esm.js create mode 100644 packages/father-build/src/fixtures/build/rollup-config-alias/src/index.js create mode 100644 packages/father-build/src/fixtures/build/rollup-css-autoprefixer/.fatherrc.js create mode 100644 packages/father-build/src/fixtures/build/rollup-css-autoprefixer/expected/index.esm.js create mode 100644 packages/father-build/src/fixtures/build/rollup-css-autoprefixer/src/foo.css create mode 100644 packages/father-build/src/fixtures/build/rollup-css-autoprefixer/src/index.js create mode 100644 packages/father-build/src/fixtures/build/rollup-css-extract/.fatherrc.js create mode 100644 packages/father-build/src/fixtures/build/rollup-css-extract/expected/index.esm.css create mode 100644 packages/father-build/src/fixtures/build/rollup-css-extract/expected/index.esm.js create mode 100644 packages/father-build/src/fixtures/build/rollup-css-extract/src/foo.css create mode 100644 packages/father-build/src/fixtures/build/rollup-css-extract/src/index.js create mode 100644 packages/father-build/src/fixtures/build/rollup-css-inject/.fatherrc.js create mode 100644 packages/father-build/src/fixtures/build/rollup-css-inject/expected/index.esm.js create mode 100644 packages/father-build/src/fixtures/build/rollup-css-inject/src/foo.css create mode 100644 packages/father-build/src/fixtures/build/rollup-css-inject/src/index.js create mode 100644 packages/father-build/src/fixtures/build/rollup-css-modules/.fatherrc.js create mode 100644 packages/father-build/src/fixtures/build/rollup-css-modules/expected/index.esm.js create mode 100644 packages/father-build/src/fixtures/build/rollup-css-modules/src/foo.css create mode 100644 packages/father-build/src/fixtures/build/rollup-css-modules/src/index.js create mode 100644 packages/father-build/src/fixtures/build/rollup-css/.fatherrc.js create mode 100644 packages/father-build/src/fixtures/build/rollup-css/expected/index.esm.js create mode 100644 packages/father-build/src/fixtures/build/rollup-css/src/foo.css create mode 100644 packages/father-build/src/fixtures/build/rollup-css/src/index.js create mode 100644 packages/father-build/src/fixtures/build/rollup-esm-minify/.fatherrc.js create mode 100644 packages/father-build/src/fixtures/build/rollup-esm-minify/expected/index.esm.js create mode 100644 packages/father-build/src/fixtures/build/rollup-esm-minify/package.json create mode 100644 packages/father-build/src/fixtures/build/rollup-esm-minify/src/foo.js create mode 100644 packages/father-build/src/fixtures/build/rollup-esm-minify/src/index.js create mode 100644 packages/father-build/src/fixtures/build/rollup-esm-mjs/.fatherrc.js create mode 100644 packages/father-build/src/fixtures/build/rollup-esm-mjs/expected/index.esm.js create mode 100644 packages/father-build/src/fixtures/build/rollup-esm-mjs/expected/index.mjs create mode 100644 packages/father-build/src/fixtures/build/rollup-esm-mjs/package.json create mode 100644 packages/father-build/src/fixtures/build/rollup-esm-mjs/src/foo.js create mode 100644 packages/father-build/src/fixtures/build/rollup-esm-mjs/src/index.js create mode 100644 packages/father-build/src/fixtures/build/rollup-externalsExclude/.fatherrc.js create mode 100644 packages/father-build/src/fixtures/build/rollup-externalsExclude/expected/index.esm.js create mode 100644 packages/father-build/src/fixtures/build/rollup-externalsExclude/expected/index.js create mode 100644 packages/father-build/src/fixtures/build/rollup-externalsExclude/package.json create mode 100644 packages/father-build/src/fixtures/build/rollup-externalsExclude/src/index.js create mode 100644 packages/father-build/src/fixtures/build/rollup-extra-babel-presets-and-plugins/.fatherrc.js create mode 100644 packages/father-build/src/fixtures/build/rollup-extra-babel-presets-and-plugins/expected/index.esm.js create mode 100644 packages/father-build/src/fixtures/build/rollup-extra-babel-presets-and-plugins/p1.js create mode 100644 packages/father-build/src/fixtures/build/rollup-extra-babel-presets-and-plugins/p2.js create mode 100644 packages/father-build/src/fixtures/build/rollup-extra-babel-presets-and-plugins/preset.js create mode 100644 packages/father-build/src/fixtures/build/rollup-extra-babel-presets-and-plugins/src/index.js create mode 100644 packages/father-build/src/fixtures/build/rollup-extraExternals/.fatherrc.js create mode 100644 packages/father-build/src/fixtures/build/rollup-extraExternals/expected/index.esm.js create mode 100644 packages/father-build/src/fixtures/build/rollup-extraExternals/package.json create mode 100644 packages/father-build/src/fixtures/build/rollup-extraExternals/src/index.js create mode 100644 packages/father-build/src/fixtures/build/rollup-extraRollupPlugins/.fatherrc.js create mode 100644 packages/father-build/src/fixtures/build/rollup-extraRollupPlugins/expected/index.esm.js create mode 100644 packages/father-build/src/fixtures/build/rollup-extraRollupPlugins/package.json create mode 100644 packages/father-build/src/fixtures/build/rollup-extraRollupPlugins/src/index.js create mode 100644 packages/father-build/src/fixtures/build/rollup-import-directory/.fatherrc.js create mode 100644 packages/father-build/src/fixtures/build/rollup-import-directory/expected/index.esm.js create mode 100644 packages/father-build/src/fixtures/build/rollup-import-directory/src/foo/index.js create mode 100644 packages/father-build/src/fixtures/build/rollup-import-directory/src/index.js create mode 100644 packages/father-build/src/fixtures/build/rollup-importLibToEs/.fatherrc.js create mode 100644 packages/father-build/src/fixtures/build/rollup-importLibToEs/expected/index.esm.js create mode 100644 packages/father-build/src/fixtures/build/rollup-importLibToEs/expected/index.js create mode 100644 packages/father-build/src/fixtures/build/rollup-importLibToEs/src/index.js create mode 100644 packages/father-build/src/fixtures/build/rollup-inject/.fatherrc.js create mode 100644 packages/father-build/src/fixtures/build/rollup-inject/expected/index.esm.js create mode 100644 packages/father-build/src/fixtures/build/rollup-inject/package.json create mode 100644 packages/father-build/src/fixtures/build/rollup-inject/src/index.js create mode 100644 packages/father-build/src/fixtures/build/rollup-json/.fatherrc.js create mode 100644 packages/father-build/src/fixtures/build/rollup-json/expected/index.esm.js create mode 100644 packages/father-build/src/fixtures/build/rollup-json/src/index.js create mode 100644 packages/father-build/src/fixtures/build/rollup-json/src/pkg.json create mode 100644 packages/father-build/src/fixtures/build/rollup-less-modules/.fatherrc.js create mode 100644 packages/father-build/src/fixtures/build/rollup-less-modules/expected/index.esm.js create mode 100644 packages/father-build/src/fixtures/build/rollup-less-modules/src/foo.less create mode 100644 packages/father-build/src/fixtures/build/rollup-less-modules/src/index.js create mode 100644 packages/father-build/src/fixtures/build/rollup-less-node-modules/.fatherrc.js create mode 100644 packages/father-build/src/fixtures/build/rollup-less-node-modules/expected/index.esm.js create mode 100644 packages/father-build/src/fixtures/build/rollup-less-node-modules/src/foo.less create mode 100644 packages/father-build/src/fixtures/build/rollup-less-node-modules/src/index.js create mode 100644 packages/father-build/src/fixtures/build/rollup-less/.fatherrc.js create mode 100644 packages/father-build/src/fixtures/build/rollup-less/expected/index.esm.js create mode 100644 packages/father-build/src/fixtures/build/rollup-less/src/foo.less create mode 100644 packages/father-build/src/fixtures/build/rollup-less/src/index.js create mode 100644 packages/father-build/src/fixtures/build/rollup-multiple-entry/.fatherrc.js create mode 100644 packages/father-build/src/fixtures/build/rollup-multiple-entry/expected/bar.esm.js create mode 100644 packages/father-build/src/fixtures/build/rollup-multiple-entry/expected/foo.esm.js create mode 100644 packages/father-build/src/fixtures/build/rollup-multiple-entry/src/bar.js create mode 100644 packages/father-build/src/fixtures/build/rollup-multiple-entry/src/foo.js create mode 100644 packages/father-build/src/fixtures/build/rollup-node-resolve/.fatherrc.js create mode 100644 packages/father-build/src/fixtures/build/rollup-node-resolve/expected/index.umd.js create mode 100644 packages/father-build/src/fixtures/build/rollup-node-resolve/package.json create mode 100644 packages/father-build/src/fixtures/build/rollup-node-resolve/src/index.js create mode 100644 packages/father-build/src/fixtures/build/rollup-normal/.fatherrc.js create mode 100644 packages/father-build/src/fixtures/build/rollup-normal/expected/index.esm.js create mode 100644 packages/father-build/src/fixtures/build/rollup-normal/expected/index.js create mode 100644 packages/father-build/src/fixtures/build/rollup-normal/expected/index.umd.js create mode 100644 packages/father-build/src/fixtures/build/rollup-normal/expected/index.umd.min.js create mode 100644 packages/father-build/src/fixtures/build/rollup-normal/package.json create mode 100644 packages/father-build/src/fixtures/build/rollup-normal/src/foo.js create mode 100644 packages/father-build/src/fixtures/build/rollup-normal/src/index.js create mode 100644 packages/father-build/src/fixtures/build/rollup-outputFilename-with-overrides/.fatherrc.js create mode 100644 packages/father-build/src/fixtures/build/rollup-outputFilename-with-overrides/expected/dva.bar.js create mode 100644 packages/father-build/src/fixtures/build/rollup-outputFilename-with-overrides/expected/dva.bar.min.js create mode 100644 packages/father-build/src/fixtures/build/rollup-outputFilename-with-overrides/expected/dva.esm.js create mode 100644 packages/father-build/src/fixtures/build/rollup-outputFilename-with-overrides/expected/dva.foo.cjs.js create mode 100644 packages/father-build/src/fixtures/build/rollup-outputFilename-with-overrides/expected/dva.foo.esm.js create mode 100644 packages/father-build/src/fixtures/build/rollup-outputFilename-with-overrides/expected/dva.foo.umd.js create mode 100644 packages/father-build/src/fixtures/build/rollup-outputFilename-with-overrides/expected/dva.foo.umd.min.js create mode 100644 packages/father-build/src/fixtures/build/rollup-outputFilename-with-overrides/expected/dva.js create mode 100644 packages/father-build/src/fixtures/build/rollup-outputFilename-with-overrides/expected/dva.umd.js create mode 100644 packages/father-build/src/fixtures/build/rollup-outputFilename-with-overrides/expected/dva.umd.min.js create mode 100644 packages/father-build/src/fixtures/build/rollup-outputFilename-with-overrides/src/bar.js create mode 100644 packages/father-build/src/fixtures/build/rollup-outputFilename-with-overrides/src/foo.js create mode 100644 packages/father-build/src/fixtures/build/rollup-outputFilename-with-overrides/src/index.js create mode 100644 packages/father-build/src/fixtures/build/rollup-replace/.fatherrc.js create mode 100644 packages/father-build/src/fixtures/build/rollup-replace/expected/index.esm.js create mode 100644 packages/father-build/src/fixtures/build/rollup-replace/package.json create mode 100644 packages/father-build/src/fixtures/build/rollup-replace/src/index.js create mode 100644 packages/father-build/src/fixtures/build/rollup-resolve-jsx/.fatherrc.js create mode 100644 packages/father-build/src/fixtures/build/rollup-resolve-jsx/expected/index.esm.js create mode 100644 packages/father-build/src/fixtures/build/rollup-resolve-jsx/package.json create mode 100644 packages/father-build/src/fixtures/build/rollup-resolve-jsx/src/Foo.jsx create mode 100644 packages/father-build/src/fixtures/build/rollup-resolve-jsx/src/index.js create mode 100644 packages/father-build/src/fixtures/build/rollup-runtimeHelpers/.fatherrc.js create mode 100644 packages/father-build/src/fixtures/build/rollup-runtimeHelpers/expected/index.esm.js create mode 100644 packages/father-build/src/fixtures/build/rollup-runtimeHelpers/expected/index.js create mode 100644 packages/father-build/src/fixtures/build/rollup-runtimeHelpers/package.json create mode 100644 packages/father-build/src/fixtures/build/rollup-runtimeHelpers/src/index.js create mode 100644 packages/father-build/src/fixtures/build/rollup-sass/.fatherrc.js create mode 100644 packages/father-build/src/fixtures/build/rollup-sass/expected/index.esm.js create mode 100644 packages/father-build/src/fixtures/build/rollup-sass/src/foo.scss create mode 100644 packages/father-build/src/fixtures/build/rollup-sass/src/index.js create mode 100644 packages/father-build/src/fixtures/build/rollup-svgr/.umirc.library.js create mode 100644 packages/father-build/src/fixtures/build/rollup-svgr/dist/index.esm.js create mode 100644 packages/father-build/src/fixtures/build/rollup-svgr/expected/index.esm.js create mode 100644 packages/father-build/src/fixtures/build/rollup-svgr/package.json create mode 100644 packages/father-build/src/fixtures/build/rollup-svgr/src/index.jsx create mode 100644 packages/father-build/src/fixtures/build/rollup-svgr/src/menu.svg create mode 100644 packages/father-build/src/fixtures/build/rollup-typescript-declarationDir/.fatherrc.ts create mode 100644 packages/father-build/src/fixtures/build/rollup-typescript-declarationDir/expected/index.esm.js create mode 100644 packages/father-build/src/fixtures/build/rollup-typescript-declarationDir/expected/index.js create mode 100644 packages/father-build/src/fixtures/build/rollup-typescript-declarationDir/expected/type/index.d.ts create mode 100644 packages/father-build/src/fixtures/build/rollup-typescript-declarationDir/src/index.ts create mode 100644 packages/father-build/src/fixtures/build/rollup-typescript-declarationDir/tsconfig.json create mode 100644 packages/father-build/src/fixtures/build/rollup-typescript-declarationDir/typings.d.ts create mode 100644 packages/father-build/src/fixtures/build/rollup-typescript/.fatherrc.ts create mode 100644 packages/father-build/src/fixtures/build/rollup-typescript/expected/index.d.ts create mode 100644 packages/father-build/src/fixtures/build/rollup-typescript/expected/index.esm.js create mode 100644 packages/father-build/src/fixtures/build/rollup-typescript/expected/index.js create mode 100644 packages/father-build/src/fixtures/build/rollup-typescript/src/index.ts create mode 100644 packages/father-build/src/fixtures/build/rollup-typescript/tsconfig.json create mode 100644 packages/father-build/src/fixtures/build/rollup-typescript/typings.d.ts create mode 100644 packages/father-build/src/fixtures/build/rollup-umd-auto-name/.fatherrc.js create mode 100644 packages/father-build/src/fixtures/build/rollup-umd-auto-name/expected/index.umd.js create mode 100644 packages/father-build/src/fixtures/build/rollup-umd-auto-name/package.json create mode 100644 packages/father-build/src/fixtures/build/rollup-umd-auto-name/src/index.js create mode 100644 packages/father-build/src/fixtures/build/rollup-umd-css-extract-minFile/.fatherrc.js create mode 100644 packages/father-build/src/fixtures/build/rollup-umd-css-extract-minFile/expected/index.umd.css create mode 100644 packages/father-build/src/fixtures/build/rollup-umd-css-extract-minFile/expected/index.umd.js create mode 100644 packages/father-build/src/fixtures/build/rollup-umd-css-extract-minFile/expected/index.umd.min.css create mode 100644 packages/father-build/src/fixtures/build/rollup-umd-css-extract-minFile/expected/index.umd.min.js create mode 100644 packages/father-build/src/fixtures/build/rollup-umd-css-extract-minFile/src/foo.css create mode 100644 packages/father-build/src/fixtures/build/rollup-umd-css-extract-minFile/src/index.js create mode 100644 packages/father-build/src/fixtures/build/rollup-umd-globals/.fatherrc.js create mode 100644 packages/father-build/src/fixtures/build/rollup-umd-globals/expected/index.umd.js create mode 100644 packages/father-build/src/fixtures/build/rollup-umd-globals/package.json create mode 100644 packages/father-build/src/fixtures/build/rollup-umd-globals/src/index.js create mode 100644 packages/father-build/src/fixtures/build/rollup-umd-name/.fatherrc.js create mode 100644 packages/father-build/src/fixtures/build/rollup-umd-name/expected/index.umd.js create mode 100644 packages/father-build/src/fixtures/build/rollup-umd-name/src/index.js create mode 100644 packages/father-build/src/fixtures/build/rollup-umd-overrides/.fatherrc.js create mode 100644 packages/father-build/src/fixtures/build/rollup-umd-overrides/expected/fetch.umd.js create mode 100644 packages/father-build/src/fixtures/build/rollup-umd-overrides/expected/index.umd.js create mode 100644 packages/father-build/src/fixtures/build/rollup-umd-overrides/src/fetch.js create mode 100644 packages/father-build/src/fixtures/build/rollup-umd-overrides/src/index.js create mode 100644 packages/father-build/src/fixtures/build/rollup-umd-sourcemap/.fatherrc.js create mode 100644 packages/father-build/src/fixtures/build/rollup-umd-sourcemap/expected/index.umd.js create mode 100644 packages/father-build/src/fixtures/build/rollup-umd-sourcemap/expected/index.umd.js.map create mode 100644 packages/father-build/src/fixtures/build/rollup-umd-sourcemap/src/index.js create mode 100644 packages/father-build/src/fixtures/build/target-node-version/.fatherrc.js create mode 100644 packages/father-build/src/fixtures/build/target-node-version/expected/es/index.js create mode 100644 packages/father-build/src/fixtures/build/target-node-version/src/index.js create mode 100644 packages/father-build/src/fixtures/build/target-node/.fatherrc.js create mode 100644 packages/father-build/src/fixtures/build/target-node/expected/index.esm.js create mode 100644 packages/father-build/src/fixtures/build/target-node/expected/index.js create mode 100644 packages/father-build/src/fixtures/build/target-node/src/index.js create mode 100644 packages/father-build/src/getBabelConfig.ts create mode 100644 packages/father-build/src/getRollupConfig.ts create mode 100644 packages/father-build/src/getUserConfig.ts create mode 100644 packages/father-build/src/importLibToEs.js create mode 100644 packages/father-build/src/index.ts create mode 100644 packages/father-build/src/normalizeBundleOpts.test.ts create mode 100644 packages/father-build/src/normalizeBundleOpts.ts create mode 100644 packages/father-build/src/randomColor.ts create mode 100644 packages/father-build/src/registerBabel.ts create mode 100644 packages/father-build/src/rollup.ts create mode 100644 packages/father-build/src/schema.test.ts create mode 100644 packages/father-build/src/schema.ts create mode 100644 packages/father-build/src/types.d.ts create mode 100644 packages/father-build/src/utils.ts create mode 100644 packages/father-build/template/tsconfig.json create mode 100644 packages/plugin-collections/package.json create mode 100644 packages/plugin-collections/src/index.ts create mode 100644 packages/plugin-collections/src/models/collection.ts create mode 100644 packages/plugin-collections/src/models/field.ts create mode 100644 packages/plugin-collections/src/resources/collections.ts create mode 100644 packages/plugin-collections/src/tables/collections.ts create mode 100644 packages/plugin-collections/src/tables/fields.ts create mode 100644 packages/plugin-file-manager/package.json create mode 100644 packages/plugin-file-manager/src/index.ts create mode 100644 packages/plugin-pages/package.json create mode 100644 packages/plugin-pages/src/index.ts create mode 100644 packages/plugin-permissions/package.json create mode 100644 packages/plugin-permissions/src/index.ts create mode 100644 packages/plugin-users/package.json create mode 100644 packages/plugin-users/src/index.ts create mode 100644 packages/resourcer/package.json create mode 100644 packages/resourcer/src/__tests__/actions/demo0.js create mode 100644 packages/resourcer/src/__tests__/actions/demo1.ts create mode 100644 packages/resourcer/src/__tests__/koa.test.ts create mode 100644 packages/resourcer/src/__tests__/middlewares/demo0.js create mode 100644 packages/resourcer/src/__tests__/middlewares/demo1.ts create mode 100644 packages/resourcer/src/__tests__/resourcer.test.ts create mode 100644 packages/resourcer/src/__tests__/resources/demo.ts create mode 100644 packages/resourcer/src/__tests__/utils.test.ts create mode 100644 packages/resourcer/src/action.ts create mode 100644 packages/resourcer/src/index.ts create mode 100644 packages/resourcer/src/middleware.ts create mode 100644 packages/resourcer/src/resource.ts create mode 100644 packages/resourcer/src/resourcer.ts create mode 100644 packages/resourcer/src/utils.ts create mode 100644 packages/ui/package.json create mode 100644 packages/ui/src/details/index.tsx create mode 100644 packages/ui/src/form/index.tsx create mode 100644 packages/ui/src/index.ts create mode 100644 packages/ui/src/spin/index.tsx create mode 100644 packages/ui/src/table/index.tsx create mode 100644 tsconfig.json diff --git a/.env b/.env new file mode 100644 index 000000000..c6323f000 --- /dev/null +++ b/.env @@ -0,0 +1 @@ +DIALECT=postgres \ No newline at end of file diff --git a/.eslintrc b/.eslintrc new file mode 100755 index 000000000..27846dfbe --- /dev/null +++ b/.eslintrc @@ -0,0 +1,17 @@ +{ + "parser": "@typescript-eslint/parser", + "env": { + "node": true + }, + "globals": { + "sleep": true, + "prettyFormat": true + }, + "parserOptions": { + "ecmaVersion": 2018, + "sourceType": "module" + }, + "rules": { + // "quotes": ["error", "single"] + } +} \ No newline at end of file diff --git a/.fatherrc.ts b/.fatherrc.ts new file mode 100755 index 000000000..87b27ac7a --- /dev/null +++ b/.fatherrc.ts @@ -0,0 +1,23 @@ +import { readdirSync } from 'fs'; +import { join } from 'path'; + +// utils must build before core +// runtime must build before renderer-react +const headPkgs = [ + 'database', + 'resourcer', + 'actions', +]; +const tailPkgs = []; +const otherPkgs = readdirSync(join(__dirname, 'packages')).filter( + (pkg) => { + return pkg !== 'father-build' && pkg.charAt(0) !== '.' && !headPkgs.includes(pkg) && !tailPkgs.includes(pkg) + }, +); + +export default { + target: 'node', + cjs: { type: 'babel', lazy: true }, + // disableTypeCheck: true, + pkgs: [...headPkgs, ...otherPkgs, ...tailPkgs], +}; diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..70eec6e8a --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +node_modules/ +lib/ +.DS_Store +package-lock.json +yarn.lock +yarn-error.log +lerna-debug.log +packages/database/package-lock.json +packages/resourcer/package-lock.json + +verdaccio \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 000000000..bd10a15a2 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,38 @@ +version: "3" +networks: + node-network: + driver: bridge +services: + verdaccio: + image: verdaccio/verdaccio + container_name: "verdaccio" + networks: + - node-network + environment: + - VERDACCIO_PORT=4873 + ports: + - "4873:4873" + # volumes: + # - "./verdaccio/storage:/verdaccio/storage" + # - "./verdaccio/config:/verdaccio/conf" + # - "./verdaccio/plugins:/verdaccio/plugins" + mysql: + image: mysql:5.7 + environment: + MYSQL_DATABASE: "test" + MYSQL_USER: "test" + MYSQL_PASSWORD: "test" + MYSQL_ROOT_PASSWORD: "test" + restart: always + ports: + - "43306:3306" + postgres: + image: postgres:10 + restart: always + ports: + - "45432:5432" + command: postgres -c wal_level=logical + environment: + POSTGRES_USER: test + POSTGRES_DB: test + POSTGRES_PASSWORD: test \ No newline at end of file diff --git a/dotenv.js b/dotenv.js new file mode 100644 index 000000000..f8518254b --- /dev/null +++ b/dotenv.js @@ -0,0 +1 @@ +require('dotenv').config(); diff --git a/jest.config.js b/jest.config.js new file mode 100755 index 000000000..aec232535 --- /dev/null +++ b/jest.config.js @@ -0,0 +1,11 @@ +const path = require('path'); + +module.exports = { + preset: 'ts-jest', + testEnvironment: 'node', + setupFiles: [path.resolve(__dirname, 'dotenv.js')], + testMatch: [ + // '**/__tests__/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)' + ], +}; \ No newline at end of file diff --git a/lerna.json b/lerna.json new file mode 100644 index 000000000..79dfc4aad --- /dev/null +++ b/lerna.json @@ -0,0 +1,19 @@ +{ + "packages": [ + "packages/*" + ], + "version": "independent", + "command": { + "bootstrap": { + "npmClientArgs": [ + "--no-package-lock" + ] + }, + "publish": { + "allowBranch": "master", + "ignoreChanges": [ + "*.md" + ] + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 000000000..c952fe652 --- /dev/null +++ b/package.json @@ -0,0 +1,47 @@ +{ + "name": "root", + "private": true, + "scripts": { + "bootstrap": "lerna bootstrap --no-ci", + "build": "npm run build-father-build && node packages/father-build/bin/father-build.js", + "build-father-build": "cd packages/father-build && npm run build", + "clean": "lerna clean", + "db:start": "docker-compose up -d", + "lint": "eslint --ext .ts,.tsx,.js \"packages/*/src/**.@(ts|tsx|js)\" --fix", + "test": "npm run lint && jest --clearCache && jest", + "release": "npm run build && lerna publish --yes --registry https://npm.pkg.github.com" + }, + "devDependencies": { + "@koa/router": "^9.3.1", + "@types/jest": "^26.0.4", + "@types/koa-bodyparser": "^4.3.0", + "@types/koa-mount": "^4.0.0", + "@types/koa__router": "^8.0.2", + "@types/lodash": "^4.14.158", + "@types/node": "^14.0.23", + "@types/supertest": "^2.0.10", + "@typescript-eslint/eslint-plugin": "^3.6.1", + "@typescript-eslint/parser": "^3.6.1", + "cross-env": "^7.0.2", + "dotenv": "^8.2.0", + "eslint": "^7.4.0", + "eslint-plugin-import": "^2.22.0", + "eslint-plugin-node": "^11.1.0", + "eslint-plugin-promise": "^4.2.1", + "eslint-plugin-standard": "^4.0.1", + "jest": "^26.1.0", + "koa": "^2.13.0", + "koa-bodyparser": "^4.3.0", + "lerna": "^3.22.0", + "mysql2": "^2.1.0", + "nodemon": "^2.0.4", + "path-to-regexp": "^6.1.0", + "pg": "^8.3.0", + "pg-hstore": "^2.3.3", + "sequelize": "^6.3.4", + "supertest": "^4.0.2", + "ts-jest": "^26.1.2", + "ts-node": "^8.10.2", + "typescript": "^3.9.6" + } +} diff --git a/packages/actions/package.json b/packages/actions/package.json new file mode 100644 index 000000000..76af01999 --- /dev/null +++ b/packages/actions/package.json @@ -0,0 +1,24 @@ +{ + "name": "@nocobase/actions", + "version": "0.3.0-alpha.0", + "description": "", + "main": "./lib/index.js", + "types": "./lib/index.d.ts", + "scripts": { + }, + "license": "MIT", + "dependencies": { + "@nocobase/database": "^0.3.0-alpha.0", + "@nocobase/resourcer": "^0.3.0-alpha.0" + }, + "devDependencies": { + "koa": "^2.13.0", + "sequelize": "^6.3.4", + "typescript": "^3.9.6" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/nocobase/nocobase.git", + "directory": "packages/actions" + } +} diff --git a/packages/actions/src/__tests__/add.test.ts b/packages/actions/src/__tests__/add.test.ts new file mode 100644 index 000000000..6ff404a76 --- /dev/null +++ b/packages/actions/src/__tests__/add.test.ts @@ -0,0 +1,43 @@ +import Koa from 'koa'; +import http from 'http'; +import request from 'supertest'; +import actions from '..'; +import { getConfig } from './index'; +import Database, { Model } from '@nocobase/database'; +import Resourcer from '@nocobase/resourcer'; +import { resolve } from 'path'; + +describe('add', () => { + let db: Database; + // let resourcer: Resourcer; + let app: Koa; + + beforeAll(async () => { + const config = getConfig(); + app = config.app; + db = config.database; + db.import({ + directory: resolve(__dirname, './tables'), + }); + await db.sync({ + force: true, + }); + // resourcer = config.resourcer; + }); + + afterAll(async () => { + await db.close(); + }); + + it('belongsToMany1', async () => { + const [Post, Tag] = db.getModels(['posts', 'tags']); + let post = await Post.create(); + let tag1 = await Tag.create({name: 'tag1'}); + let tag2 = await Tag.create({name: 'tag2'}); + await request(http.createServer(app.callback())).post(`/posts/${post.id}/tags:add/${tag1.id}`); + await request(http.createServer(app.callback())).post(`/posts/${post.id}/tags:add/${tag2.id}`); + let [tag01, tag02] = await post.getTags(); + expect(tag01.id).toBe(tag1.id); + expect(tag02.id).toBe(tag2.id); + }); +}); diff --git a/packages/actions/src/__tests__/common.test.ts b/packages/actions/src/__tests__/common.test.ts new file mode 100644 index 000000000..e9a6353d3 --- /dev/null +++ b/packages/actions/src/__tests__/common.test.ts @@ -0,0 +1,130 @@ +import Koa from 'koa'; +import http from 'http'; +import request from 'supertest'; +import actions from '..'; +import { getConfig } from './index'; +import Database, { Model } from '@nocobase/database'; +import Resourcer from '@nocobase/resourcer'; +import { resolve } from 'path'; + +describe('common', () => { + let db: Database; + let resourcer: Resourcer; + let app: Koa; + beforeAll(async () => { + const config = getConfig(); + app = config.app; + db = config.database; + db.import({ + directory: resolve(__dirname, './tables'), + }); + await db.sync({ + force: true, + }); + resourcer = config.resourcer; + resourcer.define({ + name: 'posts', + actions: actions.common, + }); + }); + afterAll(async () => { + await db.close(); + }); + it('create', async () => { + const response = await request(http.createServer(app.callback())) + .post('/posts') + .send({ + title: 'title1', + }); + expect(response.body.title).toBe('title1'); + }); + it('update', async () => { + const Post = db.getModel('posts'); + const post = await Post.create(); + const response = await request(http.createServer(app.callback())) + .put(`/posts/${post.id}`) + .send({ + title: 'title2', + }); + expect(response.body.title).toBe('title2'); + }); + it('get', async () => { + const Post = db.getModel('posts'); + const post = await Post.create({title: 'title3'}); + const response = await request(http.createServer(app.callback())) + .get(`/posts/${post.id}`); + expect(response.body.title).toBe('title3'); + }); + it('delete', async () => { + const Post = db.getModel('posts'); + let post = await Post.create(); + await request(http.createServer(app.callback())) + .delete(`/posts/${post.id}`); + post = await Post.findByPk(post.id); + expect(post).toBeNull(); + }); + describe('list', () => { + beforeAll(async () => { + const Post = db.getModel('posts'); + const items = []; + for (let index = 0; index < 2; index++) { + items.push({ + title: `title${index}`, + status: 'draft', + }); + } + await Post.bulkCreate(items); + }); + + it('list1', async () => { + const Post = db.getModel('posts'); + const response = await request(http.createServer(app.callback())).get('/posts'); + expect(response.body.count).toBe(await Post.count()); + }); + + it('list2', async () => { + const Post = db.getModel('posts'); + const response = await request(http.createServer(app.callback())).get('/posts?filter[title]=title1'); + expect(response.body.count).toBe(await Post.count({ + where: { + title: 'title1', + }, + })); + }); + + it('list3', async () => { + const response = await request(http.createServer(app.callback())).get('/posts?filter[status]=draft&fields=title&page=1'); + expect(response.body).toEqual({ + count: 2, + page: 1, + per_page: 20, + rows: [ { title: 'title0' }, { title: 'title1' } ], + }); + }); + + it('list4', async () => { + const response = await request(http.createServer(app.callback())).get('/posts?filter[status]=draft&fields=title&page=1&perPage=1'); + expect(response.body).toEqual({ + count: 2, + page: 1, + per_page: 1, + rows: [ { title: 'title0' } ], + }); + }); + + it('list5', async () => { + const response = await request(http.createServer(app.callback())).get('/posts?filter[status]=draft&fields=title&page=2&per_page=1'); + expect(response.body).toEqual({ + count: 2, + page: 2, + per_page: 1, + rows: [ { title: 'title1' } ], + }); + }); + + it('list6', async () => { + const response = await request(http.createServer(app.callback())).get('/posts?fields=title&filter[customTitle]=title0&filter[status]=draft'); + expect(response.body).toEqual({ count: 1, rows: [ { title: 'title0' } ] }); + }); + }) +}); \ No newline at end of file diff --git a/packages/actions/src/__tests__/create.test.ts b/packages/actions/src/__tests__/create.test.ts new file mode 100644 index 000000000..e3fd47bbb --- /dev/null +++ b/packages/actions/src/__tests__/create.test.ts @@ -0,0 +1,55 @@ +import Koa from 'koa'; +import http from 'http'; +import request from 'supertest'; +import actions from '..'; +import { getConfig } from './index'; +import Database, { Model } from '@nocobase/database'; +import Resourcer from '@nocobase/resourcer'; +import { resolve } from 'path'; + +describe('create', () => { + let db: Database; + // let resourcer: Resourcer; + let app: Koa; + beforeAll(async () => { + const config = getConfig(); + app = config.app; + db = config.database; + db.import({ + directory: resolve(__dirname, './tables'), + }); + await db.sync({ + force: true, + }); + // resourcer = config.resourcer; + }); + + afterAll(async () => { + await db.close(); + }); + + describe('common', () => { + it('create', async () => { + const response = await request(http.createServer(app.callback())) + .post('/posts') + .send({ + title: 'title1', + }); + expect(response.body.title).toBe('title1'); + }); + }); + + describe('hasMany', () => { + it('create', async () => { + const Post = db.getModel('posts'); + const post = await Post.create(); + const response = await request(http.createServer(app.callback())) + .post(`/posts/${post.id}/comments`) + .send({ + content: 'content1', + }); + expect(response.body.post_id).toBe(post.id); + expect(response.body.content).toBe('content1'); + }); + }); +}); diff --git a/packages/actions/src/__tests__/destroy.test.ts b/packages/actions/src/__tests__/destroy.test.ts new file mode 100644 index 000000000..6d15a1a89 --- /dev/null +++ b/packages/actions/src/__tests__/destroy.test.ts @@ -0,0 +1,95 @@ +import Koa from 'koa'; +import http from 'http'; +import request from 'supertest'; +import actions from '..'; +import { getConfig } from './index'; +import Database, { Model } from '@nocobase/database'; +import Resourcer from '@nocobase/resourcer'; +import { resolve } from 'path'; + +describe('destroy', () => { + let db: Database; + // let resourcer: Resourcer; + let app: Koa; + + beforeAll(async () => { + const config = getConfig(); + app = config.app; + db = config.database; + db.import({ + directory: resolve(__dirname, './tables'), + }); + await db.sync({ + force: true, + }); + // resourcer = config.resourcer; + }); + + afterAll(async () => { + await db.close(); + }); + + it('common1', async () => { + const Post = db.getModel('posts'); + const post = await Post.create(); + const response = await request(http.createServer(app.callback())) + .delete(`/posts/${post.id}`); + // console.log(response.body); + expect(response.body).toBe(post.id); + }); + + it('hasOne1', async () => { + const User = db.getModel('users'); + const user = await User.create(); + await user.updateAssociations({ + profile: { + email: 'email1122', + } + }); + const response = await request(http.createServer(app.callback())) + .delete(`/users/${user.id}/profile`); + const profile = await user.getProfile(); + expect(profile).toBeNull(); + }); + + it('hasMany1', async () => { + const Post = db.getModel('posts'); + const post = await Post.create(); + await post.updateAssociations({ + comments: [ + {content: 'content111222'}, + ], + }); + let [comment] = await post.getComments(); + await request(http.createServer(app.callback())) + .delete(`/posts/${post.id}/comments/${comment.id}`); + const count = await post.countComments(); + expect(count).toBe(0); + }); + + it('belongsTo1', async () => { + const Post = db.getModel('posts'); + const post = await Post.create(); + await post.updateAssociations({ + user: {name: 'name121234'}, + }); + await request(http.createServer(app.callback())).delete(`/posts/${post.id}/user:destroy`); + const user = await post.getUser(); + expect(user).toBeNull(); + }); + + it('belongsToMany', async () => { + const Post = db.getModel('posts'); + const post = await Post.create(); + await post.updateAssociations({ + tags: [ + {name: 'tag112233'}, + ], + }); + const [tag] = await post.getTags(); + await request(http.createServer(app.callback())) + .delete(`/posts/${post.id}/tags:destroy/${tag.id}`); + const tags = await post.getTags(); + expect(tags.length).toBe(0); + }); +}); diff --git a/packages/actions/src/__tests__/get.test.ts b/packages/actions/src/__tests__/get.test.ts new file mode 100644 index 000000000..0ed5f4017 --- /dev/null +++ b/packages/actions/src/__tests__/get.test.ts @@ -0,0 +1,111 @@ +import Koa from 'koa'; +import http from 'http'; +import request from 'supertest'; +import actions from '..'; +import { getConfig } from './index'; +import Database, { Model } from '@nocobase/database'; +import Resourcer from '@nocobase/resourcer'; +import { resolve } from 'path'; + +describe('get', () => { + let db: Database; + // let resourcer: Resourcer; + let app: Koa; + + beforeAll(async () => { + const config = getConfig(); + app = config.app; + db = config.database; + db.import({ + directory: resolve(__dirname, './tables'), + }); + await db.sync({ + force: true, + }); + // resourcer = config.resourcer; + }); + + afterAll(async () => { + await db.close(); + }); + + it('common1', async () => { + const Post = db.getModel('posts'); + const post = await Post.create({ + title: 'title11112222' + }); + const response = await request(http.createServer(app.callback())) + .get(`/posts/${post.id}`); + expect(response.body.title).toBe('title11112222'); + }); + + it('hasOne1', async () => { + const User = db.getModel('users'); + const user = await User.create(); + const response = await request(http.createServer(app.callback())) + .get(`/users/${user.id}/profile?fields=email`); + expect(response.body).toEqual({}); + }); + + it('hasOne2', async () => { + const User = db.getModel('users'); + const user = await User.create(); + await user.updateAssociations({ + profile: { + email: 'email1', + }, + }); + const response = await request(http.createServer(app.callback())) + .get(`/users/${user.id}/profile?fields=email`); + expect(response.body).toEqual({ email: 'email1' }); + }); + + it('hasMany1', async () => { + const Post = db.getModel('posts'); + const post = await Post.create(); + await post.updateAssociations({ + comments: [ + {content: 'content111222'}, + ], + }); + const [comment] = await post.getComments(); + const response = await request(http.createServer(app.callback())) + .get(`/posts/${post.id}/comments/${comment.id}`); + expect(response.body.post_id).toBe(post.id); + expect(response.body.content).toBe('content111222'); + }); + + it('belongsTo1', async () => { + const Post = db.getModel('posts'); + const post = await Post.create(); + const response = await request(http.createServer(app.callback())) + .get(`/posts/${post.id}/user?fields=name`); + expect(response.body).toEqual({}); + }); + + it('belongsTo2', async () => { + const Post = db.getModel('posts'); + const post = await Post.create(); + await post.updateAssociations({ + user: {name: 'name121234'}, + }); + const response = await request(http.createServer(app.callback())) + .get(`/posts/${post.id}/user?fields=name`); + expect(response.body).toEqual({name: 'name121234'}); + }); + + it('belongsToMany', async () => { + const Post = db.getModel('posts'); + const post = await Post.create(); + await post.updateAssociations({ + tags: [ + {name: 'tag112233'}, + ], + }); + const [tag] = await post.getTags(); + const response = await request(http.createServer(app.callback())) + .get(`/posts/${post.id}/tags/${tag.id}?fields=name,posts.id`); + expect(response.body.posts[0].id).toBe(post.id); + expect(response.body.name).toBe('tag112233'); + }); +}); diff --git a/packages/actions/src/__tests__/index.ts b/packages/actions/src/__tests__/index.ts new file mode 100644 index 000000000..ce025d829 --- /dev/null +++ b/packages/actions/src/__tests__/index.ts @@ -0,0 +1,75 @@ +import Database from '@nocobase/database'; +import Resourcer from '@nocobase/resourcer'; +import Koa from 'koa'; +import { Options } from 'sequelize'; +import bodyParser from 'koa-bodyparser'; +import associated from '../middlewares/associated'; +import actions from '..'; + +export const config: { + [key: string]: Options; +} = { + mysql: { + username: 'test', + password: 'test', + database: 'test', + host: '127.0.0.1', + port: 43306, + dialect: 'mysql', + }, + postgres: { + username: 'test', + password: 'test', + database: 'test', + host: '127.0.0.1', + port: 45432, + dialect: 'postgres', + define: { + hooks: { + beforeCreate(model, options) { + + }, + }, + }, + logging: false, + }, +}; + +export function getConfig() { + const app = new Koa(); + const database = new Database(config.postgres); + const resourcer = new Resourcer(); + resourcer.use(associated); + resourcer.registerHandlers({...actions.associate, ...actions.common}); + resourcer.define({ + name: 'posts', + actions: actions.common, + }); + resourcer.define({ + type: 'hasOne', + name: 'users.profile', + actions: actions.associate, + }); + resourcer.define({ + type: 'hasMany', + name: 'posts.comments', + actions: actions.associate, + }); + resourcer.define({ + type: 'belongsTo', + name: 'posts.user', + actions: actions.associate, + }); + resourcer.define({ + type: 'belongsToMany', + name: 'posts.tags', + actions: actions.associate, + }); + app.use(async (ctx, next) => { + ctx.db = database; + await next(); + }); + app.use(bodyParser()); + app.use(resourcer.middleware()); + return { app, database, resourcer }; +} \ No newline at end of file diff --git a/packages/actions/src/__tests__/list.test.ts b/packages/actions/src/__tests__/list.test.ts new file mode 100644 index 000000000..82c44cef1 --- /dev/null +++ b/packages/actions/src/__tests__/list.test.ts @@ -0,0 +1,148 @@ +import Koa from 'koa'; +import http from 'http'; +import request from 'supertest'; +import actions from '..'; +import { getConfig } from './index'; +import Database, { Model } from '@nocobase/database'; +import Resourcer from '@nocobase/resourcer'; +import { resolve } from 'path'; + +describe('list', () => { + let db: Database; + // let resourcer: Resourcer; + let app: Koa; + beforeAll(async () => { + const config = getConfig(); + app = config.app; + db = config.database; + db.import({ + directory: resolve(__dirname, './tables'), + }); + await db.sync({ + force: true, + }); + // resourcer = config.resourcer; + }); + + afterAll(async () => { + await db.close(); + }); + + describe('common', () => { + + beforeAll(async () => { + const Post = db.getModel('posts'); + const items = []; + for (let index = 0; index < 2; index++) { + items.push({ + title: `title${index}`, + status: 'common', + }); + } + await Post.bulkCreate(items); + }); + + it('list1', async () => { + const Post = db.getModel('posts'); + const response = await request(http.createServer(app.callback())).get('/posts?filter[status]=common'); + expect(response.body.count).toBe(await Post.count({where: {status: 'common'}})); + }); + + it('list2', async () => { + const Post = db.getModel('posts'); + const response = await request(http.createServer(app.callback())).get('/posts?filter[title]=title1'); + expect(response.body.count).toBe(await Post.count({ + where: { + title: 'title1', + }, + })); + }); + + it('list3', async () => { + const response = await request(http.createServer(app.callback())).get('/posts?filter[status]=common&fields=title&page=1'); + expect(response.body).toEqual({ + count: 2, + page: 1, + per_page: 20, + rows: [ { title: 'title0' }, { title: 'title1' } ], + }); + }); + + it('list4', async () => { + const response = await request(http.createServer(app.callback())).get('/posts?filter[status]=common&fields=title&sort=title&page=2&perPage=1'); + expect(response.body).toEqual({ + count: 2, + page: 2, + per_page: 1, + rows: [ { title: 'title1' } ], + }); + }); + + it('list5', async () => { + const response = await request(http.createServer(app.callback())).get('/posts?filter[status]=common&fields=title&page=2&per_page=1'); + expect(response.body).toEqual({ + count: 2, + page: 2, + per_page: 1, + rows: [ { title: 'title1' } ], + }); + }); + + it('list6', async () => { + const response = await request(http.createServer(app.callback())).get('/posts?fields=title&filter[customTitle]=title0&filter[status]=common'); + expect(response.body).toEqual({ count: 1, rows: [ { title: 'title0' } ] }); + }); + }); + + describe('hasMany', () => { + it('list1', async () => { + const Post = db.getModel('posts'); + const post = await Post.create(); + await post.updateAssociations({ + comments: [ + {content: 'content1', status: 'published'}, + {content: 'content2', status: 'published'}, + {content: 'content3', status: 'draft'}, + {content: 'content4', status: 'published'}, + {content: 'content5', status: 'draft'}, + {content: 'content6', status: 'published'}, + ], + }); + const response = await request(http.createServer(app.callback())) + .get(`/posts/${post.id}/comments?page=2&perPage=2&sort=content&fields=content&filter[published]=1`); + expect(response.body).toEqual({ + rows: [ { content: 'content4' }, { content: 'content6' } ], + count: 4, + page: 2, + per_page: 2 + }); + }); + }); + + describe('belongsToMany', () => { + it('list1', async () => { + const Post = db.getModel('posts'); + const post = await Post.create(); + await post.updateAssociations({ + tags: [ + {name: 'tag1', status: 'published'}, + {name: 'tag2', status: 'draft'}, + {name: 'tag3', status: 'published'}, + {name: 'tag4', status: 'draft'}, + {name: 'tag5', status: 'published'}, + {name: 'tag6', status: 'draft'}, + {name: 'tag7', status: 'published'}, + ], + }); + const response = await request(http.createServer(app.callback())) + .get(`/posts/${post.id}/tags?page=2&perPage=2&sort=name&fields=name&filter[published]=1`); + expect(response.body).toEqual({ + rows: [ { name: 'tag5' }, { name: 'tag7' } ], + count: 4, + page: 2, + per_page: 2 + }); + }); + }); + +}); diff --git a/packages/actions/src/__tests__/middleware.test.ts b/packages/actions/src/__tests__/middleware.test.ts new file mode 100644 index 000000000..c8a9180af --- /dev/null +++ b/packages/actions/src/__tests__/middleware.test.ts @@ -0,0 +1,73 @@ +import Koa from 'koa'; +import http from 'http'; +import request from 'supertest'; +import actions from '../'; +import { getConfig } from './index'; +import Database from '@nocobase/database'; +import Resourcer from '@nocobase/resourcer'; +import { Context } from '../actions'; +import jsonReponse from '../middlewares/json-reponse'; + +describe('middleware', () => { + let db: Database; + let resourcer: Resourcer; + let app: Koa; + beforeAll(async () => { + const config = getConfig(); + app = config.app; + db = config.database; + db.table({ + name: 'posts', + tableName: 'actions__m__posts', + fields: [ + { + type: 'string', + name: 'title', + }, + { + type: 'string', + name: 'status', + defaultValue: 'publish', + } + ], + scopes: { + customTitle: (title, ctx: Context) => { + return { + where: { + title: title, + }, + } + }, + } + }); + await db.sync({ + force: true, + }); + resourcer = config.resourcer; + resourcer.define({ + name: 'posts', + middlewares: [ + jsonReponse, + ], + actions: actions.common, + }); + }); + afterAll(async () => { + await db.close(); + }); + it('create', async () => { + const response = await request(http.createServer(app.callback())) + .post('/posts') + .send({ + title: 'title1', + }); + expect(response.body.data.title).toBe('title1'); + }); + it('list', async () => { + const response = await request(http.createServer(app.callback())).get('/posts?fields=title&page=1'); + expect(response.body).toEqual({ + data: [ { title: 'title1' } ], + meta: { count: 1, page: 1, per_page: 20 } + }); + }); +}); \ No newline at end of file diff --git a/packages/actions/src/__tests__/remove.test.ts b/packages/actions/src/__tests__/remove.test.ts new file mode 100644 index 000000000..3325f77fe --- /dev/null +++ b/packages/actions/src/__tests__/remove.test.ts @@ -0,0 +1,96 @@ +import Koa from 'koa'; +import http from 'http'; +import request from 'supertest'; +import actions from '..'; +import { getConfig } from './index'; +import Database, { Model } from '@nocobase/database'; +import Resourcer from '@nocobase/resourcer'; +import { resolve } from 'path'; + +describe('remove', () => { + let db: Database; + // let resourcer: Resourcer; + let app: Koa; + + beforeAll(async () => { + const config = getConfig(); + app = config.app; + db = config.database; + db.import({ + directory: resolve(__dirname, './tables'), + }); + await db.sync({ + force: true, + }); + // resourcer = config.resourcer; + }); + + afterAll(async () => { + await db.close(); + }); + + it('hasOne1', async () => { + const User = db.getModel('users'); + const user = await User.create(); + await user.updateAssociations({ + profile: { + email: 'email1122', + } + }); + const response = await request(http.createServer(app.callback())) + .post(`/users/${user.id}/profile:remove`); + const profile = await user.getProfile(); + expect(profile).toBeNull(); + }); + + it('hasMany1', async () => { + const Post = db.getModel('posts'); + const post = await Post.create(); + await post.updateAssociations({ + comments: [ + {content: 'content111222'}, + ], + }); + let [comment] = await post.getComments(); + await request(http.createServer(app.callback())) + .post(`/posts/${post.id}/comments:remove/${comment.id}`); + const count = await post.countComments(); + expect(count).toBe(0); + }); + + it('belongsTo1', async () => { + const Post = db.getModel('posts'); + let post = await Post.create(); + await post.updateAssociations({ + user: {name: 'name121234'}, + }); + await request(http.createServer(app.callback())).post(`/posts/${post.id}/user:remove`); + post = await Post.findOne({ + where: { + id: post.id, + } + }); + const user = await post.getUser(); + expect(user).toBeNull(); + }); + + it('belongsToMany', async () => { + const Post = db.getModel('posts'); + const post = await Post.create(); + await post.updateAssociations({ + tags: [ + { + name: 'tag112233', + posts_tags: { + test: 'test1', + } + }, + ], + }); + const [tag] = await post.getTags(); + await request(http.createServer(app.callback())) + .delete(`/posts/${post.id}/tags:remove/${tag.id}`); + const tags = await post.getTags(); + expect(tags.length).toBe(0); + }); +}); diff --git a/packages/actions/src/__tests__/set.test.ts b/packages/actions/src/__tests__/set.test.ts new file mode 100644 index 000000000..0606ab919 --- /dev/null +++ b/packages/actions/src/__tests__/set.test.ts @@ -0,0 +1,61 @@ +import Koa from 'koa'; +import http from 'http'; +import request from 'supertest'; +import actions from '..'; +import { getConfig } from './index'; +import Database, { Model } from '@nocobase/database'; +import Resourcer from '@nocobase/resourcer'; +import { resolve } from 'path'; + +describe('set', () => { + let db: Database; + // let resourcer: Resourcer; + let app: Koa; + + beforeAll(async () => { + const config = getConfig(); + app = config.app; + db = config.database; + db.import({ + directory: resolve(__dirname, './tables'), + }); + await db.sync({ + force: true, + }); + // resourcer = config.resourcer; + }); + + afterAll(async () => { + await db.close(); + }); + + it('belongsTo1', async () => { + const Post = db.getModel('posts'); + const User = db.getModel('users'); + let post = await Post.create(); + let user = await User.create(); + await request(http.createServer(app.callback())).post(`/posts/${post.id}/user:set/${user.id}`); + post = await Post.findOne({ + where: { + id: post.id, + } + }); + const postUser = await post.getUser(); + expect(user.id).toBe(postUser.id); + }); + + it('belongsToMany1', async () => { + const [Post, Tag] = db.getModels(['posts', 'tags']); + let post = await Post.create(); + let tag1 = await Tag.create({name: 'tag1'}); + let tag2 = await Tag.create({name: 'tag2'}); + await request(http.createServer(app.callback())).post(`/posts/${post.id}/tags:set/${tag1.id}`); + let [tag01] = await post.getTags(); + expect(tag1.id).toBe(tag01.id); + expect(await post.countTags()).toBe(1); + await request(http.createServer(app.callback())).post(`/posts/${post.id}/tags:set/${tag2.id}`); + let [tag02] = await post.getTags(); + expect(tag2.id).toBe(tag02.id); + expect(await post.countTags()).toBe(1); + }); +}); diff --git a/packages/actions/src/__tests__/tables/comments.ts b/packages/actions/src/__tests__/tables/comments.ts new file mode 100644 index 000000000..a19023006 --- /dev/null +++ b/packages/actions/src/__tests__/tables/comments.ts @@ -0,0 +1,31 @@ +import { TableOptions } from "@nocobase/database"; + +export default { + name: 'comments', + tableName: 'actions__comments', + fields: [ + { + type: 'string', + name: 'content', + }, + { + type: 'string', + name: 'status', + }, + { + type: 'belongsTo', + name: 'post', + }, + { + type: 'belongsTo', + name: 'user', + } + ], + scopes: { + published: { + where: { + status: 'published' + } + } + }, +} as TableOptions; diff --git a/packages/actions/src/__tests__/tables/posts.ts b/packages/actions/src/__tests__/tables/posts.ts new file mode 100644 index 000000000..a6b2f4d20 --- /dev/null +++ b/packages/actions/src/__tests__/tables/posts.ts @@ -0,0 +1,42 @@ +import { TableOptions } from "@nocobase/database"; + +export default { + name: 'posts', + tableName: 'actions__posts', + fields: [ + { + type: 'string', + name: 'title', + }, + { + type: 'string', + name: 'status', + defaultValue: 'publish', + }, + { + type: 'belongsTo', + name: 'user', + }, + { + type: 'hasmany', + name: 'comments', + }, + { + type: 'belongsToMany', + name: 'tags', + }, + ], + hooks: { + beforeCreate(model, options) { + }, + }, + scopes: { + customTitle: (title, ctx) => { + return { + where: { + title: title, + }, + } + }, + }, +} as TableOptions; diff --git a/packages/actions/src/__tests__/tables/posts_tags.ts b/packages/actions/src/__tests__/tables/posts_tags.ts new file mode 100644 index 000000000..039ddcda1 --- /dev/null +++ b/packages/actions/src/__tests__/tables/posts_tags.ts @@ -0,0 +1,12 @@ +import { TableOptions } from "@nocobase/database"; + +export default { + name: 'posts_tags', + tableName: 'actions__posts_tags', + fields: [ + { + type: 'string', + name: 'test', + }, + ], +} as TableOptions; diff --git a/packages/actions/src/__tests__/tables/profiles.ts b/packages/actions/src/__tests__/tables/profiles.ts new file mode 100644 index 000000000..992fdb885 --- /dev/null +++ b/packages/actions/src/__tests__/tables/profiles.ts @@ -0,0 +1,12 @@ +import { TableOptions } from "@nocobase/database"; + +export default { + name: 'profiles', + tableName: 'actions__profiles', + fields: [ + { + type: 'string', + name: 'email', + }, + ], +} as TableOptions; diff --git a/packages/actions/src/__tests__/tables/tags.ts b/packages/actions/src/__tests__/tables/tags.ts new file mode 100644 index 000000000..63368e711 --- /dev/null +++ b/packages/actions/src/__tests__/tables/tags.ts @@ -0,0 +1,27 @@ +import { TableOptions } from "@nocobase/database"; + +export default { + name: 'tags', + tableName: 'actions__tags', + fields: [ + { + type: 'string', + name: 'name', + }, + { + type: 'string', + name: 'status', + }, + { + type: 'belongsToMany', + name: 'posts', + }, + ], + scopes: { + published: { + where: { + status: 'published' + } + } + } +} as TableOptions; diff --git a/packages/actions/src/__tests__/tables/users.ts b/packages/actions/src/__tests__/tables/users.ts new file mode 100644 index 000000000..224ddf7f7 --- /dev/null +++ b/packages/actions/src/__tests__/tables/users.ts @@ -0,0 +1,16 @@ +import { TableOptions } from "@nocobase/database"; + +export default { + name: 'users', + tableName: 'actions__users', + fields: [ + { + type: 'string', + name: 'name', + }, + { + type: 'hasone', + name: 'profile', + }, + ], +} as TableOptions; diff --git a/packages/actions/src/__tests__/update.test.ts b/packages/actions/src/__tests__/update.test.ts new file mode 100644 index 000000000..5dfc71b6f --- /dev/null +++ b/packages/actions/src/__tests__/update.test.ts @@ -0,0 +1,106 @@ +import Koa from 'koa'; +import http from 'http'; +import request from 'supertest'; +import actions from '..'; +import { getConfig } from './index'; +import Database, { Model } from '@nocobase/database'; +import Resourcer from '@nocobase/resourcer'; +import { resolve } from 'path'; + +describe('update', () => { + let db: Database; + // let resourcer: Resourcer; + let app: Koa; + + beforeAll(async () => { + const config = getConfig(); + app = config.app; + db = config.database; + db.import({ + directory: resolve(__dirname, './tables'), + }); + await db.sync({ + force: true, + }); + // resourcer = config.resourcer; + }); + + afterAll(async () => { + await db.close(); + }); + + it('common1', async () => { + const Post = db.getModel('posts'); + const post = await Post.create(); + const response = await request(http.createServer(app.callback())) + .put(`/posts/${post.id}`).send({ + title: 'title11112222' + }); + expect(response.body.title).toBe('title11112222'); + }); + + it('hasOne1', async () => { + const User = db.getModel('users'); + const user = await User.create(); + const response = await request(http.createServer(app.callback())) + .put(`/users/${user.id}/profile`).send({ + email: 'email1122', + }); + expect(response.body.email).toEqual('email1122'); + }); + + it('hasMany1', async () => { + const Post = db.getModel('posts'); + const post = await Post.create(); + await post.updateAssociations({ + comments: [ + {content: 'content111222'}, + ], + }); + const [comment] = await post.getComments(); + const response = await request(http.createServer(app.callback())) + .put(`/posts/${post.id}/comments/${comment.id}`).send({content: 'content111222333'}); + expect(response.body.post_id).toBe(post.id); + expect(response.body.content).toBe('content111222333'); + }); + + it('belongsTo1', async () => { + const Post = db.getModel('posts'); + const post = await Post.create(); + await post.updateAssociations({ + user: {name: 'name121234'}, + }); + const response = await request(http.createServer(app.callback())) + .post(`/posts/${post.id}/user:update`).send({name: 'name1212345'}); + expect(response.body.name).toEqual('name1212345'); + }); + + it('belongsToMany', async () => { + const Post = db.getModel('posts'); + const post = await Post.create(); + await post.updateAssociations({ + tags: [ + {name: 'tag112233'}, + ], + }); + const [tag] = await post.getTags(); + let response = await request(http.createServer(app.callback())) + .post(`/posts/${post.id}/tags:update/${tag.id}`).send({ + name: 'tag11223344', + posts_tags: { + test: 'test1', + }, + }); + const [tag1] = await post.getTags(); + expect(tag1.posts_tags.test).toBe('test1'); + expect(response.body.name).toBe('tag11223344'); + response = await request(http.createServer(app.callback())) + .post(`/posts/${post.id}/tags:update/${tag.id}`).send({ + posts_tags: { + test: 'test112233', + }, + }); + const [tag2] = await post.getTags(); + expect(tag2.posts_tags.test).toBe('test112233'); + }); +}); diff --git a/packages/actions/src/actions/associate.ts b/packages/actions/src/actions/associate.ts new file mode 100644 index 000000000..23ee1f183 --- /dev/null +++ b/packages/actions/src/actions/associate.ts @@ -0,0 +1,206 @@ +import { Context, Next } from '.'; + +import { list, get, create, update, destroy } from './common'; +import { HasOne, BelongsTo, BelongsToMany, HasMany, Model, Relation } from '@nocobase/database'; +import { Op } from 'sequelize'; + +/** + * 建立关联 + * + * BlongsTo + * BlongsToMany + * + * @param ctx + * @param next + */ +export async function set(ctx: Context, next: Next) { + const { + associated, + resourceField, + associatedName, + } = ctx.action.params as { + associated: Model, + associatedName: string, + resourceField: Relation, + values: any, + }; + const AssociatedModel = ctx.db.getModel(associatedName); + if (!(associated instanceof AssociatedModel)) { + throw new Error(`${associatedName} associated model invalid`); + } + const { set: setAccessor } = resourceField.getAccessors(); + const { resourceKey, resourceKeyAttribute, fields = [] } = ctx.action.params; + const TargetModel = ctx.db.getModel(resourceField.getTarget()); + // const options = TargetModel.parseApiJson({ + // fields, + // }); + const model = await TargetModel.findOne({ + where: { + [resourceKeyAttribute || resourceField.options.targetKey || TargetModel.primaryKeyAttribute]: resourceKey, + }, + // @ts-ignore + context: ctx, + }); + ctx.body = await associated[setAccessor](model); + await next(); +} + +/** + * 附加关联 + * + * BlongsToMany + * + * @param ctx + * @param next + */ +export async function add(ctx: Context, next: Next) { + const { + associated, + resourceField, + associatedName, + } = ctx.action.params as { + associated: Model, + associatedName: string, + resourceField: Relation, + values: any, + }; + const AssociatedModel = ctx.db.getModel(associatedName); + if (!(associated instanceof AssociatedModel)) { + throw new Error(`${associatedName} associated model invalid`); + } + const { add: addAccessor } = resourceField.getAccessors(); + const { resourceKey, resourceKeyAttribute, fields = [] } = ctx.action.params; + const TargetModel = ctx.db.getModel(resourceField.getTarget()); + // const options = TargetModel.parseApiJson({ + // fields, + // }); + const model = await TargetModel.findOne({ + // ...options, + where: { + [resourceKeyAttribute || resourceField.options.targetKey || TargetModel.primaryKeyAttribute]: resourceKey, + }, + // @ts-ignore + context: ctx, + }); + ctx.body = await associated[addAccessor](model); + await next(); +} + +/** + * 删除关联 + * + * BlongsTo + * BlongsToMany + * + * @param ctx + * @param next + */ +export async function remove(ctx: Context, next: Next) { + const { + associated, + resourceField, + associatedName, + } = ctx.action.params as { + associated: Model, + associatedName: string, + resourceField: Relation, + values: any, + }; + const AssociatedModel = ctx.db.getModel(associatedName); + if (!(associated instanceof AssociatedModel)) { + throw new Error(`${associatedName} associated model invalid`); + } + const {get: getAccessor, remove: removeAccessor, set: setAccessor} = resourceField.getAccessors(); + const { resourceKey, resourceKeyAttribute, fields = [] } = ctx.action.params; + const TargetModel = ctx.db.getModel(resourceField.getTarget()); + const options = TargetModel.parseApiJson({ + fields, + }); + if (resourceField instanceof HasOne || resourceField instanceof BelongsTo) { + ctx.body = await associated[setAccessor](null); + } else if (resourceField instanceof HasMany || resourceField instanceof BelongsToMany) { + const [model]: Model[] = await associated[getAccessor]({ + ...options, + where: { + [resourceKeyAttribute || resourceField.options.targetKey || TargetModel.primaryKeyAttribute]: resourceKey, + }, + context: ctx, + }); + await associated[removeAccessor](model); + ctx.body = {id: model.id}; + } + await next(); +} + +export async function toggle(ctx: Context, next: Next) { + const { + associated, + resourceField, + associatedName, + } = ctx.action.params as { + associated: Model, + associatedName: string, + resourceField: Relation, + values: any, + }; + const AssociatedModel = ctx.db.getModel(associatedName); + if (!(associated instanceof AssociatedModel)) { + throw new Error(`${associatedName} associated model invalid`); + } + const {get: getAccessor, remove: removeAccessor, set: setAccessor, add: addAccessor} = resourceField.getAccessors(); + const { resourceKey, resourceKeyAttribute, fields = [] } = ctx.action.params; + const TargetModel = ctx.db.getModel(resourceField.getTarget()); + const options = TargetModel.parseApiJson({ + fields, + }); + if (resourceField instanceof HasOne || resourceField instanceof BelongsTo) { + const m1 = await associated[getAccessor](); + if (m1 && m1[resourceKeyAttribute || resourceField.options.targetKey || TargetModel.primaryKeyAttribute] == resourceKey) { + ctx.body = await associated[setAccessor](null); + } else { + const m2 = await TargetModel.findOne({ + // ...options, + where: { + [resourceKeyAttribute || resourceField.options.targetKey || TargetModel.primaryKeyAttribute]: resourceKey, + }, + // @ts-ignore + context: ctx, + }); + ctx.body = await associated[setAccessor](m2); + } + } else if (resourceField instanceof HasMany || resourceField instanceof BelongsToMany) { + const [model]: Model[] = await associated[getAccessor]({ + ...options, + where: { + [resourceKeyAttribute || resourceField.options.targetKey || TargetModel.primaryKeyAttribute]: resourceKey, + }, + context: ctx, + }); + if (model) { + ctx.body = await associated[removeAccessor](model); + } else { + const m2 = await TargetModel.findOne({ + // ...options, + where: { + [resourceKeyAttribute || resourceField.options.targetKey || TargetModel.primaryKeyAttribute]: resourceKey, + }, + // @ts-ignore + context: ctx, + }); + ctx.body = await associated[addAccessor](m2); + } + } + await next(); +} + +export default { + list, // hasMany、belongsToMany + get, // 所有关系都有 + create, // hasMany + update, // hasOne, hasMany, blongsToMany 中间表的数据更新 + destroy, // 所有情况 + set, // belongsTo、blongsToMany + add, // blongsToMany + remove, // belongsTo、blongsToMany + toggle, // blongsToMany +} diff --git a/packages/actions/src/actions/common.ts b/packages/actions/src/actions/common.ts new file mode 100644 index 000000000..185052648 --- /dev/null +++ b/packages/actions/src/actions/common.ts @@ -0,0 +1,358 @@ +import { Context, Next } from '.'; +import { Relation, Model, Field, HasOne, HasMany, BelongsTo, BelongsToMany } from '@nocobase/database'; +import { Utils, Op, Sequelize } from 'sequelize'; +import { isEmpty } from 'lodash'; + +/** + * 查询数据列表 + * + * - Signle + * - HasMany + * - BelongsToMany + * + * HasOne 和 belongsTo 不涉及到 list + * + * @param ctx + * @param next + */ +export async function list(ctx: Context, next: Next) { + const { + page, + perPage, + sort = [], + fields = [], + filter = {}, + associated, + associatedName, + resourceName, + resourceField, + } = ctx.action.params; + const Model = ctx.db.getModel(resourceName); + const options = Model.parseApiJson({ + sort, + page, + perPage, + filter, + fields, + context: ctx, + }); + let data = {}; + if (page || perPage) { + options.limit = 1*(perPage||20); + options.offset = options.limit * (page > 0 ? page - 1 : 0); + } + if (associated && resourceField) { + const AssociatedModel = ctx.db.getModel(associatedName); + if (!(associated instanceof AssociatedModel)) { + throw new Error(`${associatedName} associated model invalid`); + } + const getAccessor = resourceField.getAccessors().get; + const countAccessor = resourceField.getAccessors().count; + options.scope = options.scopes||[]; + const rows = await associated[getAccessor]({ + joinTableAttributes: [], + ...options, + context: ctx, + }); + delete options.attributes; + delete options.limit; + delete options.offset; + delete options.order; + if (options.include) { + options.include = options.include.map(includeOptions => { + includeOptions.attributes = []; + return includeOptions; + }); + } + const count = await associated[countAccessor]({ ...options, context: ctx }); + data = { + rows, + count, + }; + } else { + data = await Model.scope(options.scopes||[]).findAndCountAll({ + ...options, + // @ts-ignore hooks 里添加 context + context: ctx, + }); + } + if (page || perPage) { + data['page'] = 1*(page||1); + data[Utils.underscoredIf('perPage', Model.options.underscored)] = 1*(perPage||20); + } + ctx.body = data; + await next(); +} + +/** + * 新增数据 + * + * Signle + * HasMany + * + * resource action 层面一般不开放 HasOne、BelongsTo、BelongsToMany 的新增数据操作 + * 如果需要这类操作建议使用 model.updateAssociations 方法 + * + * TODO 字段验证 + * + * @param ctx + * @param next + */ +export async function create(ctx: Context, next: Next) { + const { + associated, + resourceField, + associatedName, + values, + } = ctx.action.params as { associated: Model, associatedName: string, resourceField: Relation, values: any }; + if (associated && resourceField) { + const AssociatedModel = ctx.db.getModel(associatedName); + if (!(associated instanceof AssociatedModel)) { + throw new Error(`${associatedName} associated model invalid`); + } + const create = resourceField.getAccessors().create; + const model: Model = await associated[create](values, { context: ctx }); + await model.updateAssociations(values, { context: ctx }); + ctx.body = model; + } else { + const { resourceName } = ctx.action.params; + const Model = ctx.db.getModel(resourceName); + // @ts-ignore + const model = await Model.create(values, { context: ctx }); + // @ts-ignore + await model.updateAssociations(values, { context: ctx }); + ctx.body = model; + } + await next(); +} + +/** + * 查询数据详情 + * + * @param ctx + * @param next + */ +export async function get(ctx: Context, next: Next) { + const { + associated, + resourceField, + associatedName, + } = ctx.action.params as { + associated: Model, + associatedName: string, + resourceField: Relation, + values: any, + }; + if (associated && resourceField) { + const AssociatedModel = ctx.db.getModel(associatedName); + if (!(associated instanceof AssociatedModel)) { + throw new Error(`${associatedName} associated model invalid`); + } + const getAccessor = resourceField.getAccessors().get; + const { resourceKey, resourceKeyAttribute, fields = [] } = ctx.action.params; + const TargetModel = ctx.db.getModel(resourceField.getTarget()); + const options = TargetModel.parseApiJson({ + fields, + }); + if (resourceField instanceof HasOne || resourceField instanceof BelongsTo) { + const model: Model = await associated[getAccessor]({ ...options, context: ctx }); + ctx.body = model; + } else if (resourceField instanceof HasMany || resourceField instanceof BelongsToMany) { + const [model]: Model[] = await associated[getAccessor]({ + ...options, + where: { + [resourceKeyAttribute || resourceField.options.targetKey || TargetModel.primaryKeyAttribute]: resourceKey, + }, + context: ctx, + }); + ctx.body = model; + } + } else { + const { resourceName, resourceKey, resourceKeyAttribute, fields = [] } = ctx.action.params; + const Model = ctx.db.getModel(resourceName); + const options = Model.parseApiJson({ + fields, + }); + const data = await Model.findOne({ + ...options, + where: { + [resourceKeyAttribute || Model.primaryKeyAttribute]: resourceKey, + }, + // @ts-ignore hooks 里添加 context + context: ctx, + }); + ctx.body = data; + } + await next(); +} + +/** + * 更新数据 + * + * TODO 字段验证 + * + * @param ctx + * @param next + */ +export async function update(ctx: Context, next: Next) { + const { + associated, + resourceField, + associatedName, + } = ctx.action.params as { + associated: Model, + associatedName: string, + resourceField: Relation, + values: any, + }; + if (associated && resourceField) { + const AssociatedModel = ctx.db.getModel(associatedName); + if (!(associated instanceof AssociatedModel)) { + throw new Error(`${associatedName} associated model invalid`); + } + const {get: getAccessor, create: createAccessor, add: addAccessor} = resourceField.getAccessors(); + const { resourceKey, resourceKeyAttribute, fields = [], values } = ctx.action.params; + const TargetModel = ctx.db.getModel(resourceField.getTarget()); + const options = TargetModel.parseApiJson({ + fields, + }); + if (resourceField instanceof HasOne || resourceField instanceof BelongsTo) { + let model: Model = await associated[getAccessor]({ ...options, context: ctx }); + if (model) { + // @ts-ignore + await model.update(values, { context: ctx }); + } else if (!model && resourceField instanceof HasOne) { + model = await associated[createAccessor](values, { context: ctx }); + } + await model.updateAssociations(values, { context: ctx }); + ctx.body = model; + } else if (resourceField instanceof HasMany || resourceField instanceof BelongsToMany) { + const [model]: Model[] = await associated[getAccessor]({ + ...options, + where: { + [resourceKeyAttribute || resourceField.options.targetKey || TargetModel.primaryKeyAttribute]: resourceKey, + }, + context: ctx, + }); + + if (resourceField instanceof BelongsToMany) { + const throughName = resourceField.getThroughName(); + if (typeof values[throughName] === 'object') { + const ThroughModel = resourceField.getThroughModel(); + const throughValues = values[throughName]; + const { foreignKey, sourceKey, otherKey } = resourceField.options; + const through = await ThroughModel.findOne({ + where: { + [foreignKey]: associated[sourceKey], + [otherKey]: resourceKey, + }, + }); + await through.update(throughValues); + await through.updateAssociations(throughValues); + delete values[throughName]; + } + } + if (!isEmpty(values)) { + // @ts-ignore + await model.update(values, { context: ctx }); + await model.updateAssociations(values, { context: ctx }); + } + ctx.body = model; + } + } else { + const { resourceName, resourceKey, resourceKeyAttribute, fields = [], values } = ctx.action.params; + const Model = ctx.db.getModel(resourceName); + const options = Model.parseApiJson({ + fields, + }); + const model = await Model.findOne({ + ...options, + where: { + [resourceKeyAttribute || Model.primaryKeyAttribute]: resourceKey, + }, + // @ts-ignore hooks 里添加 context + context: ctx, + }); + // @ts-ignore + await model.update(values, { context: ctx }); + // @ts-ignore + await model.updateAssociations(values, { context: ctx }); + ctx.body = model; + } + await next(); +} + +/** + * 删除数据,支持批量 + * + * Single + * HasOne + * HasMany + * + * TODO 关联数据的删除,建议在 onUpdate/onDelete 层面处理 + * + * @param ctx + * @param next + */ +export async function destroy(ctx: Context, next: Next) { + const { + associated, + resourceField, + associatedName, + } = ctx.action.params as { + associated: Model, + associatedName: string, + resourceField: Relation, + values: any, + }; + if (associated && resourceField) { + const AssociatedModel = ctx.db.getModel(associatedName); + if (!(associated instanceof AssociatedModel)) { + throw new Error(`${associatedName} associated model invalid`); + } + const {get: getAccessor, remove: removeAccessor, set: setAccessor} = resourceField.getAccessors(); + const { resourceKey, resourceKeyAttribute, fields = [] } = ctx.action.params; + const TargetModel = ctx.db.getModel(resourceField.getTarget()); + const options = TargetModel.parseApiJson({ + fields, + }); + if (resourceField instanceof HasOne || resourceField instanceof BelongsTo) { + const model: Model = await associated[getAccessor]({ ...options, context: ctx }); + await associated[setAccessor](null); + ctx.body = await model.destroy(); + } else if (resourceField instanceof HasMany || resourceField instanceof BelongsToMany) { + const [model]: Model[] = await associated[getAccessor]({ + ...options, + where: { + [resourceKeyAttribute || resourceField.options.targetKey || TargetModel.primaryKeyAttribute]: resourceKey, + }, + context: ctx, + }); + await associated[removeAccessor](model); + ctx.body = await model.destroy(); + } + } else { + const { resourceName, resourceKey, resourceKeyAttribute, values } = ctx.action.params; + const Model = ctx.db.getModel(resourceName); + const resourceKeys = resourceKey ? resourceKey.split(',') : values[`${resourceKeyAttribute || Model.primaryKeyAttribute}s`]; + const data = await Model.destroy({ + where: { + [resourceKeyAttribute || Model.primaryKeyAttribute]: { + [Op.in]: resourceKeys, + }, + }, + // @ts-ignore hooks 里添加 context + context: ctx, + }); + ctx.body = data; + } + await next(); +} + +export default { + list, // single、hasMany、belongsToMany + create, // signle、hasMany + get, // all + update, // single、 + destroy, +}; diff --git a/packages/actions/src/actions/index.ts b/packages/actions/src/actions/index.ts new file mode 100644 index 000000000..73bfb5dfa --- /dev/null +++ b/packages/actions/src/actions/index.ts @@ -0,0 +1,13 @@ +import Koa from 'koa'; +import Database from '@nocobase/database'; +import { Action } from '@nocobase/resourcer'; + +export type Next = () => Promise; + +export type Context = Koa.Context & { + db: Database; + action: Action; +}; + +export { default as common } from './common'; +export { default as associate } from './associate'; diff --git a/packages/actions/src/index.ts b/packages/actions/src/index.ts new file mode 100644 index 000000000..47d2550ee --- /dev/null +++ b/packages/actions/src/index.ts @@ -0,0 +1,5 @@ +import * as actions from './actions'; + +export * from './middleware'; + +export default actions; diff --git a/packages/actions/src/middleware.ts b/packages/actions/src/middleware.ts new file mode 100644 index 000000000..16aa5f897 --- /dev/null +++ b/packages/actions/src/middleware.ts @@ -0,0 +1,21 @@ +import { Context, Next } from './actions'; +import { Action } from '@nocobase/resourcer'; + +export async function middleware(ctx: Context, next: Next) { + await next(); + if (ctx.action instanceof Action) { + const { rows, ...meta } = ctx.body; + if (rows) { + ctx.body = { + data: rows, + meta, + }; + } else { + ctx.body = { + data: ctx.body, + }; + } + } +} + +export default middleware; diff --git a/packages/actions/src/middlewares/associated.ts b/packages/actions/src/middlewares/associated.ts new file mode 100644 index 000000000..2220ad5ad --- /dev/null +++ b/packages/actions/src/middlewares/associated.ts @@ -0,0 +1,59 @@ +import { Context, Next, associate } from '../actions'; +import { Action } from '@nocobase/resourcer'; +import { HasOne, HasMany, BelongsTo, BelongsToMany, Model } from '@nocobase/database'; + +export async function associated(ctx: Context, next: Next) { + if (!(ctx.action instanceof Action)) { + return next(); + } + + const { associated, associatedName, associatedKey, resourceName } = ctx.action.params; + + if (!associatedName || !associatedKey) { + return next(); + } + + if (associated) { + return next(); + } + + const Model = ctx.db.getModel(associatedName); + const field = ctx.db.getTable(associatedName).getField(resourceName); + + let model: Model; + + if (field instanceof HasOne) { + model = await Model.findOne({ + where: { + [field.options.sourceKey]: associatedKey, + } + }); + } else if (field instanceof HasMany) { + model = await Model.findOne({ + where: { + [field.options.sourceKey]: associatedKey, + } + }); + } else if (field instanceof BelongsTo) { + model = await Model.findOne({ + where: { + [Model.primaryKeyAttribute]: associatedKey, + } + }); + } else if (field instanceof BelongsToMany) { + model = await Model.findOne({ + where: { + [field.options.sourceKey]: associatedKey, + } + }); + } + + if (model) { + ctx.action.setParam('associated', model); + ctx.action.setParam('resourceField', field); + } + + await next(); +} + +export default associated; diff --git a/packages/actions/src/middlewares/json-reponse.ts b/packages/actions/src/middlewares/json-reponse.ts new file mode 100644 index 000000000..efab7d283 --- /dev/null +++ b/packages/actions/src/middlewares/json-reponse.ts @@ -0,0 +1,21 @@ +import { Context, Next } from '../actions'; +import { Action } from '@nocobase/resourcer'; + +export async function jsonResponse(ctx: Context, next: Next) { + await next(); + if (ctx.action instanceof Action) { + const { rows, ...meta } = ctx.body; + if (rows) { + ctx.body = { + data: rows, + meta, + }; + } else { + ctx.body = { + data: ctx.body, + }; + } + } +} + +export default jsonResponse; diff --git a/packages/api/.gitignore b/packages/api/.gitignore new file mode 100644 index 000000000..1be219988 --- /dev/null +++ b/packages/api/.gitignore @@ -0,0 +1,4 @@ +node_modules +yarn-error.log +.env +src2 \ No newline at end of file diff --git a/packages/api/Dockerfile b/packages/api/Dockerfile new file mode 100644 index 000000000..fc9816be4 --- /dev/null +++ b/packages/api/Dockerfile @@ -0,0 +1,15 @@ +FROM node:stretch + +WORKDIR /app +COPY . /app + +EXPOSE 23000 + +# # Install app dependencies +# ENV NPM_CONFIG_LOGLEVEL warn +# RUN yarn install + +# # Show current folder structure in logs +# RUN ls -al -R + +# CMD [ "npm", "run", "serve" ] diff --git a/packages/api/docker-compose.yml b/packages/api/docker-compose.yml new file mode 100644 index 000000000..b85c156c8 --- /dev/null +++ b/packages/api/docker-compose.yml @@ -0,0 +1,41 @@ +version: "3" +networks: + backend: + driver: bridge +services: + app: + build: + context: ./ + volumes: + - ./:/app + ports: + - "${HTTP_PORT}:23000" + command: [ "yarn", "start" ] + env_file: + - ./.env + networks: + - backend + mysql: + image: mysql:5.7 + environment: + MYSQL_DATABASE: ${DB_DATABASE} + MYSQL_USER: ${DB_USER} + MYSQL_PASSWORD: ${DB_PASSWORD} + MYSQL_ROOT_PASSWORD: ${DB_PASSWORD} + restart: always + ports: + - "23306:3306" + networks: + - backend + postgres: + image: postgres:10 + restart: always + ports: + - "25432:5432" + networks: + - backend + command: postgres -c wal_level=logical + environment: + POSTGRES_DB: ${DB_DATABASE} + POSTGRES_USER: ${DB_USER} + POSTGRES_PASSWORD: ${DB_PASSWORD} diff --git a/packages/api/example/index.ts b/packages/api/example/index.ts new file mode 100644 index 000000000..a4e640a6d --- /dev/null +++ b/packages/api/example/index.ts @@ -0,0 +1,42 @@ +import Api from '../src'; +import dotenv from 'dotenv'; + +const sync = { + force: true, + alter: { + drop: true, + }, +}; + +dotenv.config(); + +const api = Api.create({ + database: { + username: process.env.DB_USER, + password: process.env.DB_PASSWORD, + database: process.env.DB_DATABASE, + host: process.platform === 'linux' ? process.env.DB_HOST : 'localhost', + port: process.platform === 'linux' ? parseInt(process.env.DB_PORT) : ( process.env.DB_DIALECT == 'postgres' ? 25432 : 23306 ), + dialect: process.env.DB_DIALECT as any, + dialectOptions: { + charset: 'utf8mb4', + collate: 'utf8mb4_unicode_ci', + }, + // logging: false, + define: {}, + sync, + }, + resourcer: { + prefix: '/api', + }, +}); + +api + .plugins([ + [require('../../plugin-collections/src/index').default, {}], + ]) + .then(() => { + api.listen(23001, () => { + console.log('http://localhost:23001/'); + }); + }); diff --git a/packages/api/nodemon.json b/packages/api/nodemon.json new file mode 100644 index 000000000..1c3c15cda --- /dev/null +++ b/packages/api/nodemon.json @@ -0,0 +1,6 @@ +{ + "watch": ["src", ".env", ".env.dev", "../plugin-collections/src"], + "ext": "ts", + "ignore": ["src/**/*.test.ts"], + "exec": "ts-node ./example/index.ts" +} \ No newline at end of file diff --git a/packages/api/package.json b/packages/api/package.json new file mode 100644 index 000000000..f453936db --- /dev/null +++ b/packages/api/package.json @@ -0,0 +1,38 @@ +{ + "name": "@nocobase/api", + "version": "0.3.0-alpha.0", + "main": "lib/index.js", + "license": "MIT", + "scripts": { + "start": "nodemon", + "sync": "ts-node ./src/sync.ts", + "serve": "pm2-runtime start pm2.json", + "build": "tsc --declaration", + "db:sync": "docker-compose run app bash -c 'yarn sync'", + "logs": "docker-compose logs app" + }, + "dependencies": { + "@koa/cors": "^3.1.0", + "@koa/router": "^9.4.0", + "@nocobase/actions": "^0.3.0-alpha.0", + "@nocobase/database": "^0.3.0-alpha.0", + "@nocobase/resourcer": "^0.3.0-alpha.0", + "@types/koa": "^2.11.4", + "@types/koa-bodyparser": "^4.3.0", + "@types/koa__router": "^8.0.2", + "bcrypt": "^5.0.0", + "crypto-random-string": "^3.3.0", + "dotenv": "^8.2.0", + "koa": "^2.13.0", + "koa-bodyparser": "^4.3.0", + "mockjs": "^1.1.0", + "mysql": "^2.18.1", + "mysql2": "^2.1.0", + "nodemon": "^2.0.4", + "pg": "^8.3.3", + "pg-hstore": "^2.3.3", + "pm2": "^4.4.1", + "ts-node": "^9.0.0", + "typescript": "^4.0.2" + } +} diff --git a/packages/api/pm2.json b/packages/api/pm2.json new file mode 100755 index 000000000..c44d08639 --- /dev/null +++ b/packages/api/pm2.json @@ -0,0 +1,14 @@ +{ + "apps": [{ + "name": "nocobase-api", + "script": "lib/index.js", + "instances": 0, + "exec_mode": "cluster", + "env": { + "NODE_ENV": "development" + }, + "env_production" : { + "NODE_ENV": "production" + } + }] +} diff --git a/packages/api/src/index.ts b/packages/api/src/index.ts new file mode 100644 index 000000000..94c8f627a --- /dev/null +++ b/packages/api/src/index.ts @@ -0,0 +1,53 @@ +import Koa from 'koa'; +import Database from '@nocobase/database'; +import Resourcer from '@nocobase/resourcer'; +import actions from '@nocobase/actions'; + +export class Application extends Koa { + + database: Database; + + resourcer: Resourcer; + + async plugins(plugins: any[]) { + await Promise.all(plugins.map(async (pluginOption) => { + let plugin: Function; + let options = {}; + if (Array.isArray(pluginOption)) { + plugin = pluginOption.shift(); + plugin = plugin.bind(this); + options = pluginOption.shift()||{}; + } else if (typeof pluginOption === 'function') { + plugin = pluginOption.bind(this); + } + return await plugin(options); + })); + } +} + +export default { + create(options: any): Application { + console.log(options); + + const app = new Application(); + const resourcer = new Resourcer(); + const database = new Database(options.database); + + app.database = database; + app.resourcer = resourcer; + + resourcer.registerHandlers(actions.common); + + app.use(async (ctx, next) => { + ctx.db = database; + ctx.database = database; + await next(); + }); + + app.use(resourcer.middleware(options.resourcer || { + prefix: '/api', + })); + + return app; + } +} diff --git a/packages/create-nocobase-app/.local b/packages/create-nocobase-app/.local new file mode 100755 index 000000000..edbafef81 --- /dev/null +++ b/packages/create-nocobase-app/.local @@ -0,0 +1 @@ +Used in bin/create-nocobase-app.js to determine if it is in the local debug state. diff --git a/packages/create-nocobase-app/README.md b/packages/create-nocobase-app/README.md new file mode 100755 index 000000000..449b5843f --- /dev/null +++ b/packages/create-nocobase-app/README.md @@ -0,0 +1 @@ +# @nocobase/create-nocobase-app diff --git a/packages/create-nocobase-app/bin/create-nocobase-app.js b/packages/create-nocobase-app/bin/create-nocobase-app.js new file mode 100755 index 000000000..d2316ff6d --- /dev/null +++ b/packages/create-nocobase-app/bin/create-nocobase-app.js @@ -0,0 +1,3 @@ +#!/usr/bin/env node + +require('../lib/cli'); diff --git a/packages/create-nocobase-app/package.json b/packages/create-nocobase-app/package.json new file mode 100755 index 000000000..4a9057ab7 --- /dev/null +++ b/packages/create-nocobase-app/package.json @@ -0,0 +1,16 @@ +{ + "name": "@nocobase/create-nocobase-app", + "version": "0.3.0-alpha.0", + "description": "create-nocobase-app", + "main": "lib/index.js", + "types": "lib/index.d.ts", + "license": "MIT", + "scripts": { + }, + "dependencies": { + "@umijs/utils": "3.2.23" + }, + "bin": { + "create-nocobase-app": "bin/create-nocobase-app.js" + } +} diff --git a/packages/create-nocobase-app/src/AppGenerator/AppGenerator.ts b/packages/create-nocobase-app/src/AppGenerator/AppGenerator.ts new file mode 100755 index 000000000..cd5a854c3 --- /dev/null +++ b/packages/create-nocobase-app/src/AppGenerator/AppGenerator.ts @@ -0,0 +1,15 @@ +import { Generator } from '@umijs/utils'; +import { join } from 'path'; + +export default class AppGenerator extends Generator { + async writing() { + this.copyDirectory({ + context: { + version: require('../../package').version, + conventionRoutes: this.args.conventionRoutes, + }, + path: join(__dirname, '../../templates/AppGenerator'), + target: this.cwd, + }); + } +} diff --git a/packages/create-nocobase-app/src/app.ts b/packages/create-nocobase-app/src/app.ts new file mode 100755 index 000000000..e69de29bb diff --git a/packages/create-nocobase-app/src/cli.ts b/packages/create-nocobase-app/src/cli.ts new file mode 100755 index 000000000..c4b3d372c --- /dev/null +++ b/packages/create-nocobase-app/src/cli.ts @@ -0,0 +1,30 @@ +import { chalk, yParser } from '@umijs/utils'; +import { existsSync } from 'fs'; +import { join } from 'path'; + +const args = yParser(process.argv.slice(2), { + alias: { + version: ['v'], + help: ['h'], + }, + boolean: ['version'], +}); + +if (args.version && !args._[0]) { + args._[0] = 'version'; + const local = existsSync(join(__dirname, '../.local')) + ? chalk.cyan('@local') + : ''; + const { name, version } = require('../package.json'); + console.log(`${name}@${version}${local}`); +} else { + require('./') + .default({ + cwd: process.cwd(), + args, + }) + .catch((err: Error) => { + console.error(`Create failed, ${err.message}`); + console.error(err); + }); +} diff --git a/packages/create-nocobase-app/src/fixtures/.gitkeep b/packages/create-nocobase-app/src/fixtures/.gitkeep new file mode 100755 index 000000000..e69de29bb diff --git a/packages/create-nocobase-app/src/index.test.ts b/packages/create-nocobase-app/src/index.test.ts new file mode 100755 index 000000000..2afcb8ba4 --- /dev/null +++ b/packages/create-nocobase-app/src/index.test.ts @@ -0,0 +1,19 @@ +import { join } from 'path'; +import { rimraf } from '@umijs/utils'; +import { existsSync } from 'fs'; +import runGenerator from './index'; + +const fixtures = join(__dirname, 'fixtures'); +const cwd = join(fixtures, 'generate'); + +test('generate app', async () => { + await runGenerator({ + cwd, + args: { + _: [], + $0: '', + }, + }); + expect(existsSync(join(cwd, 'src', 'pages', 'index.tsx'))).toEqual(true); + rimraf.sync(cwd); +}); diff --git a/packages/create-nocobase-app/src/index.ts b/packages/create-nocobase-app/src/index.ts new file mode 100755 index 000000000..e79f1f6a5 --- /dev/null +++ b/packages/create-nocobase-app/src/index.ts @@ -0,0 +1,16 @@ +import { yargs } from '@umijs/utils'; +import AppGenerator from './AppGenerator/AppGenerator'; + +export default async ({ + cwd, + args, +}: { + cwd: string; + args: yargs.Arguments; +}) => { + const generator = new AppGenerator({ + cwd, + args, + }); + await generator.run(); +}; diff --git a/packages/create-nocobase-app/templates/AppGenerator/.editorconfig b/packages/create-nocobase-app/templates/AppGenerator/.editorconfig new file mode 100755 index 000000000..7e3649acc --- /dev/null +++ b/packages/create-nocobase-app/templates/AppGenerator/.editorconfig @@ -0,0 +1,16 @@ +# http://editorconfig.org +root = true + +[*] +indent_style = space +indent_size = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.md] +trim_trailing_whitespace = false + +[Makefile] +indent_style = tab diff --git a/packages/create-nocobase-app/templates/AppGenerator/.env b/packages/create-nocobase-app/templates/AppGenerator/.env new file mode 100644 index 000000000..e872ccbac --- /dev/null +++ b/packages/create-nocobase-app/templates/AppGenerator/.env @@ -0,0 +1,14 @@ +HTTP_PORT=23000 + +DB_DATABASE=test +DB_USER=test +DB_PASSWORD=test +# DB_HOST=mysql +# DB_PORT=3306 +# DB_DIALECT=mysql + +DB_HOST=postgres +DB_PORT=5432 +DB_DIALECT=postgres +# DB_PORT=25432 +# DB_HOST=localhost diff --git a/packages/create-nocobase-app/templates/AppGenerator/.fatherrc.ts b/packages/create-nocobase-app/templates/AppGenerator/.fatherrc.ts new file mode 100755 index 000000000..491de68f6 --- /dev/null +++ b/packages/create-nocobase-app/templates/AppGenerator/.fatherrc.ts @@ -0,0 +1,10 @@ +export default { + entry: 'src/api', + target: 'node', + cjs: { type: 'babel', lazy: true }, + include: 'api/*', + disableTypeCheck: true, + // pkgs: [ + // 'api', + // ], +}; diff --git a/packages/create-nocobase-app/templates/AppGenerator/.gitignore.tpl b/packages/create-nocobase-app/templates/AppGenerator/.gitignore.tpl new file mode 100755 index 000000000..bee1cf61c --- /dev/null +++ b/packages/create-nocobase-app/templates/AppGenerator/.gitignore.tpl @@ -0,0 +1,20 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/npm-debug.log* +/yarn-error.log +/yarn.lock +/package-lock.json + +# production +/dist + +# misc +.DS_Store + +# umi +/src/.umi +/src/.umi-production +/src/.umi-test +/.env.local diff --git a/packages/create-nocobase-app/templates/AppGenerator/.prettierignore b/packages/create-nocobase-app/templates/AppGenerator/.prettierignore new file mode 100755 index 000000000..0d4222f54 --- /dev/null +++ b/packages/create-nocobase-app/templates/AppGenerator/.prettierignore @@ -0,0 +1,8 @@ +**/*.md +**/*.svg +**/*.ejs +**/*.html +package.json +.umi +.umi-production +.umi-test diff --git a/packages/create-nocobase-app/templates/AppGenerator/.prettierrc b/packages/create-nocobase-app/templates/AppGenerator/.prettierrc new file mode 100755 index 000000000..94beb1484 --- /dev/null +++ b/packages/create-nocobase-app/templates/AppGenerator/.prettierrc @@ -0,0 +1,11 @@ +{ + "singleQuote": true, + "trailingComma": "all", + "printWidth": 80, + "overrides": [ + { + "files": ".prettierrc", + "options": { "parser": "json" } + } + ] +} diff --git a/packages/create-nocobase-app/templates/AppGenerator/.umirc.ts.tpl b/packages/create-nocobase-app/templates/AppGenerator/.umirc.ts.tpl new file mode 100755 index 000000000..226c48b20 --- /dev/null +++ b/packages/create-nocobase-app/templates/AppGenerator/.umirc.ts.tpl @@ -0,0 +1,12 @@ +import { defineConfig } from 'umi'; + +export default defineConfig({ + nodeModulesTransform: { + type: 'none', + }, +{{ ^conventionRoutes }} + routes: [ + { path: '/', component: '@/pages/index' }, + ], +{{ /conventionRoutes }} +}); diff --git a/packages/create-nocobase-app/templates/AppGenerator/README.md b/packages/create-nocobase-app/templates/AppGenerator/README.md new file mode 100755 index 000000000..e521300a0 --- /dev/null +++ b/packages/create-nocobase-app/templates/AppGenerator/README.md @@ -0,0 +1,15 @@ +# NocoBase Application + +## Getting Started + +Install dependencies, + +```bash +$ yarn install +``` + +Start the dev server, + +```bash +$ yarn start +``` diff --git a/packages/create-nocobase-app/templates/AppGenerator/mock/.gitkeep b/packages/create-nocobase-app/templates/AppGenerator/mock/.gitkeep new file mode 100755 index 000000000..e69de29bb diff --git a/packages/create-nocobase-app/templates/AppGenerator/nodemon.json b/packages/create-nocobase-app/templates/AppGenerator/nodemon.json new file mode 100644 index 000000000..36d1db809 --- /dev/null +++ b/packages/create-nocobase-app/templates/AppGenerator/nodemon.json @@ -0,0 +1,5 @@ +{ + "watch": ["src/api", ".env"], + "ext": "ts", + "exec": "ts-node ./src/api/index.ts" +} \ No newline at end of file diff --git a/packages/create-nocobase-app/templates/AppGenerator/package.json.tpl b/packages/create-nocobase-app/templates/AppGenerator/package.json.tpl new file mode 100755 index 000000000..96cfacb8c --- /dev/null +++ b/packages/create-nocobase-app/templates/AppGenerator/package.json.tpl @@ -0,0 +1,38 @@ +{ + "private": true, + "scripts": { + "start": "concurrently \"nodemon\" \"umi dev\"", + "build": "father-build && umi build", + "postinstall": "umi generate tmp", + "prettier": "prettier --write '**/*.{js,jsx,tsx,ts,less,md,json}'", + "test": "umi-test", + "test:coverage": "umi-test --coverage" + }, + "gitHooks": { + "pre-commit": "lint-staged" + }, + "lint-staged": { + "*.{js,jsx,less,md,json}": [ + "prettier --write" + ], + "*.ts?(x)": [ + "prettier --parser=typescript --write" + ] + }, + "dependencies": { + "@ant-design/pro-layout": "^5.0.12", + "@umijs/preset-react": "1.x", + "@umijs/test": "^3.2.23", + "@nocobase/api": "^{{{ version }}}", + "@nocobase/father-build": "^{{{ version }}}", + "@nocobase/plugin-collections": "^{{{ version }}}", + "concurrently": "^5.3.0", + "lint-staged": "^10.0.7", + "nodemon": "^2.0.6", + "prettier": "^1.19.1", + "react": "^16.12.0", + "react-dom": "^16.12.0", + "umi": "^3.2.23", + "yorkie": "^2.0.0" + } +} diff --git a/packages/create-nocobase-app/templates/AppGenerator/server.js b/packages/create-nocobase-app/templates/AppGenerator/server.js new file mode 100644 index 000000000..549072ca8 --- /dev/null +++ b/packages/create-nocobase-app/templates/AppGenerator/server.js @@ -0,0 +1,7 @@ +const api = require('@nocobase/api'); + +require('dotenv').config(); + +api.listen(23000, () => { + console.log('http://localhost:23000/'); +}); diff --git a/packages/create-nocobase-app/templates/AppGenerator/src/api/index.ts b/packages/create-nocobase-app/templates/AppGenerator/src/api/index.ts new file mode 100644 index 000000000..4c08fbb7b --- /dev/null +++ b/packages/create-nocobase-app/templates/AppGenerator/src/api/index.ts @@ -0,0 +1,42 @@ +import Api from '@nocobase/api'; +import dotenv from 'dotenv'; + +const sync = { + force: true, + alter: { + drop: true, + }, +}; + +dotenv.config(); + +const api = Api.create({ + database: { + username: process.env.DB_USER, + password: process.env.DB_PASSWORD, + database: process.env.DB_DATABASE, + host: process.platform === 'linux' ? process.env.DB_HOST : 'localhost', + port: process.platform === 'linux' ? process.env.DB_PORT : ( process.env.DB_DIALECT == 'postgres' ? 25432 : 23306 ), + dialect: process.env.DB_DIALECT as any, + dialectOptions: { + charset: 'utf8mb4', + collate: 'utf8mb4_unicode_ci', + }, + // logging: false, + define: {}, + sync, + }, + resourcer: { + prefix: '/api', + }, +}); + +api + .plugins([ + [require('@nocobase/plugin-collections').default, {}], + ]) + .then(() => { + api.listen(23001, () => { + console.log('http://localhost:23001/'); + }); + }); diff --git a/packages/create-nocobase-app/templates/AppGenerator/src/pages/index.less b/packages/create-nocobase-app/templates/AppGenerator/src/pages/index.less new file mode 100755 index 000000000..fd9812c34 --- /dev/null +++ b/packages/create-nocobase-app/templates/AppGenerator/src/pages/index.less @@ -0,0 +1,7 @@ + +.normal { +} + +.title { + background: rgb(121, 242, 157); +} diff --git a/packages/create-nocobase-app/templates/AppGenerator/src/pages/index.tsx b/packages/create-nocobase-app/templates/AppGenerator/src/pages/index.tsx new file mode 100755 index 000000000..9171ffc40 --- /dev/null +++ b/packages/create-nocobase-app/templates/AppGenerator/src/pages/index.tsx @@ -0,0 +1,10 @@ +import React from 'react'; +import styles from './index.less'; + +export default () => { + return ( +
+

Page index

+
+ ); +} diff --git a/packages/create-nocobase-app/templates/AppGenerator/tsconfig.json b/packages/create-nocobase-app/templates/AppGenerator/tsconfig.json new file mode 100755 index 000000000..9c54bd969 --- /dev/null +++ b/packages/create-nocobase-app/templates/AppGenerator/tsconfig.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "lib": [ "es5", "es6" ], + "module": "commonjs", + "target": "es6", + "moduleResolution": "node", + "importHelpers": true, + "jsx": "react", + "esModuleInterop": true, + "sourceMap": true, + "baseUrl": "./", + "strict": true, + "paths": { + "@/*": ["src/*"], + "@@/*": ["src/.umi/*"] + }, + "allowSyntheticDefaultImports": true + }, + "include": [ + "mock/**/*", + "src/**/*", + "config/**/*", + ".umirc.ts", + "typings.d.ts" + ] +} diff --git a/packages/create-nocobase-app/templates/AppGenerator/typings.d.ts b/packages/create-nocobase-app/templates/AppGenerator/typings.d.ts new file mode 100755 index 000000000..5df84ee02 --- /dev/null +++ b/packages/create-nocobase-app/templates/AppGenerator/typings.d.ts @@ -0,0 +1,8 @@ +declare module '*.css'; +declare module '*.less'; +declare module "*.png"; +declare module '*.svg' { + export function ReactComponent(props: React.SVGProps): React.ReactElement + const url: string + export default url +} diff --git a/packages/create-nocobase-app/tsconfig.json b/packages/create-nocobase-app/tsconfig.json new file mode 100644 index 000000000..0843093a6 --- /dev/null +++ b/packages/create-nocobase-app/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "lib": [ "es5", "es6" ], + "module": "commonjs", + "target": "es6", + "allowJs": true, + "declaration": false, + "resolveJsonModule": true, + "esModuleInterop": true, + "sourceMap": false, + "baseUrl": "./", + "paths": { + } + }, + "exclude": [ + "node_modules" + ] +} \ No newline at end of file diff --git a/packages/database/examples/index.ts b/packages/database/examples/index.ts new file mode 100644 index 000000000..8905be620 --- /dev/null +++ b/packages/database/examples/index.ts @@ -0,0 +1,125 @@ +import Database from '../src'; +import path from 'path'; +import dbDriven from './plugins/db-driven'; + +const sync = { + force: true, + alter: { + drop: true, + } +} + +const db = new Database({ + username: 'test', + password: 'test', + database: 'test', + host: '127.0.0.1', + port: 45432, + dialect: 'postgres', + logging: false, + define: { + }, + sync, +}); + +(async () => { + + const tables = db.import({ + directory: path.resolve(__dirname, 'tables'), + }); + + await db.sync({ tables }); + + await db.plugin(dbDriven()); + + if (!sync.force) { + await db.sequelize.drop(); + await db.sync(); + } + + const [Table, Field] = db.getModels(['tables', 'fields']); + + const [table] = await Table.findOrCreate({ + where: { + name: 'demos', + }, + defaults: { + options: { + name: 'demos', + }, + }, + }); + + await db.getModel('demos').create({}); + + await Field.bulkCreate([ + { + name: 'col1', + table_name: 'demos', + options: { + type: 'string', + name: 'col1', + }, + }, + { + name: 'col2', + table_name: 'demos', + options: { + type: 'string', + name: 'col2', + }, + }, + ]); + + await db.getModel('demos').create({ + col1: 'col1', + col2: 'col2', + }); + + await table.createField({ + name: 'col3', + options: { + type: 'string', + name: 'col3', + }, + }); + + await db.getModel('demos').create({ + col1: 'col1', + col2: 'col2', + col3: 'col3', + }); + + await table.createField({ + name: 'col4', + options: { + type: 'string', + name: 'col4', + }, + }); + + await db.getModel('demos').create({ + col1: 'col1', + col2: 'col2', + col3: 'col3', + col4: 'col4', + }); + + await table.createField({ + name: 'col5', + options: { + type: 'string', + name: 'col5', + }, + }); + + await db.getModel('demos').create({ + col1: 'col1', + col2: 'col2', + col3: 'col3', + col4: 'col4', + col5: 'col5', + }); + + await db.close(); +})(); diff --git a/packages/database/examples/plugins/db-driven/index.ts b/packages/database/examples/plugins/db-driven/index.ts new file mode 100644 index 000000000..f59914ca3 --- /dev/null +++ b/packages/database/examples/plugins/db-driven/index.ts @@ -0,0 +1,25 @@ +import path from 'path'; +import Database, { Model } from '../../../src'; + +export default (options?: any) => { + return async (db: Database) => { + const tables = db.import({ + directory: path.resolve(__dirname, 'tables'), + }); + + await db.sync({ tables }); + + const Table = db.getModel('tables'); + const items = await Table.findAll(); + + await Promise.all(items.map(async item => { + const fields: Model[] = await item.getFields(); + const table = db.table({ + ...item.options, + fields: fields.map(field => field.options), + }); + })); + + await db.sync({ tables: items.map(item => item.name) }); + } +} \ No newline at end of file diff --git a/packages/database/examples/plugins/db-driven/tables/fields.ts b/packages/database/examples/plugins/db-driven/tables/fields.ts new file mode 100644 index 000000000..61960748d --- /dev/null +++ b/packages/database/examples/plugins/db-driven/tables/fields.ts @@ -0,0 +1,78 @@ +import Database, { TableOptions, Table } from '../../../../src'; + +export default (db: Database) => ({ + name: 'fields', + tableName: 'nocobase_fields', + fields: [ + { + type: 'string', + name: 'table_name', + }, + { + type: 'string', + name: 'name', + index: { + fields: ['table_name', 'name'], + unique: true, + }, + }, + { + type: 'json', + name: 'options', + }, + { + type: 'belongsTo', + name: 'table', + foreignKey: 'table_name', + targetKey: 'name', + }, + ], + hooks: { + async afterCreate(model: any) { + if (!model.table_name) { + return; + } + const table = db.getTable(model.table_name); + table.addField(model.options); + // console.log(table); + await table.sync({ + force: false, + alter: { + drop: false, + } + }); + }, + async afterUpdate(model: any) { + if (!model.table_name) { + return; + } + const table = db.getTable(model.table_name); + table.addField(model.options); + await table.sync({ + force: false, + alter: { + drop: false, + } + }); + }, + async afterBulkCreate(models) { + const tables = new Map(); + for (const model of new Map(Object.entries(models)).values()) { + if (!model.table_name) { + return; + } + const table = db.getTable(model.table_name); + table.addField(model.options); + tables.set(table.getName(), table); + } + for (const table of tables.values()) { + await table.sync({ + force: false, + alter: { + drop: false, + } + }); + } + }, + } +} as TableOptions); diff --git a/packages/database/examples/plugins/db-driven/tables/tables.ts b/packages/database/examples/plugins/db-driven/tables/tables.ts new file mode 100644 index 000000000..be27fa09f --- /dev/null +++ b/packages/database/examples/plugins/db-driven/tables/tables.ts @@ -0,0 +1,53 @@ +import { FindOrCreateOptions } from 'sequelize'; +import Database, { TableOptions, Model } from '../../../../src'; + +export class Table extends Model { + +} + +export default (db: Database) => ({ + name: 'tables', + tableName: 'nocobase_tables', + model: Table, + fields: [ + { + type: 'string', + name: 'name', + unique: true, + }, + { + type: 'json', + name: 'options', + }, + { + type: 'hasMany', + name: 'fields', + sourceKey: 'name', + foreignKey: 'table_name', + }, + ], + hooks: { + async afterCreate(model: Table) { + const fields: Model[] = await model.getFields(); + const table = db.table({...model.options, fields: fields.map(field => field.options)}); + await table.sync(); + }, + async afterUpdate(model: Table) { + const fields: Model[] = await model.getFields(); + const table = db.table({...model.options, fields: fields.map(field => field.options)}); + await table.sync({ + force: false, + alter: { + drop: false, + } + }); + }, + async afterBulkCreate(models: Table[]) { + await Promise.all(models.map(async (model: any) => { + const fields: Model[] = await model.getFields(); + const table = db.table({...model.options, fields: fields.map(field => field.options)}); + await table.sync(); + })); + }, + }, +} as TableOptions); diff --git a/packages/database/examples/tables/bar.js b/packages/database/examples/tables/bar.js new file mode 100644 index 000000000..32eaf0ee3 --- /dev/null +++ b/packages/database/examples/tables/bar.js @@ -0,0 +1,26 @@ +module.exports = { + name: 'bar', + fields: [ + { + type: 'string', + name: 'title', + }, + { + type: 'text', + length: 'long', + name: 'content', + }, + { + type: 'belongsTo', + name: 'user', + }, + { + type: 'belongsToMany', + name: 'tags', + }, + { + type: 'hasMany', + name: 'comments' + }, + ], +}; diff --git a/packages/database/examples/tables/comments.ts b/packages/database/examples/tables/comments.ts new file mode 100644 index 000000000..5cdba71db --- /dev/null +++ b/packages/database/examples/tables/comments.ts @@ -0,0 +1,19 @@ +import { TableOptions } from '../../src'; + +export default { + name: 'comments', + fields: [ + { + type: 'text', + name: 'content', + }, + { + type: 'belongsTo', + name: 'user', + }, + { + type: 'belongsTo', + name: 'post', + }, + ], +} as TableOptions; \ No newline at end of file diff --git a/packages/database/examples/tables/foo.json b/packages/database/examples/tables/foo.json new file mode 100644 index 000000000..fb4eeaffd --- /dev/null +++ b/packages/database/examples/tables/foo.json @@ -0,0 +1,3 @@ +{ + "name": "foo" +} \ No newline at end of file diff --git a/packages/database/examples/tables/posts.ts b/packages/database/examples/tables/posts.ts new file mode 100644 index 000000000..260e2d736 --- /dev/null +++ b/packages/database/examples/tables/posts.ts @@ -0,0 +1,28 @@ +import { TableOptions } from '../../src'; + +export default { + name: 'posts', + fields: [ + { + type: 'string', + name: 'title', + }, + { + type: 'text', + length: 'long', + name: 'content', + }, + { + type: 'belongsTo', + name: 'user', + }, + { + type: 'belongsToMany', + name: 'tags', + }, + { + type: 'hasMany', + name: 'comments' + }, + ], +} as TableOptions; \ No newline at end of file diff --git a/packages/database/examples/tables/profiles.ts b/packages/database/examples/tables/profiles.ts new file mode 100644 index 000000000..cee70d5ad --- /dev/null +++ b/packages/database/examples/tables/profiles.ts @@ -0,0 +1,23 @@ +import { TableOptions } from '../../src'; + +export default { + name: 'profiles', + fields: [ + { + type: 'string', + name: 'realname', + }, + { + type: 'string', + name: 'email', + }, + { + type: 'string', + name: 'gender', + }, + { + type: 'date', + name: 'birthday', + }, + ], +} as TableOptions; \ No newline at end of file diff --git a/packages/database/examples/tables/tags.ts b/packages/database/examples/tables/tags.ts new file mode 100644 index 000000000..b956a9adc --- /dev/null +++ b/packages/database/examples/tables/tags.ts @@ -0,0 +1,15 @@ +import { TableOptions } from '../../src'; + +export default { + name: 'tags', + fields: [ + { + type: 'string', + name: 'name', + }, + { + type: 'belongsToMany', + name: 'posts', + }, + ], +} as TableOptions; \ No newline at end of file diff --git a/packages/database/examples/tables/users.ts b/packages/database/examples/tables/users.ts new file mode 100644 index 000000000..88572a47a --- /dev/null +++ b/packages/database/examples/tables/users.ts @@ -0,0 +1,34 @@ +import { TableOptions } from '../../src'; + +export default { + name: 'users', + fields: [ + { + type: 'string', + name: 'username', + unique: true, + }, + { + type: 'string', + name: 'password', + // index: true, + }, + { + type: 'string', + name: 'openid', + index: true, + }, + { + type: 'hasOne', + name: 'profile', + }, + { + type: 'hasMany', + name: 'posts', + }, + { + type: 'hasMany', + name: 'comments', + } + ], +} as TableOptions; \ No newline at end of file diff --git a/packages/database/package.json b/packages/database/package.json new file mode 100644 index 000000000..bf61bdd94 --- /dev/null +++ b/packages/database/package.json @@ -0,0 +1,23 @@ +{ + "name": "@nocobase/database", + "version": "0.3.0-alpha.0", + "description": "", + "main": "./lib/index.js", + "types": "./lib/index.d.ts", + "scripts": { + }, + "license": "MIT", + "dependencies": { + "bcrypt": "^5.0.0", + "glob": "^7.1.6", + "sequelize": "^6.3.3" + }, + "devDependencies": { + "typescript": "^3.9.6" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/nocobase/nocobase.git", + "directory": "packages/database" + } +} diff --git a/packages/database/src/__tests__/associations.test.ts b/packages/database/src/__tests__/associations.test.ts new file mode 100644 index 000000000..de4d22750 --- /dev/null +++ b/packages/database/src/__tests__/associations.test.ts @@ -0,0 +1,694 @@ +import { getDatabase } from './'; +import { + HasMany, HasOne, Integer, BelongsTo, BelongsToMany +} from '../fields'; +import { DataTypes } from 'sequelize'; + +describe('associations', () => { + describe('hasOne', () => { + it('shound be defaults', async () => { + const db = getDatabase(); + db.table({ + name: 'foos', + }); + db.table({ + name: 'bars', + fields: [ + { + type: 'hasone', + name: 'foo', + }, + ], + }); + const field: HasOne = db.getTable('bars').getField('foo'); + expect(field.options.target).toBe('foos'); + expect(field.options.foreignKey).toBe('bar_id'); + expect(field.options.sourceKey).toBe('id'); + }); + it('shound be ok when the association table is defined later', async () => { + const db = getDatabase(); + db.table({ + name: 'bars', + fields: [ + { + type: 'hasone', + name: 'foo', + }, + ], + }); + db.table({ + name: 'foos', + }); + const field: HasOne = db.getTable('bars').getField('foo'); + expect(field.options.target).toBe('foos'); + expect(field.options.foreignKey).toBe('bar_id'); + expect(field.options.sourceKey).toBe('id'); + }); + it('shound be custom when target is defined', () => { + const db = getDatabase(); + db.table({ + name: 'foos', + }); + db.table({ + name: 'bars', + fields: [ + { + type: 'hasone', + name: 'foo2', + target: 'foos', + }, + ], + }); + const field: HasOne = db.getTable('bars').getField('foo2'); + expect(field.options.target).toBe('foos'); + expect(field.options.foreignKey).toBe('bar_id'); + expect(field.options.sourceKey).toBe('id'); + }); + + it('shound be custom when sourceKey is defined', () => { + const db = getDatabase(); + db.table({ + name: 'foos', + }); + db.table({ + name: 'bars', + fields: [ + { + type: 'hasone', + name: 'foo', + sourceKey: 'sid', + }, + ], + }); + const field: HasOne = db.getTable('bars').getField('foo'); + expect(field.options.target).toBe('foos'); + expect(field.options.foreignKey).toBe('bar_sid'); + expect(field.options.sourceKey).toBe('sid'); + }); + + it('shound be integer type when the column of sourceKey does not exist', () => { + const db = getDatabase(); + db.table({ + name: 'foos', + }); + db.table({ + name: 'bars', + fields: [ + { + type: 'hasone', + name: 'foo', + sourceKey: 'sid', + }, + ], + }); + const field: HasOne = db.getTable('bars').getField('foo'); + expect(field.options.target).toBe('foos'); + expect(field.options.foreignKey).toBe('bar_sid'); + expect(field.options.sourceKey).toBe('sid'); + const sourceKeyColumn: Integer = db.getTable('bars').getField('sid'); + expect(sourceKeyColumn).toBeInstanceOf(Integer); + expect(sourceKeyColumn.options.unique).toBe(true); + }); + }); + + describe('hasMany', () => { + it('shound be defaults', async () => { + const db = getDatabase(); + db.table({ + name: 'foo', + }); + db.table({ + name: 'bar', + fields: [ + { + type: 'hasMany', + name: 'foo', + }, + ], + }); + const field: HasMany = db.getTable('bar').getField('foo'); + expect(field.options.target).toBe('foo'); + expect(field.options.foreignKey).toBe('bar_id'); + expect(field.options.sourceKey).toBe('id'); + }); + it('shound be ok when the association table is defined later', async () => { + const db = getDatabase(); + db.table({ + name: 'bars', + fields: [ + { + type: 'hasMany', + name: 'foos', + }, + ], + }); + db.table({ + name: 'foos', + }); + const field: HasMany = db.getTable('bars').getField('foos'); + expect(field.options.target).toBe('foos'); + expect(field.options.foreignKey).toBe('bar_id'); + expect(field.options.sourceKey).toBe('id'); + }); + it('shound be custom when target is defined', () => { + const db = getDatabase(); + db.table({ + name: 'foos', + }); + db.table({ + name: 'bars', + fields: [ + { + type: 'hasMany', + name: 'foo2', + target: 'foos', + }, + ], + }); + const field: HasMany = db.getTable('bars').getField('foo2'); + expect(field.options.target).toBe('foos'); + expect(field.options.foreignKey).toBe('bar_id'); + expect(field.options.sourceKey).toBe('id'); + }); + + it('shound be custom when sourceKey is defined', () => { + const db = getDatabase(); + db.table({ + name: 'foos', + }); + db.table({ + name: 'bars', + fields: [ + { + type: 'hasMany', + name: 'foos', + sourceKey: 'sid', + }, + ], + }); + const field: HasMany = db.getTable('bars').getField('foos'); + expect(field.options.target).toBe('foos'); + expect(field.options.foreignKey).toBe('bar_sid'); + expect(field.options.sourceKey).toBe('sid'); + }); + + it('shound be integer type when the column of sourceKey does not exist', () => { + const db = getDatabase(); + db.table({ + name: 'foos', + }); + db.table({ + name: 'bars', + fields: [ + { + type: 'hasMany', + name: 'foos', + sourceKey: 'sid', + }, + ], + }); + const field: HasMany = db.getTable('bars').getField('foos'); + expect(field.options.target).toBe('foos'); + expect(field.options.foreignKey).toBe('bar_sid'); + expect(field.options.sourceKey).toBe('sid'); + const sourceKeyColumn: Integer = db.getTable('bars').getField('sid'); + expect(sourceKeyColumn).toBeInstanceOf(Integer); + expect(sourceKeyColumn.options.unique).toBe(true); + }); + }); + + describe('belongsTo', () => { + it('shound be custom foreignKey', async () => { + const db = getDatabase(); + db.table({ + name: 'bars', + fields: [ + { + type: 'belongsTo', + name: 'foo', + }, + ], + }); + db.table({ + name: 'foos', + }); + const field: BelongsTo = db.getTable('bars').getField('foo'); + expect(field.options.targetKey).toBe('id'); + expect(field.options.foreignKey).toBe('foo_id'); + }); + it('shound be custom foreignKey', async () => { + const db = getDatabase(); + db.table({ + name: 'bars', + fields: [ + { + type: 'belongsTo', + name: 'custom_foo', + target: 'foos', + }, + ], + }); + db.table({ + name: 'foos', + }); + const field: BelongsTo = db.getTable('bars').getField('custom_foo'); + expect(field.options.targetKey).toBe('id'); + expect(field.options.foreignKey).toBe('custom_foo_id'); + }); + it('shound be custom primaryKey', () => { + const db = getDatabase(); + db.table({ + name: 'foos', + fields: [ + { + type: 'integer', + name: 'fid', + primaryKey: true, + autoIncrement: true, + } + ], + }); + db.table({ + name: 'bars', + fields: [ + { + type: 'belongsTo', + name: 'foo', + }, + ], + }); + const field: BelongsTo = db.getTable('bars').getField('foo'); + expect(field.options.target).toBe('foos'); + expect(field.options.targetKey).toBe('fid'); + expect(field.options.foreignKey).toBe('foo_fid'); + }); + it('shound be custom primaryKey', () => { + const db = getDatabase(); + db.table({ + name: 'bars', + fields: [ + { + type: 'belongsTo', + name: 'foo', + }, + ], + }); + db.table({ + name: 'foos', + fields: [ + { + type: 'integer', + name: 'fid', + primaryKey: true, + autoIncrement: true, + } + ], + }); + const field: BelongsTo = db.getTable('bars').getField('foo'); + expect(field.options.target).toBe('foos'); + expect(field.options.targetKey).toBe('fid'); + expect(field.options.foreignKey).toBe('foo_fid'); + }); + it('shound throw error', () => { + const db = getDatabase(); + db.table({ + name: 'foos', + }); + expect(() => { + db.table({ + name: 'bars', + fields: [ + { + type: 'belongsTo', + name: 'foo', + targetKey: 'fid', + }, + ], + }); + }).toThrow('Unknown attribute "fid" passed as targetKey, define this attribute on model "foos" first') + }); + it('shound be ok when the association table is defined later', async () => { + const db = getDatabase(); + db.table({ + name: 'bars', + fields: [ + { + type: 'belongsTo', + name: 'foo', + targetKey: 'fid', + }, + ], + }); + db.table({ + name: 'foos', + fields: [ + { + type: 'integer', + name: 'fid', + }, + ], + }); + const field: BelongsTo = db.getTable('bars').getField('foo'); + expect(field.options.targetKey).toBe('fid'); + expect(field.options.foreignKey).toBe('foo_fid'); + }); + + it('shound work', async () => { + const db = getDatabase(); + db.table({ + name: 'rows', + fields: [ + { + type: 'string', + name: 'name', + unique: true, + }, + { + type: 'hasMany', + name: 'columns', + sourceKey: 'name', + } + ], + }); + db.table({ + name: 'columns', + fields: [ + { + type: 'belongsTo', + name: 'row', + targetKey: 'name', + }, + { + type: 'string', + name: 'name', + } + ], + }); + const f1: BelongsTo = db.getTable('columns').getField('row'); + expect(f1.options.foreignKey).toBe('row_name'); + const f2: HasMany = db.getTable('rows').getField('columns'); + expect(f2.options.foreignKey).toBe('row_name'); + }); + }); + + describe('belongsToMany', () => { + it('shound be defaults', async () => { + const db = getDatabase({ + logging: false, + }); + db.table({ + name: 'foos', + fields: [ + { + type: 'belongsToMany', + name: 'bars', + }, + ], + }); + db.table({ + name: 'bars_foos', + fields: [ + { + type: 'string', + name: 'name', + }, + ], + }); + db.table({ + name: 'bars', + fields: [ + { + type: 'belongsToMany', + name: 'foos', + }, + ], + }); + // console.log(db.getModel('bars_foos').rawAttributes); + // await db.sync({ + // force: true, + // }); + let field: BelongsToMany; + field = db.getTable('bars').getField('foos'); + expect(field.options.target).toBe('foos'); + expect(field.options.through).toBe('bars_foos'); + expect(field.options.sourceKey).toBe('id'); + expect(field.options.foreignKey).toBe('bar_id'); + expect(field.options.targetKey).toBe('id'); + expect(field.options.otherKey).toBe('foo_id'); + field = db.getTable('foos').getField('bars'); + expect(field.options.target).toBe('bars'); + expect(field.options.through).toBe('bars_foos'); + expect(field.options.sourceKey).toBe('id'); + expect(field.options.foreignKey).toBe('foo_id'); + expect(field.options.targetKey).toBe('id'); + expect(field.options.otherKey).toBe('bar_id'); + + expect(db.getModel('foos').associations.bars).toBeDefined(); + expect(db.getModel('bars').associations.foos).toBeDefined(); + }); + + it('shound be correct when use custom primary key', async () => { + const db = getDatabase(); + db.table({ + name: 'foos', + fields: [ + { + type: 'integer', + autoIncrement: true, + name: 'fid', + primaryKey: true, + }, + { + type: 'belongsToMany', + name: 'bars', + }, + ], + }); + db.table({ + name: 'bars', + fields: [ + { + type: 'integer', + autoIncrement: true, + name: 'bid', + primaryKey: true, + }, + { + type: 'belongsToMany', + name: 'foos', + }, + ], + }); + // await db.sync({force: true}); + // await db.sequelize.close(); + let field: BelongsToMany; + field = db.getTable('bars').getField('foos'); + expect(field.options.target).toBe('foos'); + expect(field.options.through).toBe('bars_foos'); + expect(field.options.sourceKey).toBe('bid'); + expect(field.options.foreignKey).toBe('bar_bid'); + expect(field.options.targetKey).toBe('fid'); + expect(field.options.otherKey).toBe('foo_fid'); + field = db.getTable('foos').getField('bars'); + expect(field.options.target).toBe('bars'); + expect(field.options.through).toBe('bars_foos'); + expect(field.options.sourceKey).toBe('fid'); + expect(field.options.foreignKey).toBe('foo_fid'); + expect(field.options.targetKey).toBe('bid'); + expect(field.options.otherKey).toBe('bar_bid'); + expect(db.getModel('foos').associations.bars).toBeDefined(); + expect(db.getModel('bars').associations.foos).toBeDefined(); + const { foos: barAssociation } = db.getModel('bars').associations as any; + expect(barAssociation.target.name).toBe('foos'); + expect(barAssociation.through.model.name).toBe('bars_foos'); + expect(barAssociation.sourceKey).toBe('bid'); + expect(barAssociation.foreignKey).toBe('bar_bid'); + expect(barAssociation.targetKey).toBe('fid'); + expect(barAssociation.otherKey).toBe('foo_fid'); + const { bars: fooAssociation } = db.getModel('foos').associations as any; + expect(fooAssociation.target.name).toBe('bars'); + expect(fooAssociation.through.model.name).toBe('bars_foos'); + expect(fooAssociation.sourceKey).toBe('fid'); + expect(fooAssociation.foreignKey).toBe('foo_fid'); + expect(fooAssociation.targetKey).toBe('bid'); + expect(fooAssociation.otherKey).toBe('bar_bid'); + }); + + it('through be defined after source and target', async () => { + const db = getDatabase({ + logging: false, + }); + db.table({ + name: 'foos', + fields: [ + { + type: 'belongsToMany', + name: 'bars', + }, + ], + }); + db.table({ + name: 'bars', + fields: [ + { + type: 'belongsToMany', + name: 'foos', + }, + ], + }); + db.table({ + name: 'bars_foos', + fields: [ + { + type: 'string', + name: 'name', + }, + ], + }); + // await db.sync({ + // force: true, + // }); + const Through = db.getModel('bars_foos'); + expect(Through.rawAttributes.name).toBeDefined(); + expect(Through.rawAttributes.foo_id).toBeDefined(); + expect(Through.rawAttributes.bar_id).toBeDefined(); + }); + + it('through be defined after source', async () => { + const db = getDatabase({ + logging: false, + }); + db.table({ + name: 'foos', + fields: [ + { + type: 'belongsToMany', + name: 'bars', + }, + ], + }); + db.table({ + name: 'bars_foos', + fields: [ + { + type: 'string', + name: 'name', + }, + ], + }); + db.table({ + name: 'bars', + fields: [ + { + type: 'belongsToMany', + name: 'foos', + }, + ], + }); + + // await db.sync({ + // force: true, + // }); + const Through = db.getModel('bars_foos'); + expect(Through.rawAttributes.name).toBeDefined(); + expect(Through.rawAttributes.foo_id).toBeDefined(); + expect(Through.rawAttributes.bar_id).toBeDefined(); + }); + + it('#', () => { + const db = getDatabase({ + logging: false, + }); + db.table({ + name: 'posts', + fields: [ + { + type: 'string', + name: 'slug', + unique: true, + }, + { + type: 'belongsToMany', + name: 'tags', + sourceKey: 'slug', + targetKey: 'name', + }, + ], + }); + db.table({ + name: 'tags', + fields: [ + { + type: 'string', + name: 'name', + unique: true, + }, + { + type: 'belongsToMany', + name: 'posts', + sourceKey: 'name', + targetKey: 'slug', + }, + ], + }); + + const f1: BelongsToMany = db.getTable('posts').getField('tags'); + expect(f1.options).toEqual({ + target: 'tags', + through: 'posts_tags', + sourceKey: 'slug', + foreignKey: 'post_slug', + type: 'BELONGSTOMANY', + name: 'tags', + targetKey: 'name', + otherKey: 'tag_name' + }); + const f2: BelongsToMany = db.getTable('tags').getField('posts'); + expect(f2.options).toEqual({ + target: 'posts', + through: 'posts_tags', + sourceKey: 'name', + foreignKey: 'tag_name', + type: 'BELONGSTOMANY', + name: 'posts', + targetKey: 'slug', + otherKey: 'post_slug' + }); + }); + }); + + it('shound be defined', () => { + const db = getDatabase(); + db.table({ + name: 'bars', + fields: [ + { + type: 'string', + name: 'foo_name', + }, + { + type: 'belongsTo', + name: 'foo', + foreignKey: 'foo_name', + targetKey: 'name', + } + ], + }) + db.table({ + name: 'foos', + fields: [ + { + type: 'string', + name: 'name', + unique: true, + }, + { + type: 'hasMany', + name: 'bars', + sourceKey: 'name', + foreignKey: 'foo_name' + }, + ], + }); + }); +}); diff --git a/packages/database/src/__tests__/index.ts b/packages/database/src/__tests__/index.ts new file mode 100644 index 000000000..09c59c7b7 --- /dev/null +++ b/packages/database/src/__tests__/index.ts @@ -0,0 +1,42 @@ +import Database from '../database'; +import { Options } from 'sequelize'; +import path from 'path'; + +require('dotenv').config({ + path: path.resolve(__dirname, '.env'), +}) + +export const config: { + [key: string]: Options; +} = { + mysql: { + username: 'test', + password: 'test', + database: 'test', + host: '127.0.0.1', + port: 43306, + dialect: 'mysql', + }, + postgres: { + username: 'test', + password: 'test', + database: 'test', + host: '127.0.0.1', + port: 45432, + dialect: 'postgres', + define: { + hooks: { + beforeCreate(model, options) { + + }, + }, + }, + // logging: false, + }, +}; + +export function getDatabase(options: Options = {}) { + // console.log(process.env.DIALECT); + const db = new Database({...config[process.env.DIALECT||'postgres'], ...options}); + return db; +}; diff --git a/packages/database/src/__tests__/model.test.ts b/packages/database/src/__tests__/model.test.ts new file mode 100644 index 000000000..b9d6c4108 --- /dev/null +++ b/packages/database/src/__tests__/model.test.ts @@ -0,0 +1,970 @@ +import { getDatabase } from '.'; +import Database from '..'; +import { Op, Sequelize } from 'sequelize'; +import Model from '../model'; +import { BelongsToMany } from '../fields'; +import { Mode } from 'fs'; + +describe('actions', () => { + let db: Database; + + beforeAll(async () => { + db = getDatabase({ + logging: false, + }); + + db.table({ + name: 'users', + tableName: 'user1234', + fields: [ + { + type: 'string', + name: 'name', + }, + { + type: 'hasone', + name: 'profile', + }, + { + type: 'hasMany', + name: 'posts', + }, + { + type: 'hasMany', + name: 'posts_title1', + target: 'posts', + scope: { + title: 'title1', + }, + } + ] + }); + + db.table({ + name: 'profiles', + tableName: 'profile1234', + fields: [ + { + type: 'string', + name: 'name', + }, + ] + }); + + db.table({ + name: 'posts', + tableName: 'post123456', + fields: [ + { + type: 'string', + name: 'title', + }, + { + type: 'belongsTo', + name: 'user', + }, + { + type: 'belongsToMany', + name: 'tags', + }, + { + type: 'belongsToMany', + name: 'tags_name1', + target: 'tags', + scope: { + name: 'name1', + }, + }, + { + type: 'hasmany', + name: 'comments', + }, + { + type: 'hasmany', + name: 'current_user_comments', + target: 'comments', + }, + ] + }); + + db.table({ + name: 'posts_tags', + tableName: 'posts_tags1234', + fields: [ + { + type: 'string', + name: 'name', + }, + ] + }); + + db.table({ + name: 'tags', + tableName: 'tag1234', + fields: [ + { + type: 'string', + name: 'name', + }, + { + type: 'belongsToMany', + name: 'posts', + }, + ], + }); + + db.table({ + name: 'comments', + tableName: 'comment1234', + fields: [ + { + type: 'belongsTo', + name: 'user', + }, + { + type: 'belongsTo', + name: 'post', + }, + { + type: 'string', + name: 'content', + } + ] + }); + + db.table({ + name: 'tables', + fields: [ + { + type: 'string', + name: 'name', + primaryKey: true, + autoIncrement: false, + }, + { + type: 'hasMany', + name: 'fields', + } + ], + }); + + db.table({ + name: 'fields', + fields: [ + { + type: 'belongsTo', + name: 'table', + }, + { + type: 'string', + name: 'name', + } + ], + }); + + db.table({ + name: 'rows', + fields: [ + { + type: 'string', + name: 'name', + unique: true, + }, + { + type: 'hasMany', + name: 'columns', + sourceKey: 'name', + } + ], + }); + + db.table({ + name: 'columns', + fields: [ + { + type: 'belongsTo', + name: 'row', + targetKey: 'name', + }, + { + type: 'string', + name: 'name', + } + ], + }); + + await db.sync({ + force: true, + }); + + }); + + afterAll(async () => { + await db.close(); + }); + + it('through attributes', async () => { + const [Post, Tag] = db.getModels(['posts', 'tags']); + const post = await Post.create(); + const tag = await Tag.create(); + await post.updateAssociations({ + tags: [{ + name: 'xxx', + posts_tags: { + name: 'name134', + } + }, { + id: tag.id, + posts_tags: { + name: 'name234', + } + }], + }); + const PostTag = db.getModel('posts_tags'); + const [t1, t2] = await PostTag.findAll({ + where: { + post_id: post.id, + }, + order: ['tag_id'], + }); + expect(t1.name).toBe('name234'); + expect(t2.name).toBe('name134'); + }); + + describe('scope', () => { + it('scope', async () => { + const [User, Post, Comment] = db.getModels(['users', 'posts', 'comments']); + const user1 = await User.create(); + const user2 =await User.create(); + const user3 =await User.create(); + const user4 =await User.create(); + const post = await Post.create(); + const comment = await Comment.create(); + comment.updateAssociations({ + post: post, + user: user1, + }); + await post.updateAssociations({ + comments: [ + { + content: 'content1', + user: user1, + }, + { + content: 'content2', + user: user2, + }, + { + content: 'content3', + user: user3, + }, + { + content: 'content4', + user: user4, + }, + ], + }); + const comments = await post.getCurrent_user_comments(); + // console.log(comments); + }); + }); + + describe('findByApiJson', () => { + it('q', async () => { + + db.getModel('tags').addScope('scopeName', (name, ctx) => { + expect(ctx.scopeName).toBe(name); + console.log(ctx); + return { + where: { + name: name, + } + } + }); + + const [User, Post] = db.getModels(['users', 'posts']); + const postData = []; + for (let index = 0; index < 20; index++) { + postData.push({ + title: `title${index}`, + }); + } + const user = await User.create({ + name: 'name112233', + }); + await Post.create({ + title: 'xxxx', + }); + const post = await Post.create({ + title: 'title112233', + }); + await user.updateAssociations({ + posts: post, + }); + await post.updateAssociations({ + tags: [ + {name: 'tag1'}, + {name: 'tag2'}, + {name: 'tag3'}, + ], + }); + // where & include + const options = Post.parseApiJson({ + filter: { + title: 'title112233', + user: { // belongsTo + name: 'name112233', + }, + tags: { // belongsToMany + scopeName: 'tag3', + }, + }, + fields: [ + 'title', + 'tags_count', + 'tags.name', + 'user.name' + ], + sort: '-tags_count,tags.name,user.posts_count', + context: { + scopeName: 'tag3', + }, + }); + + const { rows, count } = await Post.findAndCountAll({ + ...options, + // group: ['id'], + // limit: 20, + // offset: 20, + }); + + // console.log(JSON.stringify(rows[0].toJSON(), null, 2)); + + rows.forEach(post => { + // expect(post.toJSON()).toEqual({ title: 'title112233', 'tags_count': 3, user: { name: 'name112233', posts_count: 1 } }); + expect(post.get('title')).toBe('title112233'); + expect(post.user.get('name')).toBe('name112233'); + }); + + // console.log(count); + + // expect(count).toBe(1); + }); + }); + + describe('query', () => { + it('to be explained', async () => { + const [User, Post] = db.getModels(['users', 'posts']); + const postData = []; + for (let index = 0; index < 20; index++) { + postData.push({ + title: `title${index}`, + }); + } + const user = await User.create(); + let posts = await Post.bulkCreate(postData); + await user.updateAssociations({ + posts: posts, + }); + const userOne = await User.findOne({ + attributes: { + exclude: ['updated_at'], + include: [ + User.withCountAttribute('posts'), + User.withCountAttribute('posts_title1'), + ], + }, + where: { + id: user.id, + }, + }); + + expect(userOne.get('posts_count')).toBe(20); + expect(userOne.get('posts_title1_count')).toBe(1); + }); + + it('to be explained', async () => { + const [User, Post] = db.getModels(['users', 'posts']); + const postData = []; + for (let index = 0; index < 20; index++) { + postData.push({ + title: `title${index}`, + }); + } + const user = await User.create(); + let posts = await Post.bulkCreate(postData); + await user.updateAssociations({ + posts: posts, + }); + const userOne = await User.findOne({ + attributes: { + exclude: ['updated_at'], + include: [ + User.withCountAttribute({ + association: 'posts', + alias: 'posts2_count' + }), + ], + }, + where: { + id: user.id, + }, + }); + + expect(userOne.get('posts2_count')).toBe(20); + }); + + it('to be explained', async () => { + const [Tag, Post, User] = db.getModels(['tags', 'posts', 'users']); + const tagData = []; + for (let index = 0; index < 5; index++) { + tagData.push({ + name: `name${index}`, + }); + } + let post = await Post.create(); + let tags = await Tag.bulkCreate(tagData); + await post.updateAssociations({ + user: { + name: 'user1', + }, + tags, + }); + post = await Post.findOne({ + attributes: { + include: [ + 'id', + Post.withCountAttribute('tags'), + Post.withCountAttribute('tags_name1'), + ], + }, + where: { + id: post.id, + }, + include: [ + { + association: 'user', + attributes: ['id', 'name', + User.withCountAttribute({ + sourceAlias: 'user', + association: 'posts', + }), + ], + }, + { + association: 'tags', + attributes: ['id', 'name', Tag.withCountAttribute('posts')], + }, + ], + }); + expect(post.get('tags_count')).toBe(5); + expect(post.get('tags_name1_count')).toBe(1); + expect(post.user.get('posts_count')).toBe(1); + post.tags.forEach(tag => { + expect(tag.get('posts_count')).toBe(1); + }); + }); + }); + + describe('hasOne', () => { + it('shoud associated id when association is integer', async () => { + const [User, Profile] = db.getModels(['users', 'profiles']); + const user = await User.create(); + const profile = await Profile.create(); + await user.updateAssociations({ + // 关联 id + profile, + }); + const userProfile = await user.getProfile(); + expect(userProfile.id).toBe(profile.id); + }); + it('shoud associated id when association is integer', async () => { + const [User, Profile] = db.getModels(['users', 'profiles']); + const user = await User.create(); + const profile = await Profile.create(); + await user.updateAssociations({ + // 关联 id + profile: profile.id, + }); + const userProfile = await user.getProfile(); + expect(userProfile.id).toBe(profile.id); + }); + it('shoud associated id when association is integer', async () => { + const [User, Profile] = db.getModels(['users', 'profiles']); + const user = await User.create(); + const profile = await Profile.create(); + await user.updateAssociations({ + // 关联 id + profile: { + id: profile.id, + }, + }); + const userProfile = await user.getProfile(); + expect(userProfile.id).toBe(profile.id); + }); + it('shoud associated id when association is integer', async () => { + const [User, Profile] = db.getModels(['users', 'profiles']); + const user = await User.create(); + const profile = await Profile.create(); + await user.updateAssociations({ + // 关联 id + profile: { + id: profile.id, + name: 'profile1', + }, + }); + const userProfile = await user.getProfile(); + expect(userProfile.id).toBe(profile.id); + expect(userProfile.name).toBe('profile1'); + }); + it('shoud associated id when association is integer', async () => { + const [User] = db.getModels(['users']); + const user = await User.create(); + await user.updateAssociations({ + // 关联 id + profile: { + name: 'profile2', + }, + }); + const userProfile = await user.getProfile(); + expect(userProfile.name).toBe('profile2'); + }); + }); + + describe('hasMany', () => { + it('@1', async () => { + const [Comment, Post] = db.getModels(['comments', 'posts']); + const comment = await Comment.create(); + const post = await Post.create(); + await post.updateAssociations({ + comments: comment, + }); + const count = await post.countComments(); + expect(count).toBe(1); + }); + it('@2', async () => { + const [Comment, Post] = db.getModels(['comments', 'posts']); + const comment = await Comment.create(); + const post = await Post.create(); + await post.updateAssociations({ + comments: [comment], + }); + const count = await post.countComments(); + expect(count).toBe(1); + }); + it('@3', async () => { + const [Comment, Post] = db.getModels(['comments', 'posts']); + const comment = await Comment.create(); + const post = await Post.create(); + await post.updateAssociations({ + comments: [comment.id], + }); + const count = await post.countComments(); + expect(count).toBe(1); + }); + it('@4', async () => { + const [Comment, Post] = db.getModels(['comments', 'posts']); + const comment = await Comment.create(); + const post = await Post.create(); + await post.updateAssociations({ + comments: comment.id, + }); + const count = await post.countComments(); + expect(count).toBe(1); + }); + it('@5', async () => { + const [Post] = db.getModels(['posts']); + const post = await Post.create(); + await post.updateAssociations({ + comments: { + content: 'content1', + }, + }); + const count = await post.countComments(); + expect(count).toBe(1); + }); + it('@6', async () => { + const [Post, Comment] = db.getModels(['posts', 'comments']); + const post = await Post.create(); + const comment1 = await Comment.create(); + const comment2 = await Comment.create(); + await post.updateAssociations({ + comments: [ + { + content: 'content2', + }, + { + content: 'content3', + }, + { + id: comment1.id, + }, + comment2, + ], + }); + const count = await post.countComments(); + expect(count).toBe(4); + }); + + it('shoud work1', async () => { + const [Table, Field] = db.getModels(['tables', 'fields']); + const table = await Table.create({ + name: 'examples', + }); + await table.updateAssociations({ + fields: [ + {name: 'name'}, + ] + }); + const field = await Field.findOne({ + where: { + table_name: 'examples', + name: 'name', + }, + }); + expect(field).toBeDefined(); + expect(field.get('name')).toBe('name'); + }); + + it('shoud work2', async () => { + const [Row, Column] = db.getModels(['rows', 'columns']); + const row = await Row.create({ + name: 'examples', + }); + await row.updateAssociations({ + columns: [ + {name: 'name'}, + ] + }); + const column = await Column.findOne({ + where: { + row_name: 'examples', + name: 'name', + }, + }); + expect(column).toBeDefined(); + expect(column.get('name')).toBe('name'); + }); + + it('shoud work3', async () => { + const [Table, Field] = db.getModels(['tables', 'fields']); + const table = await Table.create({ + name: 'abcdef', + }); + const field = await Field.create({name: 'name123'}); + await table.updateAssociations({ + fields: [ + { + id: field.id, + name: 'nam1234', + }, + ] + }); + + const f = await Field.findOne({ + where: { + table_name: 'abcdef', + name: 'nam1234', + }, + }); + + expect(f).toBeDefined(); + expect(f.id).toBe(field.id); + + const options = Table.parseApiJson({ + fields: ['name', 'fields_count'], + }); + const t = await Table.findOne(options); + + expect(t.get('fields_count')).toBe(1); + }); + }); + + describe('blongsTo', () => { + + it('shoud associated id when association is integer', async () => { + const [User, Post] = db.getModels(['users', 'posts']); + const user = await User.create(); + const post = await Post.create(); + await post.updateAssociations({ + // 关联 id + user, + }); + expect(user.id).toBe(post.user_id); + const postUser = await post.getUser(); + expect(user.id).toBe(postUser.id); + }); + + it('shoud associated id when association is integer', async () => { + const [User, Post] = db.getModels(['users', 'posts']); + const user = await User.create(); + const post = await Post.create(); + await post.updateAssociations({ + // 关联 id + user: user.id, + }); + expect(user.id).toBe(post.user_id); + const postUser = await post.getUser(); + expect(user.id).toBe(postUser.id); + }); + + it('shoud associated id when association is object only id attribute', async () => { + const [User, Tag, Post] = db.getModels(['users', 'tags', 'posts']); + const user = await User.create(); + const post = await Post.create(); + await post.updateAssociations({ + // 关联 id + user: { + id: user.id, + }, + }); + expect(user.id).toBe(post.user_id); + const postUser = await post.getUser(); + expect(user.id).toBe(postUser.id); + }); + + it('shoud associate and update other attributes', async () => { + const [User, Post] = db.getModels(['users', 'posts']); + const user = await User.create(); + let post = await Post.create(); + await post.updateAssociations({ + // 关联并更新当前 id 的数据 + user: { + id: user.id, + name: 'user1234', + }, + }); + expect(user.id).toBe(post.user_id); + const postUser = await post.getUser(); + expect(user.id).toBe(postUser.id); + expect(postUser.name).toBe('user1234'); + }); + + it('shoud work', async () => { + const [Post] = db.getModels(['posts']); + const post = await Post.create(); + await post.updateAssociations({ + // 新建并关联 user + user: { + name: 'user123456', + }, + }); + const postUser = await post.getUser(); + expect(postUser.name).toBe('user123456'); + }); + + it('shoud work', async () => { + const [Table, Field] = db.getModels(['tables', 'fields']); + const field = await Field.create({ + name: 'fieldName', + }); + await Table.create({name: 'demos'}); + await field.updateAssociations({ + table: 'demos', + }); + expect(field.table_name).toBe('demos'); + }); + + it('shoud work', async () => { + const [Row, Column] = db.getModels(['rows', 'columns']); + await Row.create({ + name: 't1_examples', + }); + const column = await Column.create(); + await column.updateAssociations({ + row: 't1_examples', + }); + }); + + it('shoud work', async () => { + const [Row, Column] = db.getModels(['rows', 'columns']); + await Row.create({ + name: 't2_examples', + }); + const column = await Column.create(); + await column.updateAssociations({ + row: { + name: 't2_examples', + }, + }); + }); + }); + + describe('belongsToMany', () => { + it('@', async () => { + const [Post, Tag] = db.getModels(['posts', 'tags']); + const post = await Post.create(); + const tag = await Tag.create(); + await post.updateAssociations({ + tags: tag, + }); + const count = await post.countTags(); + expect(count).toBe(1); + }); + + it('@', async () => { + const [Post, Tag] = db.getModels(['posts', 'tags']); + const post = await Post.create(); + const tag = await Tag.create(); + await post.updateAssociations({ + tags: [tag], + }); + const count = await post.countTags(); + expect(count).toBe(1); + }); + + it('@', async () => { + const [Post, Tag] = db.getModels(['posts', 'tags']); + const post = await Post.create(); + const tag = await Tag.create(); + await post.updateAssociations({ + tags: tag.id, + }); + const count = await post.countTags(); + expect(count).toBe(1); + }); + + it('@', async () => { + const [Post, Tag] = db.getModels(['posts', 'tags']); + const post = await Post.create(); + const tag = await Tag.create(); + await post.updateAssociations({ + tags: [tag.id], + }); + const count = await post.countTags(); + expect(count).toBe(1); + }); + + it('@', async () => { + const [Post, Tag] = db.getModels(['posts', 'tags']); + const post = await Post.create(); + const tags = await Tag.bulkCreate([{}, {}]); + await post.updateAssociations({ + tags: tags, + }); + const count = await post.countTags(); + expect(count).toBe(2); + }); + + it('@', async () => { + const [Post] = db.getModels(['posts']); + const post = await Post.create(); + await post.updateAssociations({ + tags: { + name: 'tag1' + }, + }); + const count = await post.countTags(); + expect(count).toBe(1); + }); + + it('@', async () => { + const [Post] = db.getModels(['posts']); + const post = await Post.create(); + await post.updateAssociations({ + tags: [ + { + name: 'tag2' + }, + { + name: 'tag3' + }, + ], + }); + const count = await post.countTags(); + expect(count).toBe(2); + }); + }); +}); + +describe('belongsToMany', () => { + let db: Database; + let post: Model; + let tag1: Model; + let tag2: Model; + + beforeAll(async () => { + db = getDatabase({ + logging: false, + }); + db.table({ + name: 'posts', + tableName: 't333333_posts', + fields: [ + { + type: 'string', + name: 'slug', + unique: true, + }, + { + type: 'belongsToMany', + name: 'tags', + sourceKey: 'slug', + targetKey: 'name', + }, + ], + }); + db.table({ + name: 'tags', + tableName: 't333333_tags', + fields: [ + { + type: 'string', + name: 'name', + unique: true, + }, + { + type: 'belongsToMany', + name: 'posts', + sourceKey: 'name', + targetKey: 'slug', + }, + ], + }); + await db.sync({force: true}); + const [ Post, Tag ] = db.getModels(['posts', 'tags']); + post = await Post.create({slug: 'post1'}); + tag1 = await Tag.create({name: 'tag1'}); + tag2 = await Tag.create({name: 'tag2'}); + }); + + afterAll(async () => { + await db.close(); + }); + + it('@', async () => { + await post.updateAssociations({ + tags: tag1.name, + }); + expect(await post.countTags()).toBe(1); + }); + it('@', async () => { + await post.updateAssociations({ + tags: tag2.id, + }); + expect(await post.countTags()).toBe(1); + }); + it('@', async () => { + await post.updateAssociations({ + tags: [tag1, tag2], + }); + expect(await post.countTags()).toBe(2); + }); + it('@', async () => { + await post.updateAssociations({ + tags: { + name: 'tag2', + }, + }); + expect(await post.countTags()).toBe(1); + expect((await post.getTags())[0].id).toBe(tag2.id); + }); + it('@', async () => { + await post.updateAssociations({ + tags: [{ + name: 'tag3', + }], + }); + expect(await post.countTags()).toBe(1); + }); +}); diff --git a/packages/database/src/__tests__/modules/fn.js b/packages/database/src/__tests__/modules/fn.js new file mode 100644 index 000000000..2def40610 --- /dev/null +++ b/packages/database/src/__tests__/modules/fn.js @@ -0,0 +1,3 @@ +module.exports = function () { + return 'fn'; +} \ No newline at end of file diff --git a/packages/database/src/__tests__/modules/fnts.ts b/packages/database/src/__tests__/modules/fnts.ts new file mode 100644 index 000000000..5e3a38c43 --- /dev/null +++ b/packages/database/src/__tests__/modules/fnts.ts @@ -0,0 +1 @@ +export default () => 'foo'; \ No newline at end of file diff --git a/packages/database/src/__tests__/modules/json.json b/packages/database/src/__tests__/modules/json.json new file mode 100644 index 000000000..b42f309e7 --- /dev/null +++ b/packages/database/src/__tests__/modules/json.json @@ -0,0 +1,3 @@ +{ + "foo": "bar" +} \ No newline at end of file diff --git a/packages/database/src/__tests__/modules/obj.js b/packages/database/src/__tests__/modules/obj.js new file mode 100644 index 000000000..3362767a0 --- /dev/null +++ b/packages/database/src/__tests__/modules/obj.js @@ -0,0 +1,3 @@ +module.exports = { + 'foo': 'bar', +} \ No newline at end of file diff --git a/packages/database/src/__tests__/modules/objts.ts b/packages/database/src/__tests__/modules/objts.ts new file mode 100644 index 000000000..09733b58e --- /dev/null +++ b/packages/database/src/__tests__/modules/objts.ts @@ -0,0 +1,3 @@ +export default { + 'foo': 'bar', +} \ No newline at end of file diff --git a/packages/database/src/__tests__/sync.test.ts b/packages/database/src/__tests__/sync.test.ts new file mode 100644 index 000000000..4d0c13abe --- /dev/null +++ b/packages/database/src/__tests__/sync.test.ts @@ -0,0 +1,172 @@ +import { getDatabase } from './'; + +describe('db sync', () => { + describe('table.sync', () => { + it('shound be ok1', async () => { + const db = getDatabase({ + logging: false, + }); + const table = db.table({ + name: 'foos', + }); + await table.sync(); + await db.close(); + }); + it('sync#belongsTo', async () => { + const db = getDatabase({ + logging: false, + }); + db.table({ + name: 'foos', + tableName: 'foos1', + fields: [ + { + type: 'belongsTo', + name: 'bar', + } + ], + }); + db.table({ + name: 'bars', + tableName: 'bars1', + fields: [ + { + type: 'hasMany', + name: 'foos', + } + ], + }); + await db.sequelize.drop(); + await db.getTable('foos').sync({ + force: false, + alter: { + drop: false, + } + }); + await db.close(); + }); + it('sync#hasMany', async () => { + const db = getDatabase({ + logging: false, + }); + db.table({ + name: 'foos', + tableName: 'foos2', + fields: [ + { + type: 'belongsTo', + name: 'bar', + } + ], + }); + db.table({ + name: 'bars', + tableName: 'bars2', + fields: [ + { + type: 'hasMany', + name: 'foos', + } + ], + }); + await db.sequelize.drop(); + await db.getTable('bars').sync({ + force: false, + alter: { + drop: false, + } + }); + await db.close(); + }); + it('sync#belongsToMany', async () => { + const db = getDatabase({ + logging: false, + }); + db.table({ + name: 'foos', + fields: [ + { + type: 'belongsToMany', + name: 'bars', + }, + { + type: 'string', + name: 'name', + }, + ], + }); + db.table({ + name: 'bars', + fields: [ + { + type: 'belongsToMany', + name: 'foos', + }, + { + type: 'string', + name: 'name', + } + ], + }); + await db.sequelize.drop(); + await db.getTable('foos').sync({ + force: false, + alter: { + drop: false, + } + }); + await db.getModel('bars').create({ + name: 'aa', + }); + expect(await db.getModel('bars').count()).toBe(1); + db.getTable('bars').addField({ + type: 'string', + name: 'col1', + }); + await db.getTable('bars').sync({ + force: false, + alter: { + drop: false, + } + }); + await db.getModel('bars').create({ + name: 'bb', + col1: 'val1' + }); + expect(await db.getModel('bars').count()).toBe(2); + await db.close(); + }); + it('shound be ok2', async () => { + const db = getDatabase({ + logging: false, + }); + const table = db.table({ + name: 'goos', + }); + await table.sync({ + force: true, + }); + table.addField({ + type: 'string', + name: 'col1', + }); + await table.sync({ + force: false, + alter: { + drop: false, + } + }); + table.addField({ + type: 'string', + name: 'col2', + }); + await table.sync({ + force: false, + alter: { + drop: false, + } + }); + await db.close(); + }); + }); +}); \ No newline at end of file diff --git a/packages/database/src/__tests__/tables.test.ts b/packages/database/src/__tests__/tables.test.ts new file mode 100644 index 000000000..5e9fbba8e --- /dev/null +++ b/packages/database/src/__tests__/tables.test.ts @@ -0,0 +1,300 @@ +import { getDatabase } from './'; +import Database from '../database'; +import Table from '../table'; +import Model from '../model'; + +describe('tables', () => { + let db: Database; + + // beforeAll(() => { + // db = getDatabase(); + // }); + + // afterAll(async () => { + // await db.sequelize.close(); + // }); + + describe('options', () => { + it('shoud be defined', () => { + db = getDatabase(); + db.table({ + name: 'foo', + }); + expect(db.getTable('foo')).toBeInstanceOf(Table); + expect(db.isDefined('foo')).toBe(true); + }); + + it('custom model test', () => { + class Abc extends Model { + public static database: Database; + static getModel(name: string) { + return this.database.getModel(name); + } + static test12345() { + return 'test12345'; + } + } + db = getDatabase(); + const table = db.table({ + name: 'abc', + model: Abc, + }); + expect(Abc.database).toBe(db); + expect(table.getModel().test12345()).toBe('test12345'); + expect(Abc.getModel('abc').test12345()).toBe('test12345'); + }); + + it('shoud tableName === name', async () => { + db = getDatabase(); + db.table({ + name: 'foos', + }); + expect(db.getModel('foos').name).toBe('foos'); + expect(db.getModel('foos').getTableName()).toBe('foos'); + }); + + it('shoud be custom when tableName is defined', async () => { + db = getDatabase(); + db.table({ + name: 'bar', + tableName: 'bar_v2' + }); + expect(db.getModel('bar').name).toBe('bar'); + expect(db.getModel('bar').getTableName()).toBe('bar_v2'); + }); + + it('shoud be custom when timestamps is defined', async () => { + db = getDatabase(); + db.table({ + name: 'baz', + createdAt: 'created', + updatedAt: 'updated', + }); + expect(db.getModel('baz').rawAttributes.created).toBeDefined(); + expect(db.getModel('baz').rawAttributes.updated).toBeDefined(); + }); + + it('index shound be defined', async () => { + db = getDatabase(); + db.table({ + name: 'baz', + fields: [ + { + type: 'string', + name: 'col1', + index: true, + }, + { + type: 'string', + name: 'col2', + }, + { + type: 'string', + name: 'col3', + index: { + fields: ['col2', 'col3'], + }, + }, + ], + }); + expect(db.getModel('baz').options.indexes).toStrictEqual(db.getTable('baz').getModelOptions().indexes); + expect(db.getTable('baz').getModelOptions().indexes).toStrictEqual([ + { fields: [ 'col1' ], name: 'baz_col1', type: '', parser: null }, + { fields: [ 'col2', 'col3' ], name: 'baz_col2_col3', type: '', parser: null } + ]); + }); + + it('index shound be defined', async () => { + db = getDatabase(); + db.table({ + name: 'baz2', + indexes: [ + { + fields: ['col1'], + }, + { + fields: ['col2', 'col3'], + }, + ], + fields: [ + { + type: 'string', + name: 'col1', + }, + { + type: 'string', + name: 'col2', + }, + { + type: 'string', + name: 'col3', + }, + ], + }); + expect(db.getModel('baz2').options.indexes).toStrictEqual(db.getTable('baz2').getModelOptions().indexes); + expect(db.getTable('baz2').getModelOptions().indexes).toStrictEqual([ + { fields: [ 'col1' ], name: 'baz2_col1', type: '', parser: null }, + { fields: [ 'col2', 'col3' ], name: 'baz2_col2_col3', type: '', parser: null }, + ]); + }); + }); + + describe('#extend()', () => { + it('shoud be extend', async () => { + db = getDatabase(); + db.table({ + name: 'baz', + }); + expect(db.getModel('baz').rawAttributes.created_at).toBeDefined(); + expect(db.getModel('baz').rawAttributes.updated_at).toBeDefined(); + db.extend({ + name: 'baz', + createdAt: 'created', + updatedAt: 'updated', + }); + expect(db.getModel('baz').rawAttributes.created).toBeDefined(); + expect(db.getModel('baz').rawAttributes.updated).toBeDefined(); + }); + it('shoud be extend', async () => { + db = getDatabase(); + db.table({ + name: 'foos', + }); + db.table({ + name: 'baz', + fields: [ + { + type: 'string', + name: 'col1', + }, + { + type: 'hasOne', + name: 'foo', + } + ] + }); + db.extend({ + name: 'baz', + timestamps: false, + fields: [ + { + type: 'string', + name: 'col2', + } + ], + }); + expect(db.getModel('baz').rawAttributes.col1).toBeDefined(); + expect(db.getModel('baz').rawAttributes.col2).toBeDefined(); + expect(db.getModel('baz').rawAttributes.created_at).toBeUndefined(); + expect(db.getModel('baz').rawAttributes.updated_at).toBeUndefined(); + expect(db.getModel('baz').rawAttributes.col2).toBeDefined(); + expect(db.getModel('baz').associations.foo).toBeDefined(); + // await db.sync({force: true}); + // await db.sequelize.close(); + }); + }); + + describe('associations', () => { + beforeAll(() => { + db = getDatabase(); + }); + + it('shound be undefined when target table does not exist', () => { + db.table({ + name: 'bars', + fields: [ + { + type: 'hasOne', + name: 'foo', + } + ], + }); + expect(db.getModel('bars').associations.foo).toBeUndefined(); + }); + + it('shound be defined when target table exists', () => { + db.table({name: 'foos'}); + expect(db.getModel('bars').associations.foo).toBeDefined(); + }); + + describe('#setFields()', () => { + beforeAll(() => { + db = getDatabase(); + db.table({ + name: 'table1', + fields: [ + { + type: 'hasOne', + name: 'table21', + target: 'table2', + }, + { + type: 'hasOne', + name: 'table22', + target: 'table2', + }, + ] + }); + db.table({ + name: 'table2', + }); + }); + it('shound be defined', () => { + const table1 = db.getModel('table1'); + expect(Object.keys(table1.associations).length).toBe(2); + expect(table1.associations.table21).toBeDefined(); + expect(table1.associations.table22).toBeDefined(); + }); + it('shound be defined', () => { + db.getTable('table1').setFields([ + { + type: 'string', + name: 'name', + }, + { + type: 'hasOne', + name: 'table23', + target: 'table2', + }, + ]); + const table1 = db.getModel('table1'); + expect(table1.rawAttributes.name).toBeDefined(); + expect(Object.keys(table1.associations).length).toBe(1); + expect(table1.associations.table21).toBeUndefined(); + expect(table1.associations.table22).toBeUndefined(); + expect(table1.associations.table23).toBeDefined(); + }); + }); + + describe('#addField()', () => { + beforeAll(() => { + db = getDatabase(); + db.table({ + name: 'table1', + }); + db.table({ + name: 'table2', + }); + }); + it('shound be defined when the field be added after initialization', () => { + db.getTable('table1').addField({ + type: 'hasOne', + name: 'table2', + target: 'table2', + }); + expect(Object.keys(db.getModel('table1').associations).length).toBe(1); + expect(db.getModel('table1').associations.table2).toBeDefined(); + }); + it('shound be defined when continue to add', () => { + db.getTable('table1').addField({ + type: 'hasOne', + name: 'table21', + target: 'table2', + }); + expect(Object.keys(db.getModel('table1').associations).length).toBe(2); + expect(db.getModel('table1').associations.table2).toBeDefined(); + expect(db.getModel('table1').associations.table21).toBeDefined(); + }); + }); + }); +}); diff --git a/packages/database/src/__tests__/types.test.ts b/packages/database/src/__tests__/types.test.ts new file mode 100644 index 000000000..2a38e80c7 --- /dev/null +++ b/packages/database/src/__tests__/types.test.ts @@ -0,0 +1,330 @@ +import { + buildField, + Boolean, + Integer, + String, + Text, + HasOne, + HasMany, + BelongsTo, + BelongsToMany, + Float, + Double, + Real, + Decimal, + Column, + Time, + Date, + DateOnly, + Array, + Json, + Jsonb, + Password +} from '../fields'; +import { DataTypes } from 'sequelize'; +import { ABSTRACT } from 'sequelize/lib/data-types'; +import { getDatabase } from '.'; +import Database from '..'; + +describe('field types', () => { + const assertTypeInstanceOf = (expected, actual) => { + const db = getDatabase(); + const table = db.table({ + name: 'test', + }); + const field = buildField({ + type: actual, + name: 'test', + }, { + sourceTable: table, + database: db, + }); + expect(field).toBeInstanceOf(expected); + if (field instanceof Column) { + const { type } = field.getAttributeOptions() as any; + if (actual instanceof ABSTRACT) { + expect(type).toBeInstanceOf(field.getDataType()); + // postgres 的 text 不限制长度,无需参数 + if (db.sequelize.getDialect() !== 'postgres' || field.getType() !== 'TEXT') { + // 非严谨比较,undefined == null + expect(type).toEqual(actual); + } + } else if (typeof actual === 'function') { + expect(type).toBe(field.getDataType()); + expect(type).toBe(actual); + } else if (typeof actual === 'string') { + expect(type).toBe(field.getDataType()); + } + } + } + + it('shound be boolean', () => { + assertTypeInstanceOf(Boolean, 'boolean'); + assertTypeInstanceOf(Boolean, DataTypes.BOOLEAN); + }); + + it('shound be integer', () => { + assertTypeInstanceOf(Integer, 'int'); + assertTypeInstanceOf(Integer, 'integer'); + assertTypeInstanceOf(Integer, DataTypes.INTEGER); + assertTypeInstanceOf(Integer, DataTypes.INTEGER({ + length: 5, + })); + }); + + it('shound be tiny integer', () => { + assertTypeInstanceOf(Integer, 'tinyint'); + assertTypeInstanceOf(Integer, 'tinyInt'); + assertTypeInstanceOf(Integer, 'tinyinteger'); + assertTypeInstanceOf(Integer, 'tinyInteger'); + assertTypeInstanceOf(Integer, DataTypes.TINYINT); + assertTypeInstanceOf(Integer, DataTypes.TINYINT({ + length: 5, + })); + }); + + it('shound be small integer', () => { + assertTypeInstanceOf(Integer, 'smallint'); + assertTypeInstanceOf(Integer, 'smallInt'); + assertTypeInstanceOf(Integer, 'smallinteger'); + assertTypeInstanceOf(Integer, 'smallInteger'); + assertTypeInstanceOf(Integer, DataTypes.SMALLINT); + assertTypeInstanceOf(Integer, DataTypes.SMALLINT({ + length: 5, + })); + }); + + it('shound be medium integer', () => { + assertTypeInstanceOf(Integer, 'mediumInt'); + assertTypeInstanceOf(Integer, 'MediumInt'); + assertTypeInstanceOf(Integer, 'MediumInteger'); + assertTypeInstanceOf(Integer, 'MediumInteger'); + assertTypeInstanceOf(Integer, DataTypes.MEDIUMINT); + assertTypeInstanceOf(Integer, DataTypes.MEDIUMINT({ + length: 5, + })); + }); + + it('shound be big integer', () => { + assertTypeInstanceOf(Integer, 'bigint'); + assertTypeInstanceOf(Integer, 'bigInt'); + assertTypeInstanceOf(Integer, 'biginteger'); + assertTypeInstanceOf(Integer, 'bigInteger'); + assertTypeInstanceOf(Integer, DataTypes.BIGINT); + assertTypeInstanceOf(Integer, DataTypes.BIGINT({ + length: 5, + })); + }); + + it('shound be float', () => { + assertTypeInstanceOf(Float, 'float'); + assertTypeInstanceOf(Float, DataTypes.FLOAT({ + length: 5, + })); + }); + + it('shound be double', () => { + assertTypeInstanceOf(Double, 'double'); + assertTypeInstanceOf(Double, DataTypes.DOUBLE(10)); + }); + + it('shound be real', () => { + assertTypeInstanceOf(Real, 'real'); + assertTypeInstanceOf(Real, DataTypes.REAL({ + length: 5, + })); + }); + + it('shound be decimal', () => { + assertTypeInstanceOf(Decimal, 'decimal'); + assertTypeInstanceOf(Decimal, DataTypes.DECIMAL(5)); + }); + + it('shound be string', () => { + assertTypeInstanceOf(String, 'string'); + assertTypeInstanceOf(String, DataTypes.STRING); + assertTypeInstanceOf(String, DataTypes.STRING(100)); + }); + + it('shound be text', () => { + assertTypeInstanceOf(Text, 'text'); + assertTypeInstanceOf(Text, DataTypes.TEXT); + assertTypeInstanceOf(Text, DataTypes.TEXT({ + length: 'long', + })); + }); + + it('shound be time', () => { + assertTypeInstanceOf(Time, 'time'); + assertTypeInstanceOf(Time, DataTypes.TIME); + }); + + it('shound be date', () => { + assertTypeInstanceOf(Date, 'date'); + assertTypeInstanceOf(Date, 'timestamp'); + assertTypeInstanceOf(Date, DataTypes.DATE); + assertTypeInstanceOf(Date, DataTypes.DATE(2)); + }); + + it('shound be dateonly', () => { + assertTypeInstanceOf(DateOnly, 'dateOnly'); + assertTypeInstanceOf(DateOnly, 'dateonly'); + assertTypeInstanceOf(DateOnly, DataTypes.DATEONLY); + }); + + it('shound be array', () => { + assertTypeInstanceOf(Array, 'array'); + }); + + it('shound be json', () => { + assertTypeInstanceOf(Json, 'json'); + assertTypeInstanceOf(Json, DataTypes.JSON); + }); + + it('shound be jsonb', () => { + assertTypeInstanceOf(Jsonb, 'jsonb'); + assertTypeInstanceOf(Jsonb, DataTypes.JSONB); + }); + + it('shound be HasOne relationship', () => { + assertTypeInstanceOf(HasOne, 'hasone'); + assertTypeInstanceOf(HasOne, 'hasOne'); + assertTypeInstanceOf(HasOne, 'HasOne'); + }); + + it('shound be HasMany relationship', () => { + assertTypeInstanceOf(HasMany, 'hasmany'); + assertTypeInstanceOf(HasMany, 'hasMany'); + assertTypeInstanceOf(HasMany, 'HasMany'); + }); + + it('shound be BelongsTo relationship', () => { + assertTypeInstanceOf(BelongsTo, 'belongsto'); + assertTypeInstanceOf(BelongsTo, 'belongsTo'); + assertTypeInstanceOf(BelongsTo, 'BelongsTo'); + }); + + it('shound be BelongsToMany relationship', () => { + assertTypeInstanceOf(BelongsToMany, 'belongstomany'); + assertTypeInstanceOf(BelongsToMany, 'belongsToMany'); + assertTypeInstanceOf(BelongsToMany, 'BelongsToMany'); + }); + + describe('virtual', () => { + let db: Database; + beforeAll(async () => { + db = getDatabase(); + db.table({ + name: 'formula_tests', + fields: [ + { + type: 'string', + name: 'title', + }, + { + type: 'integer', + name: 'number1', + }, + { + type: 'integer', + name: 'number2', + }, + { + type: 'json', + name: 'meta', + }, + { + type: 'formula', + name: 'formula1', + format: 'number', + formula: '{{ number1 + number2 }}', + }, + { + type: 'formula', + name: 'formula2', + formula: '1{{ title }}2', + }, + { + type: 'reference', + name: 'reference1', + dataIndex: 'key1', + source: 'meta', + }, + { + type: 'reference', + name: 'reference2', + dataIndex: 'col2', + source: 'bar', + }, + { + type: 'belongsTo', + name: 'bar', + }, + ], + }); + db.table({ + name: 'password_table', + fields: [ + { + type: 'password', + name: 'password', + }, + ], + }) + db.table({ + name: 'bars', + tableName: 'formula_bars', + fields: [ + { + type: 'string', + name: 'col2', + } + ], + }) + await db.sync({force: true}); + }); + afterAll(async () => { + await db.close(); + }); + it('pwd', async () => { + const Pwd = db.getModel('password_table'); + const pwd = await Pwd.create({ + password: '123456', + }); + expect(Password.verify('123456', pwd.password)).toBeTruthy(); + }); + it('formula', async () => { + const [ Formula ] = db.getModels(['formula_tests']); + const formula = await Formula.create({ + title: 'title1', + number1: 1, + number2: 2, + }); + expect(formula.formula1).toBe(3); + expect(formula.formula2).toBe('1title12'); + }); + it('formula', async () => { + const [ Formula ] = db.getModels(['formula_tests']); + const formula = await Formula.create({ + meta: { + key1: 'val1', + }, + }); + await formula.updateAssociations({ + bar: { + col2: 'val2', + } + }); + const f = await Formula.findOne({ + where: { + id: formula.id, + }, + include: { + association: 'bar', + } + }); + expect(f.reference1).toBe('val1'); + expect(f.reference2).toBe('val2'); + }); + }); +}); diff --git a/packages/database/src/__tests__/utils.test.ts b/packages/database/src/__tests__/utils.test.ts new file mode 100644 index 000000000..4c1730b5a --- /dev/null +++ b/packages/database/src/__tests__/utils.test.ts @@ -0,0 +1,658 @@ +import { requireModule, toWhere, toInclude } from '../utils'; +import path from 'path'; +import { Op } from 'sequelize'; +import { getDatabase } from '.'; +import Database from '../database'; +import Model, { ModelCtor } from '../model'; + +describe('utils', () => { + describe('toWhere', () => { + it('Op.eq', () => { + const where = toWhere({ + id: { + eq: 12 + }, + }); + expect(where).toEqual({ + id: { + [Op.eq]: 12, + }, + }); + }); + + it('Op.ilike', () => { + const where = toWhere({ + id: { + ilike: 'val1' + }, + }); + expect(where).toEqual({ + id: { + [Op.iLike]: 'val1', + }, + }); + }); + + it('Op.ilike', () => { + const where = toWhere({ + 'id.ilike': 'val1', + }); + expect(where).toEqual({ + id: { + [Op.iLike]: 'val1', + }, + }); + }); + + it('Op.is null', () => { + const where = toWhere({ + 'id.is': null, + }); + expect(where).toEqual({ + id: { + [Op.is]: null, + }, + }); + }); + + it('Op.in', () => { + const where = toWhere({ + id: { + in: [12] + }, + }); + expect(where).toEqual({ + id: { + [Op.in]: [12], + }, + }); + }); + + it('Op.in', () => { + const where = toWhere({ + 'id.in': [11, 12], + }); + expect(where).toEqual({ + id: { + [Op.in]: [11, 12], + }, + }); + }); + + describe('association', () => { + let db: Database; + let Foo: ModelCtor; + beforeAll(() => { + db = getDatabase(); + db.table({ + name: 'bazs', + }); + db.table({ + name: 'bars', + fields: [ + { + type: 'belongsTo', + name: 'baz', + } + ] + }); + db.table({ + name: 'foos', + fields: [ + { + type: 'hasMany', + name: 'bars', + } + ], + }); + Foo = db.getModel('foos'); + }); + + const toWhereExpect = (options, logging = false) => { + const where = toWhere(options, { + associations: Foo.associations, + }); + return expect(where); + } + + it('association', () => { + toWhereExpect({ + col1: 'val1', + bars: { + name: { + ilike: 'aa', + }, + col2: { + lt: 2, + }, + baz: { + col1: 12, + }, + }, + 'bars.col3.ilike': 'aa', + }).toEqual({ + col1: 'val1', + $__include: { + bars: { + name: { + [Op.iLike]: 'aa', + }, + col2: { + [Op.lt]: 2, + }, + col3: { + [Op.iLike]: 'aa', + }, + $__include: { + baz: { + col1: 12 + }, + }, + }, + }, + }); + }); + }); + }); + + describe('toInclude', () => { + let db: Database; + let Foo: ModelCtor; + beforeAll(() => { + db = getDatabase(); + db.table({ + name: 'bazs', + }); + db.table({ + name: 'bays', + }); + db.table({ + name: 'bars', + fields: [ + { + type: 'belongsTo', + name: 'baz', + }, + { + type: 'belongsTo', + name: 'bay', + }, + ], + }); + db.table({ + name: 'foos', + fields: [ + { + type: 'hasMany', + name: 'bars', + }, + { + type: 'hasMany', + name: 'fozs', + }, + { + type: 'hasMany', + name: 'coos', + }, + ], + }); + db.table({ + name: 'fozs', + }); + db.table({ + name: 'coos', + }); + Foo = db.getModel('foos'); + }); + + const toIncludeExpect = (options: any, logging = false) => { + const include = toInclude(options, { + Model: Foo, + associations: Foo.associations, + }); + if (logging) { + console.log(JSON.stringify(include, null, 2)); + } + return expect(include); + }; + + it('normal columns', () => { + toIncludeExpect({ + fields: ['col1', 'col2'], + }).toEqual({ attributes: [ 'col1', 'col2' ] }); + }); + + it('association count attribute', () => { + toIncludeExpect({ + fields: ['col1', 'bars_count'], + }).toEqual({ attributes: [ 'col1', Foo.withCountAttribute('bars') ] }); + }); + + it('association without attributes', () => { + toIncludeExpect({ + fields: ['col1', 'bars'], + }).toEqual({ + attributes: [ 'col1' ], + include: [ + { + association: 'bars', + } + ], + }); + }); + + it('association attributes', () => { + toIncludeExpect({ + fields: ['col1', 'bars.col1', 'bars.col2'], + }).toEqual({ + attributes: [ 'col1' ], + include: [ + { + association: 'bars', + attributes: [ 'col1', 'col2' ], + } + ], + }); + }); + + it('association attributes', () => { + toIncludeExpect({ + fields: ['col1', ['bars', 'col1'], ['bars', 'col2']], + }).toEqual({ + attributes: [ 'col1' ], + include: [ + { + association: 'bars', + attributes: [ 'col1', 'col2' ], + } + ], + }); + }); + + it('nested association', () => { + toIncludeExpect({ + fields: ['col1', 'bars.baz'], + }).toEqual({ + attributes: [ 'col1' ], + include: [ + { + association: 'bars', + attributes: [], + include: [ + { + association: 'baz', + } + ] + } + ], + }); + }); + + it.skip('nested association', () => { + // TODO,输出 bars 的所有字段 + toIncludeExpect({ + fields: ['col1', 'bars', 'bars.baz'], + }, true).toEqual({ + attributes: [ 'col1' ], + include: [ + { + association: 'bars', + include: [ + { + association: 'baz', + } + ] + } + ], + }); + }); + + it('nested association', () => { + toIncludeExpect({ + fields: ['col1', 'bars.col1', 'bars.col2', 'bars.baz'], + }).toEqual({ + attributes: [ 'col1' ], + include: [ + { + association: 'bars', + attributes: ['col1', 'col2'], + include: [ + { + association: 'baz', + } + ] + } + ], + }); + }); + + it('nested association', () => { + // TODO,输出 bars 的所有字段 + toIncludeExpect({ + fields: ['col1', 'bars.col1', 'bars.col2', 'bars.baz.col1', 'bars.baz.col2'], + }).toEqual({ + attributes: [ 'col1' ], + include: [ + { + association: 'bars', + attributes: ['col1', 'col2'], + include: [ + { + association: 'baz', + attributes: ['col1', 'col2'], + } + ] + } + ], + }); + }); + + it('append attributes', () => { + toIncludeExpect({ + fields: { + appends: ['bars_count'], + }, + }).toEqual({ + attributes: { + include: [Foo.withCountAttribute('bars')], + }, + }); + }); + + it('append attributes', () => { + toIncludeExpect({ + fields: { + appends: ['bars.col1', 'bars_count'], + }, + }).toEqual({ + attributes: { + include: [Foo.withCountAttribute('bars')], + }, + include: [ + { + association: 'bars', + attributes: { + include: ['col1'], + }, + } + ] + }); + }); + + it('only & append attributes', () => { + toIncludeExpect({ + fields: { + only: ['col1'], + appends: ['bars_count'], + }, + }).toEqual({ + attributes: ['col1', Foo.withCountAttribute('bars')], + }); + }); + + it('only & append attributes', () => { + toIncludeExpect({ + fields: { + only: ['col1', 'bars.col1'], + appends: ['bars_count'], + }, + }).toEqual({ + attributes: ['col1', Foo.withCountAttribute('bars')], + include: [ + { + association: 'bars', + attributes: ['col1'], + } + ], + }); + }); + + it('excpet attributes', () => { + toIncludeExpect({ + fields: { + except: ['col1'], + }, + }).toEqual({ + attributes: { + include: [], + exclude: ['col1'], + }, + }); + }); + + it('excpet attributes', () => { + toIncludeExpect({ + fields: { + except: ['col1', 'bars.col1'], + }, + }).toEqual({ + attributes: { + include: [], + exclude: ['col1'], + }, + include: [ + { + association: 'bars', + attributes: { + include: [], + exclude: ['col1'], + } + } + ] + }); + }); + + it('excpet & append attributes', () => { + toIncludeExpect({ + fields: { + except: ['col1'], + appends: ['bars_count'], + }, + }).toEqual({ + attributes: { + include: [Foo.withCountAttribute('bars')], + exclude: ['col1'], + }, + }); + }); + + describe('where options', () => { + it('where', () => { + toIncludeExpect({ + filter: { + col1: 'val1', + }, + fields: { + except: ['col1'], + appends: ['bars_count'], + }, + }).toEqual({ + attributes: { + include: [Foo.withCountAttribute('bars')], + exclude: ['col1'], + }, + where: { + col1: 'val1', + }, + }); + }); + + it('where', () => { + toIncludeExpect({ + filter: { + col1: 'val1', + bars: { + col1: 'val1', + } + }, + fields: { + except: ['col1'], + appends: ['bars', 'bars_count'], + }, + }).toEqual({ + attributes: { + include: [Foo.withCountAttribute('bars')], + exclude: ['col1'], + }, + where: { + col1: 'val1', + }, + include: [ + { + association: 'bars', + where: { + col1: 'val1', + } + } + ] + }); + }); + + it('where', () => { + toIncludeExpect({ + filter: { + col1: 'val1', + bars: { + col1: 'val1', + } + }, + fields: { + except: ['col1'], + appends: ['bars_count'], + }, + }).toEqual({ + attributes: { + include: [Foo.withCountAttribute('bars')], + exclude: ['col1'], + }, + where: { + col1: 'val1', + }, + include: [ + { + association: 'bars', + where: { + col1: 'val1', + } + } + ] + }); + }); + + it('parseApiJson', () => { + const data = Foo.parseApiJson({ + filter: { + col1: 'co2', + } + }); + expect(data).toEqual({ where: { col1: 'co2' } }); + }); + + it('parseApiJson', () => { + const data = Foo.parseApiJson({ + fields: ['col1'], + }); + expect(data).toEqual({ attributes: ['col1'] }); + }); + + it('parseApiJson', () => { + const data = Foo.parseApiJson({ + fields: ['col1'], + filter: { + col1: 'co2', + }, + }); + expect(data).toEqual({ attributes: ['col1'], where: { col1: 'co2' } }); + }); + + it('parseApiJson', () => { + const data = Foo.parseApiJson({ + fields: ['col1'], + filter: { + col1: 'val1', + bars: { + col1: 'val1', + } + }, + }); + expect(data).toEqual({ + attributes: ['col1'], + where: { col1: 'val1' }, + include: [ + { + association: 'bars', + where: { col1: 'val1' }, + } + ], + }); + }); + + it('parseApiJson', () => { + const data = Foo.parseApiJson({ + fields: ['col1', 'bars.col1'], + filter: { + col1: 'val1', + bars: { + col1: 'val1', + } + }, + }); + expect(data).toEqual({ + attributes: ['col1'], + where: { col1: 'val1' }, + include: [ + { + association: 'bars', + attributes: ['col1'], + where: { col1: 'val1' }, + } + ], + }); + }); + }); + + }); + + describe('requireModule', () => { + test('toBeTruthy', () => { + const r = requireModule(true); + expect(r).toBeTruthy(); + }); + + test('toBeInstanceOf Function', () => { + const r = requireModule(() => {}); + expect(r).toBeInstanceOf(Function); + }); + + test('toBeInstanceOf Function', () => { + const r = requireModule(path.resolve(__dirname, './modules/fn')); + expect(r).toBeInstanceOf(Function); + }); + + test('toBeInstanceOf Function', () => { + const r = requireModule(path.resolve(__dirname, './modules/fnts')); + expect(r).toBeInstanceOf(Function); + }); + + test('object', () => { + const r = requireModule(path.resolve(__dirname, './modules/obj')); + expect(r).toEqual({ + 'foo': 'bar', + }); + }); + + test('object', () => { + const r = requireModule(path.resolve(__dirname, './modules/objts')); + expect(r).toEqual({ + 'foo': 'bar', + }); + }); + + test('json', () => { + const r = requireModule(path.resolve(__dirname, './modules/json')); + expect(r).toEqual({ + 'foo': 'bar', + }); + }); + }); +}); diff --git a/packages/database/src/database.ts b/packages/database/src/database.ts new file mode 100644 index 000000000..1bd6be767 --- /dev/null +++ b/packages/database/src/database.ts @@ -0,0 +1,245 @@ +import { + Options, + Sequelize, + SyncOptions as SequelizeSyncOptions, +} from 'sequelize'; +import glob from 'glob'; +import Table, { TableOptions } from './table'; +import { Model, ModelCtor } from './model'; +import { requireModule } from './utils'; + +export interface SyncOptions extends SequelizeSyncOptions { + + /** + * 指定需要更新字段的 tables + */ + tables?: string[] | Table[] | Map; +} + +export interface ImportOptions { + + /** + * 指定配置所在路径 + */ + directory: string; + + /** + * 文件后缀,默认值 ['js', 'ts', 'json'] + */ + extensions?: string[]; +} + +export default class Database { + + public readonly sequelize: Sequelize; + + /** + * 哪些 Model 需要建立表关系 + */ + public readonly associating = new Set(); + + /** + * 中间表 + */ + public readonly throughTables = new Map>(); + + protected tables = new Map(); + + protected options: Options; + + constructor(options: Options) { + this.options = options; + this.sequelize = new Sequelize(options); + } + + /** + * 载入指定目录下 tables 配置(配置的文件驱动) + * + * TODO: 配置的文件驱动现在会全部初始化,大数据时可能存在性能瓶颈,后续可以加入动态加载 + * + * @param {object} [options] + * @param {string} [options.directory] 指定配置所在路径 + * @param {array} [options.extensions = ['js', 'ts', 'json']] 文件后缀 + */ + public import(options: ImportOptions): Map { + const { extensions = ['js', 'ts', 'json'], directory } = options; + const patten = `${directory}/*.{${extensions.join(',')}}`; + const files = glob.sync(patten); + const tables = new Map(); + files.forEach((file: string) => { + if (file.endsWith('.d.ts')) { + return; + } + const options = requireModule(file); + const table = this.table(typeof options === 'function' ? options(this) : options); + tables.set(table.getName(), table); + }); + return tables; + } + + /** + * 配置表 + * + * @param options + */ + public table(options: TableOptions): Table { + const { name } = options; + const table = new Table(options, { database: this }); + this.tables.set(name, table); + // 在 source 或 target 之后定义 through,需要更新 source 和 target 的 model + if (this.throughTables.has(name)) { + const [sourceTable, targetTable] = this.getTables(this.throughTables.get(name)); + sourceTable && sourceTable.modelInit(true); + targetTable && targetTable.modelInit(true); + // this.throughTables.delete(name); + } + return table; + } + + /** + * 扩展配置(实验性 API) + * + * @param options + */ + public extend(options: TableOptions): Table { + const { name } = options; + let table: Table; + if (this.tables.has(name)) { + table = this.tables.get(name); + table.extend(options); + } else { + table = this.table(options); + this.tables.set(name, table); + } + return table; + } + + /** + * 是否已配置 + * + * @param name + */ + public isDefined(name: string): boolean { + return this.sequelize.isDefined(name); + } + + /** + * 获取 Model + * + * TODO: 动态初始化并加载配置(懒汉式) + * 动态初始化需要支持文件驱动和数据库驱动 + * + * @param name + */ + public getModel(name: string): ModelCtor { + return this.isDefined(name) ? this.sequelize.model(name) as any : undefined; + } + + /** + * 获取指定 names 的 Models + * + * @param names + */ + public getModels(names: string[]): Array> { + return names.map(name => this.getModel(name)); + } + + /** + * 获取 table 配置 + * + * TODO: + * 未单独配置多对多中间表时,取不到中间表的 table,但是可以取到 Model + * 动态初始化并加载配置(懒汉式),动态初始化需要支持文件驱动和数据库驱动 + * + * @param name + */ + public getTable(name: string): Table { + return this.tables.has(name) ? this.tables.get(name) : undefined; + } + + /** + * 获取指定 names 的 table 配置 + * + * @param names + */ + public getTables(names: string[]): Array { + return names.map(name => this.getTable(name)); + } + + /** + * 建立表关系 + * + * 表关系相关字段是在 Model.init 之后进行的 + */ + public associate() { + for (const name of this.associating) { + const Model: any = this.getModel(name); + Model.associate && Model.associate(this.sequelize.models); + } + } + + /** + * 插件扩展 + * + * TODO: 细节待定 + * + * @param plugin + * @param options + */ + public async plugin(plugin: any, options = {}) { + await plugin(this, options); + } + + /** + * 表字段更新 + * + * @param options + */ + public async sync(options: SyncOptions = {}) { + const { tables = [], ...restOptions } = options; + let items: Array; + + if (tables instanceof Map) { + items = Array.from(tables.values()); + } else { + items = tables; + } + + /** + * sequelize.sync 只能处理全部 model 的字段更新 + * Model.sync 只能处理当前 Model 的字段更新,不处理关系表 + * database.sync 可以指定 tables 进行字段更新,也可以自动处理关系表的字段更新 + */ + if (items.length > 0) { + // 指定 tables 时,新建 sequelize 实例来单独处理这些 tables 相关 models 的 sync + const sequelize = new Sequelize(this.options); + const names = new Set(); + for (const key in items) { + let table = items[key]; + if (typeof table === 'string') { + table = this.getTable(table); + } + if (table instanceof Table) { + for (const name of table.getRelatedTableNames()) { + names.add(name); + } + } + } + for (const name of names) { + // @ts-ignore + sequelize.modelManager.addModel(this.getModel(name)); + } + await sequelize.sync(restOptions); + await sequelize.close(); + } else { + await this.sequelize.sync(restOptions); + } + } + + /** + * 关闭数据库连接 + */ + public async close() { + return await this.sequelize.close(); + } +} diff --git a/packages/database/src/fields/field-types.ts b/packages/database/src/fields/field-types.ts new file mode 100644 index 000000000..99e6b1722 --- /dev/null +++ b/packages/database/src/fields/field-types.ts @@ -0,0 +1,691 @@ +import { + Utils, + DataType, + DataTypes, + Sequelize, + HasOneOptions, + HasManyOptions, + BelongsToOptions, + BelongsToManyOptions, + ThroughOptions, +} from 'sequelize'; +import * as Options from './option-types'; +import { getDataTypeKey } from '.'; +import Table from '../table'; +import Database from '../database'; +import Model, { ModelCtor } from '../model'; +import { template, isArray, map, get, toNumber } from 'lodash'; +import bcrypt from 'bcrypt'; + +export interface IField { + +} + +export interface IFields { + [key: string]: IField; +} + +export interface FieldContext { + sourceTable: Table; + database: Database, +} + +export class Field implements IField { + + public readonly options: any; + + protected context: FieldContext; + + constructor(options: any, context: FieldContext) { + const { type } = options; + this.options = {...options, type: getDataTypeKey(type)}; + this.context = context; + } + + public isMultipleColumns() { + return false; + } + + public getType() { + return this.options.type; + } + + public getOptions() { + return this.options; + } +} + +export class Column extends Field { + + public getDataType() { + const { type } = this.options; + + if (DataTypes[type]) { + return DataTypes[type]; + } + + return DataTypes[(this.constructor).name.toUpperCase()]; + } + + public getDataTypeInstance(options: any = {}): any { + const dataType = this.getDataType(); + Object.keys(options).forEach(key => options[key] === undefined && delete options[key]); + return Object.keys(options).length > 0 ? dataType(options) : dataType; + } + + public getAttributeOptions() { + return { + ...this.options, + type: this.getDataTypeInstance(), + } + } +} + +export class Boolean extends Column { +} + +export class Number extends Column { +} + +export class Integer extends Number { + + public readonly options: Options.IntegerOptions; + + public getDataType(): Function { + const { type } = this.options; + + return { + INT: DataTypes.INTEGER, + INTEGER: DataTypes.INTEGER, + TINYINT: DataTypes.TINYINT, + TINYINTEGER: DataTypes.TINYINT, + SMALLINT: DataTypes.SMALLINT, + SMALLINTEGER: DataTypes.SMALLINT, + MEDIUMINT: DataTypes.MEDIUMINT, + MEDIUMINTEGER: DataTypes.MEDIUMINT, + BIGINT: DataTypes.BIGINT, + BIGINTEGER: DataTypes.BIGINT, + }[type as string] || DataTypes.INTEGER; + } + + public getAttributeOptions() { + const { length, zerofill, unsigned, ...restOptions } = this.options; + return { + ...restOptions, + type: this.getDataTypeInstance({ length, zerofill, unsigned }), + } + } +} + +export class Float extends Number { + + public readonly options: Options.FloatOptions; + + public getAttributeOptions() { + const { length, decimals, ...restOptions } = this.options; + return { + ...restOptions, + type: this.getDataTypeInstance({ length, decimals }), + } + } +} + +export class Double extends Number { + public readonly options: Options.DoubleOptions; + + public getAttributeOptions() { + const { length, decimals, ...restOptions } = this.options; + return { + ...restOptions, + type: this.getDataTypeInstance({ length, decimals }), + } + } +} + +export class Decimal extends Number { + + public readonly options: Options.DecimalOptions; + + public getAttributeOptions() { + const { precision, scale, ...restOptions } = this.options; + return { + ...restOptions, + type: this.getDataTypeInstance({ precision, scale }), + } + } +} + +export class Real extends Number { + + public readonly options: Options.RealOptions; + + public getAttributeOptions() { + const { length, decimals, ...restOptions } = this.options; + return { + ...restOptions, + type: this.getDataTypeInstance({ length, decimals }), + } + } +} + +export class String extends Column { + + public readonly options: Options.StringOptions; + + public getAttributeOptions() { + const { length, binary, ...restOptions } = this.options; + + return { + ...restOptions, + type: this.getDataTypeInstance({ length, binary }), + } + } +} + +export class Text extends Column { + + public readonly options: Options.TextOptions; + + public getDataTypeInstance(options: any = {}): any { + const { database } = this.context; + const dataType = this.getDataType(); + Object.keys(options).forEach(key => options[key] === undefined && delete options[key]); + if (database.sequelize.getDialect() === 'postgres') { + return Object.keys(options).length > 0 ? dataType() : dataType; + } + return Object.keys(options).length > 0 ? dataType(options) : dataType; + } + + public getAttributeOptions() { + const { length, ...restOptions } = this.options; + + return { + ...restOptions, + type: this.getDataTypeInstance({ length }), + } + } +} + +export class Time extends Column { +} + +export class Date extends Column { + + public readonly options: Options.DateOptions; + + public getAttributeOptions() { + const { length, ...restOptions } = this.options; + + return { + ...restOptions, + type: this.getDataTypeInstance({ length }), + } + } +} + +export class DateOnly extends Column { +} + +export class Virtual extends Column { +} + +export class Reference extends Virtual { + + public getDataType() { + return DataTypes.VIRTUAL; + } + + public getAttributeOptions() { + const { source, dataIndex, ...restOptions } = this.options; + + return { + ...restOptions, + type: this.getDataTypeInstance({ source, dataIndex }), + get() { + return get(this[source], dataIndex); + }, + } + } +} + +export class Formula extends Virtual { + + public getDataType() { + return DataTypes.VIRTUAL; + } + + public getAttributeOptions() { + const { sourceTable } = this.context; + const { formula, format = 'string', ...restOptions } = this.options; + return { + ...restOptions, + type: this.getDataTypeInstance({ formula }), + get() { + const fields = sourceTable.getFields(); + const data: any = {}; + for (const [name, field] of fields) { + console.log(field.getType()); + if (['formula', 'virtual'].indexOf((field.getType() as string).toLowerCase()) === -1) { + data[name] = this.getDataValue(name); + } + } + try { + const compiled = template(formula, { + interpolate: /{{([\s\S]+?)}}/g, + }); + const value = compiled(data); + return format === 'number' ? toNumber(value) : value; + } catch (error) { + console.log(error); + return; + } + } + } + } +} + +export class Password extends String { + + public getDataType() { + return DataTypes.STRING; + } + + public static verify(value: string, hash: string) { + return bcrypt.compareSync(value, hash); + } + + public getAttributeOptions() { + const { name, length, binary, ...restOptions } = this.options; + return { + name, + ...restOptions, + type: this.getDataTypeInstance({ length, binary }), + set(this: Model, value: string) { + this.setDataValue(name, bcrypt.hashSync(value, 10)); + }, + } + } +} + +export class Array extends Column { + + public readonly options: Options.ArrayOptions; + + public getDataType() { + return DataTypes.JSON; + } + + public getAttributeOptions() { + const { items, ...restOptions } = this.options; + return { + ...restOptions, + type: this.getDataTypeInstance({items}), + } + } +} + +export class Json extends Column { +} + +export class Jsonb extends Column { +} + +export interface HasOneAccessors { + get: string; + set: string; + create: string; +} + +export interface HasManyAccessors { + get: string; + set: string; + addMultiple: string; + add: string; + create: string; + remove: string; + removeMultiple: string; + hasSingle: string; + hasAll: string; + count: string; +} + +export interface BelongsToAccessors { + get: string; + set: string; + create: string; +} + +export interface BelongsToManyAccessors { + get: string; + set: string; + addMultiple: string; + add: string; + create: string; + remove: string; + removeMultiple: string; + hasSingle: string; + hasAll: string; + count: string; +} + +export abstract class Relation extends Field { + + public getAssociationType() { + if (this instanceof HasOne) { + return 'hasOne'; + } + if (this instanceof HasMany) { + return 'hasMany'; + } + if (this instanceof BelongsTo) { + return 'belongsTo'; + } + if (this instanceof BelongsToMany) { + return 'belongsToMany'; + } + } + + public getTarget() { + const { target, name } = this.options; + if (target) { + return target; + } + if (this instanceof HasMany) { + return name; + } + if (this instanceof BelongsToMany) { + return name; + } + return Utils.pluralize(name); + } + + public getTargetModel() { + const { name } = this.options; + const { sourceTable } = this.context; + // @ts-ignore + return sourceTable.getModel().associations[name].target; + } + + public getAccessors() { + const { name } = this.options; + const { sourceTable } = this.context; + // @ts-ignore + return sourceTable.getModel().associations[name].accessors; + } + + public getAssociationOptions(): any { + const { name, ...restOptions } = this.options; + return { + as: name, + ...restOptions, + } + } + + public getAssociationArguments() { + return { + target: this.getTarget(), + type: this.getAssociationType(), + options: this.getAssociationOptions(), + } + }; +} + +class HasOneOrMany extends Relation { + constructor(options: Options.HasOneOptions | Options.HasManyOptions, context: FieldContext) { + const { sourceTable } = context; + let { foreignKey, sourceKey } = options; + + const SourceModel = sourceTable.getModel(); + + if (!sourceKey) { + sourceKey = SourceModel.primaryKeyAttribute; + } + + if (!SourceModel.rawAttributes[sourceKey]) { + sourceTable.addField({ + type: 'integer', + name: sourceKey, + unique: true, + }); + } + + if (!foreignKey) { + foreignKey = Utils.underscoredIf( + Utils.camelize([ + SourceModel.options.name.singular, sourceKey + ].join('_')), + SourceModel.options.underscored + ); + } + + super({ sourceKey, foreignKey, ...options }, context); + } +} + +export class HasOne extends HasOneOrMany { + + public readonly options: Options.HasOneOptions; + + constructor(options: Options.HasOneOptions, context: FieldContext) { + let { name, target } = options; + + if (!target) { + target = Utils.pluralize(name); + } + + super({target, ...options}, context); + } + + public getAccessors(): HasOneAccessors { + return super.getAccessors(); + } + + public getAssociationOptions(): HasOneOptions { + const { name, ...restOptions }= this.options; + return { + as: name, + ...restOptions, + } + } +} + +export class HasMany extends HasOneOrMany { + + public readonly options: Options.HasManyOptions; + + constructor(options: Options.HasManyOptions, context: FieldContext) { + let { name, target } = options; + + if (!target) { + target = name; + } + + super({target, ...options}, context); + } + + public getAssociationOptions(): HasManyOptions { + const { name, ...restOptions }= this.options; + return { + as: name, + ...restOptions, + } + } +} + +export class BelongsTo extends Relation { + + public readonly options: Options.BelongsToOptions; + + constructor(options: Options.BelongsToOptions, context: FieldContext) { + let { name, target } = options; + + if (!target) { + target = Utils.pluralize(name); + } + + super({target, ...options}, context); + + this.updateOptionsAfterTargetModelBeDefined(); + } + + public getAccessors(): BelongsToAccessors { + return super.getAccessors(); + } + + public updateOptionsAfterTargetModelBeDefined() { + let { name, target, targetKey, foreignKey } = this.options; + const { database } = this.context; + + const TargetModel = database.getModel(target); + + if (!TargetModel) { + return; + } + + if (!targetKey) { + targetKey = TargetModel.primaryKeyAttribute; + this.options.targetKey = targetKey; + } + + if (!foreignKey) { + foreignKey = Utils.underscoredIf( + Utils.camelize([ + name, targetKey + ].join('_')), + TargetModel.options.underscored + ); + this.options.foreignKey = foreignKey; + } + } + + public getAssociationOptions(): BelongsToOptions { + const { name, ...restOptions }= this.options; + return { + as: name, + ...restOptions, + } + } +} + +export class BelongsToMany extends Relation { + + public readonly options: Options.BelongsToManyOptions; + + constructor(options: Options.BelongsToManyOptions, context: FieldContext) { + let { name, target, through, sourceKey, foreignKey, targetKey, otherKey } = options; + const { database, sourceTable } = context; + const SourceModel = sourceTable.getModel(); + + if (!target) { + target = name; + } + + if (!through) { + through = Utils.underscoredIf( + Utils.camelize( + [SourceModel.name, target] + .map(name => name.toLowerCase()) + .sort() + .join('_') + ), + SourceModel.options.underscored + ); + } + + if (!sourceKey) { + sourceKey = SourceModel.primaryKeyAttribute; + } + + if (!foreignKey) { + foreignKey = Utils.underscoredIf( + Utils.camelize([ + SourceModel.options.name.singular, sourceKey + ].join('_')), + SourceModel.options.underscored + ); + } + + super({ + target, + through, + sourceKey, + foreignKey, + ...options, + }, context); + + this.updateOptionsAfterTargetModelBeDefined(); + + // through table 未特殊定义时,默认根据 through 信息配置 through table + // database.tables 里不会有 through table,但 database.sequelize.models 有 + database.throughTables.set(this.getThroughName(), [sourceTable.getName(), target]); + } + + public getAccessors(): BelongsToManyAccessors { + return super.getAccessors(); + } + + public updateOptionsAfterTargetModelBeDefined() { + const { database } = this.context; + let { target, targetKey, otherKey } = this.options; + + const TargetModel = database.getModel(target); + + if (!TargetModel) { + return; + } + + if (!targetKey) { + targetKey = TargetModel.primaryKeyAttribute; + this.options.targetKey = targetKey; + } + + if (!otherKey) { + otherKey = Utils.underscoredIf( + Utils.camelize([ + TargetModel.options.name.singular, targetKey + ].join('_')), + TargetModel.options.underscored + ); + this.options.otherKey = otherKey; + } + } + + public getThroughName(): string { + // TODO name 必须是字符串 + return this.options.through as string; + } + + public getThroughModel(): ModelCtor { + const { through, target } = this.options; + const { database, sourceTable } = this.context; + + const throughName = this.getThroughName(); + + if (database.sequelize.isDefined(throughName)) { + return database.getModel(throughName); + } + + // 如果不存在 Through Model,需要初始化一个,不能用 Sequelize.Model + class ThroughModel extends Model {} + + // TODO:需要对接 through 的其他参数 + ThroughModel.init({}, { + modelName: throughName, + tableName: throughName, + sequelize: database.sequelize, + indexes: [], + underscored: true, + }); + + return ThroughModel; + } + + public getAssociationOptions(): BelongsToManyOptions { + const { name, ...restOptions }= this.options; + return { + as: name, + through: this.getThroughModel(), + ...restOptions, + } + } +} diff --git a/packages/database/src/fields/index.ts b/packages/database/src/fields/index.ts new file mode 100644 index 000000000..19334075e --- /dev/null +++ b/packages/database/src/fields/index.ts @@ -0,0 +1,124 @@ +import * as Fields from './field-types'; +import { IField, IFields } from './field-types'; +import { FieldOptions } from './option-types'; +import { ABSTRACT } from 'sequelize/lib/data-types'; + +/** + * 字段统一都叫 Field,分 Column 和 Relation 两大类 + * + * Column: + * + * - Boolean + * - Number + * - Integer + * - Float + * - Double + * - Decimal + * - Real + * - String + * - Text + * - Array + * - Json + * - Jsonb + * - Time + * - Date + * - Dateonly + * - Virtual + * - Formula + * + * Relation: + * + * - HasOne + * - HasMany + * - BelongsTo + * - BelongsToMany + */ +export * from './option-types'; +export * from './field-types'; + +/** + * 全局已注册字段 + */ +const registeredFields = new Map(); + +/** + * 字段注册 + * + * @param key + * @param field + */ +export function registerField(key: string, field: IField) { + registeredFields.set(key.toUpperCase(), field); +} + +/** + * 字段批量注册 + * + * @param fields + */ +export function registerFields(fields: IFields) { + for (const key in fields) { + if (fields.hasOwnProperty(key)) { + registerField(key, fields[key]); + } + } +} + +export function getField(key: string) { + key = key.toUpperCase(); + if (registeredFields.has(key)) { + return registeredFields.get(key); + } + return Fields.Column; +} + +export function getDataTypeKey(type: any): string { + if (typeof type === 'string') { + return type.toUpperCase(); + } + + if (Object.prototype.hasOwnProperty.call(type, 'key')) { + return type.key.toUpperCase(); + } + + if (type instanceof ABSTRACT) { + return type.constructor.name.toUpperCase(); + } + + return type.toUpperCase(); +} + +/** + * 字段配置初始化 + * + * @param options + * @param context + */ +export function buildField(options: FieldOptions, context: Fields.FieldContext) { + let { type, required } = options; + if (type instanceof ABSTRACT) { + options = {...type.options, ...options}; + } + type = getDataTypeKey(type); + if (type !== 'VIRTUAL' && required) { + options.allowNull = false; + } + const Field = getField(type); + return new Field({type, ...options}, context); +} + +registerFields({ + ...Fields, + // aliases + Int: Fields.Integer, + TinyInt: Fields.Integer, + TinyInteger: Fields.Integer, + SmallInt: Fields.Integer, + SmallInteger: Fields.Integer, + MediumInt: Fields.Integer, + MediumInteger: Fields.Integer, + BigInt: Fields.Integer, + BigInteger: Fields.Integer, + Timestamp: Fields.Date, + Creator: Fields.BelongsTo, +}); diff --git a/packages/database/src/fields/option-types.ts b/packages/database/src/fields/option-types.ts new file mode 100644 index 000000000..ad3338a7f --- /dev/null +++ b/packages/database/src/fields/option-types.ts @@ -0,0 +1,214 @@ +import { + Utils, + DataType, + DataTypes, + Sequelize, + ModelAttributeColumnOptions, + ThroughOptions, + StringDataTypeOptions, + IntegerDataTypeOptions, + NumberDataTypeOptions, + TextDataTypeOptions, + FloatDataTypeOptions, + DecimalDataTypeOptions, + DoubleDataTypeOptions, + RealDataTypeOptions, + DateDataTypeOptions, + HasOneOptions as SequelizeHasOneOptions, + HasManyOptions as SequelizeHasManyOptions, + BelongsToOptions as SequelizeBelongsToOptions, + BelongsToManyOptions as SequelizeBelongsToManyOptions, + ModelIndexesOptions, +} from 'sequelize'; + +export interface AbstractFieldOptions { + name: string; + [key: string]: any; +} + +export interface AbstractColumnOptions extends AbstractFieldOptions, Omit { + dataType?: DataType; + index?: boolean | ModelIndexesOptions; +} + +export interface AbstractRelationOptions extends AbstractFieldOptions { + target?: string; +} + +export interface BooleanOptions extends AbstractColumnOptions { + type: 'boolean'; +} + +export interface NumberOptions extends AbstractColumnOptions { + // type: 'number' | typeof DataTypes.NUMBER | string; +} + +export interface IntegerOptions extends IntegerDataTypeOptions, NumberOptions { + type: 'int' | 'integer' | typeof DataTypes.INTEGER | + 'tinyint' | 'tinyInteger' | typeof DataTypes.TINYINT | + 'smallint' | 'smallInteger' | typeof DataTypes.SMALLINT | + 'mediumint' | 'mediumInteger' | typeof DataTypes.MEDIUMINT | + 'bigint' | 'bigInteger' | typeof DataTypes.BIGINT; +} + +export interface FloatOptions extends FloatDataTypeOptions, NumberOptions { + type: 'float' | typeof DataTypes.FLOAT; +} + +export interface DoubleOptions extends DoubleDataTypeOptions, NumberOptions { + type: 'double' | typeof DataTypes.DOUBLE; +} + +export interface DecimalOptions extends DecimalDataTypeOptions, NumberOptions { + type: 'decimal' | typeof DataTypes.DECIMAL; +} + +export interface RealOptions extends RealDataTypeOptions, NumberOptions { + type: 'real' | typeof DataTypes.REAL; +} + +export interface StringOptions extends StringDataTypeOptions, AbstractColumnOptions { + type: 'string' | typeof DataTypes.STRING; +} + +export interface PasswordOptions extends Omit { + type: 'password'; +} + +export interface TextOptions extends TextDataTypeOptions, AbstractColumnOptions { + type: 'text' | typeof DataTypes.TEXT; +} + +export interface TimeOptions extends DateDataTypeOptions, AbstractColumnOptions { + type: 'time' | typeof DataTypes.TIME; +} + +export interface DateOptions extends DateDataTypeOptions, AbstractColumnOptions { + type: 'date' | typeof DataTypes.DATE; +} + +export interface DateOnlyOptions extends AbstractColumnOptions { + type: 'dateonly' | typeof DataTypes.DATEONLY; +} + +export interface ArrayOptions extends AbstractColumnOptions { + type: 'array' | typeof DataTypes.ARRAY; + items?: any; +} + +export interface JsonOptions extends AbstractColumnOptions { + type: 'json' | 'jsonb' | typeof DataTypes.JSON | typeof DataTypes.JSONB; + fields?: any; +} + +export interface VirtualOptions extends AbstractColumnOptions { + type: 'virtual'; +} + +export interface FormulaOptions extends AbstractColumnOptions { + type: 'formula'; + formula: string; + format: 'string' | 'number'; +} + +export interface ReferenceOptions extends AbstractColumnOptions { + type: 'reference'; + source: string; + dataIndex: string; +} + +export interface HasOneOptions extends SequelizeHasOneOptions, AbstractRelationOptions { + type: 'hasOne' | 'hasone'; +} + +export interface HasManyOptions extends SequelizeHasManyOptions, AbstractRelationOptions { + type: 'hasMany' | 'hasmany'; + /** + * The name of the field to use as the key for the association in the source table. + * Defaults to the primary key of the source table + */ + sourceKey?: string; + /** + * Defaults to source singular name + sourceKey + */ + foreignKey?: string; +} + +export interface BelongsToOptions extends SequelizeBelongsToOptions, AbstractRelationOptions { + type: 'belongsTo' | 'belongsto'; + /** + * The name of the field to use as the key for the association in the target table. + * Defaults to the primary key of the target table + */ + targetKey?: string; + /** + * Defaults to name + targetKey + */ + foreignKey?: string; +} + +export interface BelongsToManyOptions extends Omit, AbstractRelationOptions { + type: 'belongsToMany' | 'belongstomany'; + /** + * Defaults to the name of source + the name of target + * + * 两个 name 按字母顺序排序之后连接,如: + * users.belongsToMany(posts) -> through = posts_users + */ + through?: string; + /** + * Defaults to the primary key of the source table + */ + sourceKey?: string; + /** + * Defaults to the name of source + sourceKey + */ + foreignKey?: string; + /** + * Defaults to the primary key of the target table + */ + targetKey?: string; + /** + * Defaults to the name of target + targetKey + */ + otherKey?: string; +} + +export type ColumnOptions = AbstractFieldOptions + | BooleanOptions + | NumberOptions + | IntegerOptions + | FloatOptions + | DoubleOptions + | DecimalOptions + | RealOptions + | StringOptions + | PasswordOptions + | TextOptions + | TimeOptions + | DateOptions + | DateOnlyOptions + | ArrayOptions + | JsonOptions + | VirtualOptions + | FormulaOptions + | ReferenceOptions; + +export type ElementOptions = BooleanOptions + | IntegerOptions + | FloatOptions + | DoubleOptions + | DecimalOptions + | RealOptions + | StringOptions + | TextOptions + | TimeOptions + | DateOptions + | DateOnlyOptions + | ArrayOptions + | JsonOptions + | VirtualOptions; + +export type RelationOptions = HasOneOptions | HasManyOptions | BelongsToOptions | BelongsToManyOptions; + +export type FieldOptions = ColumnOptions | RelationOptions; diff --git a/packages/database/src/index.ts b/packages/database/src/index.ts new file mode 100644 index 000000000..4555b98af --- /dev/null +++ b/packages/database/src/index.ts @@ -0,0 +1,9 @@ +import Database from './database'; + +export * from './database'; +export * from './model'; +export * from './table'; +export * from './fields'; +export * from './utils'; + +export default Database; diff --git a/packages/database/src/model.ts b/packages/database/src/model.ts new file mode 100644 index 000000000..5ef640651 --- /dev/null +++ b/packages/database/src/model.ts @@ -0,0 +1,418 @@ +import { + Model as SequelizeModel, Op, Sequelize, ProjectionAlias, Utils, SaveOptions, +} from 'sequelize'; +import Database from './database'; +import { HasOne, HasMany, BelongsTo, BelongsToMany, getDataTypeKey } from './fields'; +import { toInclude } from './utils'; + +export interface ApiJsonOptions { + + /** + * 字段 + * + * 数组式: + * ['col', 'association.col1', 'association_count'], + * + * 白名单: + * { + * only: ['col1'], + * appends: ['association_count'], + * } + * + * 黑名单: + * { + * except: ['col1'], + * appends: ['association_count'], + * } + */ + fields?: string[] | { + only?: string[]; + appends?: string[]; + } | { + except?: string[]; + appends?: string[]; + }; + + /** + * 过滤 + * + * 常规用法: + * { + * col1: { + * $eq: 'val1' + * }, + * } + * + * scope 的用法(如果 scope 与 col 同名,只会执行 scope): + * { + * scope1: value + * } + * + * json 数据 & 关系数据,可以用点号: + * { + * 'association.col1': { + * $eq: 'val1' + * }, + * } + * + * meta 为 json 字段时 + * { + * 'meta.key': { + * $eq: 'val1' + * }, + * } + * + * json 数据 & 关系数据的查询也可以不用点号: + * { + * association: { + * col1: { + * $eq: 'val1' + * }, + * }, + * } + */ + filter?: any; + + /** + * 排序 + * + * TODO + * + * ['col1', '-col2', 'association.col1', '-association.col2'] + */ + sort?: any; + + context?: any; + + [key: string]: any; +} + +export interface WithCountAttributeOptions { + + /** + * 关系名 + */ + association: string; + + /** + * SourceModel 别名 + * + * 在 include 里使用时,需要指定,一般与 include 的 association 同名 + * + * include: { + * association: 'user', // Post.belongsTo(User) + * attributes: [ + * User.withCountAttribute({ + * association: 'posts', + * sourceAlias: 'user', // 内嵌时,需要指定 source 别名 + * }) + * ] + * } + */ + sourceAlias?: string; + + where?: any; + + /** + * 别名,默认为 association_count + */ + alias?: string; + + [key: string]: any; +} + +/** + * Model 相关 + * + * TODO: 自定义 model 时的提示问题 + */ +// @ts-ignore +export abstract class Model extends SequelizeModel { + + /** + * 防止 ts 报错提示 + */ + [key: string]: any; + + /** + * 当前 Model 的 database + * + * 与 Model.sequelize 对应,database 也用了 public static readonly + */ + public static database: Database; + + /** + * 供 model 实例访问的 database + */ + get database(): Database { + // @ts-ignore + return this.constructor.database; + } + + /** + * sub query 关联数据的数量 + * + * TODO: 关联字段暂不支持主键以外的字段 + * + * @param options + */ + static withCountAttribute(options?: string | WithCountAttributeOptions): (string | ProjectionAlias) { + if (typeof options === 'string') { + options = { association: options }; + } + + const { sourceAlias, association, where = {}, alias, ...restOptions } = options; + const associator = this.associations[association]; + const table = this.database.getTable(this.name); + const field = table.getField(association); + const { targetKey, otherKey, foreignKey, sourceKey } = field.options as any; + + if (associator.associationType === 'HasMany') { + where[foreignKey as string] = { + [Op.eq]: Sequelize.col(`${sourceAlias||this.name}.${sourceKey}`), + }; + } else if (associator.associationType === 'BelongsToMany') { + where[targetKey] = { + // @ts-ignore + [Op.in]: Sequelize.literal(`(${associator.through.model.selectQuery({ + attributes: [otherKey], + where: { + [foreignKey]: { + [Op.eq]: Sequelize.col(`${sourceAlias||this.name}.${sourceKey}`), + }, + // @ts-ignore + ...(associator.through.scope||{}), + }, + })})`), + }; + } + + let countLiteral = 'count(*)'; + + if (this.database.sequelize.getDialect() === 'postgres') { + countLiteral = 'cast(count(*) as integer)'; + } + + const attribute = [ + Sequelize.literal( + // @ts-ignore + `(${associator.target.selectQuery({ + ...restOptions, + attributes: [[Sequelize.literal(countLiteral), 'count']], + where: { + // @ts-ignore + ...where, ...(associator.scope||{}), + }, + })})` + ), + alias || Utils.underscoredIf(`${association}Count`, this.options.underscored), + ].filter(Boolean); + + return attribute as ProjectionAlias; + } + + /** + * 当前 Model 的 SQL + * + * @param options + */ + static selectQuery(options = {}): string { + // @ts-ignore + return this.queryGenerator.selectQuery( + this.getTableName(), + options, + this, + ).replace(/;$/, ''); + } + + static parseApiJson(options: ApiJsonOptions) { + const { fields, filter, sort, context, ...restOptions } = options; + const data = toInclude({fields, filter, sort}, { + Model: this, + associations: this.associations, + dialect: this.sequelize.getDialect(), + ctx: context, + }); + if (data.attributes && data.attributes.length === 0) { + delete data.attributes; + } + return data; + } + + /** + * 关联数据的更新 + * + * TODO: 暂不支持除主键以外关联字段的更新 + * + * @param data + */ + async updateAssociations(data: any, options?: SaveOptions & { context?: any }) { + const model = this; + const name = this.constructor.name; + const table = this.database.getTable(name); + for (const [key, association] of table.getAssociations()) { + if (!data[key]) { + continue; + } + let item = data[key]; + const accessors = association.getAccessors(); + if (association instanceof BelongsTo || association instanceof HasOne) { + if (typeof item === 'number' || typeof item === 'string') { + await model[accessors.set](item, options); + continue; + } + if (item instanceof SequelizeModel) { + await model[accessors.set](item, options); + continue; + } + if (typeof item !== 'object') { + continue; + } + const Target = association.getTargetModel(); + const targetAttribute = association instanceof BelongsTo + ? association.options.targetKey + : association.options.sourceKey; + if (item[targetAttribute]) { + await model[accessors.set](item[targetAttribute], options); + if (Object.keys(item).length > 1) { + const target = await Target.findOne({ + where: { + [targetAttribute]: item[targetAttribute], + }, + }); + await target.update(item, options); + // @ts-ignore + await target.updateAssociations(item, options); + } + continue; + } + const t = await model[accessors.create](item, options); + await t.updateAssociations(item, options); + } + if (association instanceof HasMany || association instanceof BelongsToMany) { + if (!Array.isArray(item)) { + item = [item]; + } + if (item.length === 0) { + continue; + } + await model[accessors.set](null, options); + const Target = association.getTargetModel(); + await Promise.all(item.map(async value => { + let target: SequelizeModel; + let targetKey: string; + // 支持 number 和 string 类型的字段作为关联字段 + if (typeof value === 'number' || typeof value === 'string') { + targetKey = (association instanceof BelongsToMany ? association.options.targetKey : Target.primaryKeyAttribute) as string; + let targetKeyType = getDataTypeKey(Target.rawAttributes[targetKey].type).toLocaleLowerCase(); + if (targetKeyType === 'integer') { + targetKeyType = 'number'; + } + let primaryKeyType = getDataTypeKey(Target.rawAttributes[Target.primaryKeyAttribute].type).toLocaleLowerCase(); + if (primaryKeyType === 'integer') { + primaryKeyType = 'number'; + } + if (typeof value === targetKeyType) { + target = await Target.findOne({ + where: { + [targetKey] : value, + }, + }); + } + if (Target.primaryKeyAttribute !== targetKey && !target && typeof value === primaryKeyType) { + target = await Target.findOne({ + where: { + [Target.primaryKeyAttribute] : value, + }, + }); + } + if (!target) { + console.log(targetKey); + throw new Error(`target [${value}] does not exist`); + } + return await model[accessors.add](target, options); + } + if (value instanceof SequelizeModel) { + if (association instanceof HasMany) { + return await model[accessors.add](value.getDataValue(Target.primaryKeyAttribute), options); + } + return await model[accessors.add](value, options); + } + if (typeof value !== 'object') { + return; + } + targetKey = association.options.targetKey as string; + // 如果有主键,直接查询主键 + if (value[Target.primaryKeyAttribute]) { + target = await Target.findOne({ + where: { + [Target.primaryKeyAttribute]: value[Target.primaryKeyAttribute], + }, + }); + } + // 如果主键和关系字段配置的不一样 + else if (Target.primaryKeyAttribute !== targetKey && value[targetKey]) { + target = await Target.findOne({ + where: { + [targetKey]: value[targetKey], + }, + }); + } + if (target) { + await model[accessors.add](target, options); + if (Object.keys(value).length > 1) { + await target.update(value, options); + // @ts-ignore + await target.updateAssociations(value, options); + } + if (association instanceof BelongsToMany) { + const ThroughModel = association.getThroughModel(); + const throughName = association.getThroughName(); + if (typeof value[throughName] === 'object') { + const { foreignKey, sourceKey, otherKey, targetKey } = association.options; + const through = await ThroughModel.findOne({ + where: { + [foreignKey]: this.get(sourceKey), + [otherKey]: target.get(targetKey), + }, + }); + const throughValues = value[throughName]; + await through.update(throughValues); + await through.updateAssociations(throughValues); + } + } + return; + } + const t = await model[accessors.create](value, options); + // console.log(t); + await model[accessors.add](t, options); + await t.updateAssociations(value, options); + if (association instanceof BelongsToMany) { + const ThroughModel = association.getThroughModel(); + const throughName = association.getThroughName(); + if (typeof value[throughName] === 'object') { + const { foreignKey, sourceKey, otherKey, targetKey } = association.options; + const through = await ThroughModel.findOne({ + where: { + [foreignKey]: this.get(sourceKey), + [otherKey]: t.get(targetKey), + }, + }); + const throughValues = value[throughName]; + await through.update(throughValues); + await through.updateAssociations(throughValues); + } + } + return; + })); + } + } + } +} + +/** + * ModelCtor 需要为当前 Model 的 + */ +export type ModelCtor = typeof Model & { new(): M } & { [key: string]: any }; + +export default Model; diff --git a/packages/database/src/table.ts b/packages/database/src/table.ts new file mode 100644 index 000000000..6cf9c80d5 --- /dev/null +++ b/packages/database/src/table.ts @@ -0,0 +1,345 @@ +import { + InitOptions, + ModelAttributes, + ModelOptions, + ModelIndexesOptions, + Utils, + SyncOptions, +} from 'sequelize'; +import { + buildField, + FieldOptions, + Relation, + BelongsTo, + BelongsToMany, +} from './fields'; +import Database from './database'; +import { Model, ModelCtor } from './model'; + +export interface TableOptions extends Omit, 'name'|'modelName'> { + + /** + * 唯一标识,与 ModelOptions 的 name 有区别 + * + * 注意:name 可用于初始化 tableName、modelName,但是 tableName 和 modelName 可能有所不同 + * name 主要用于获取指定的 tables 和 models + * 如果没有特殊指定 tableName 时,name、tableName、modelName 都是一个值 + * + * TODO: name, tableName, modelName,freezeTableName,underscored,单复数等等情况还比较混乱 + */ + name: string; + + /** + * 自定义 model + */ + model?: ModelCtor; + + /** + * 字段配置 + */ + fields?: Array; + + /** + * 其他的一些情况 + */ + [key: string]: any; +} + +/** + * 上下文,Tabel 配置需要的其他变量 + */ +export interface TabelContext { + database: Database; +} + +/** + * 是否重新初始化 Model。配置更新时,需要重新初始化 model。 + * + * - reinitialize = false 只执行 Model.init + * - reinitialize = true 重新初始化 Model 并执行 Model.init 和 Model.associate + * - reinitialize = modelOnly 只初始化 Model 并执行 Model.init + */ +export type Reinitialize = boolean | 'modelOnly'; + +/** + * 表配置 + * + * 用于处理相关 Model 的配置 + */ +export class Table { + + protected database: Database; + + protected options: TableOptions; + + protected fields = new Map(); + + protected modelAttributes: ModelAttributes; + + protected modelOptions: InitOptions; + + /** + * 全部关系字段的配置 + */ + protected associations = new Map(); + + /** + * 待建立关系的 association + */ + protected associating = new Map(); + + /** + * 索引 + */ + protected indexes = new Map(); + + protected Model: ModelCtor; + + /** + * 是否是中间表 + */ + public isThroughTable: boolean = false; + + public relationTables = new Set(); + + constructor(options: TableOptions, context: TabelContext) { + const { database } = context; + const { + name, + fields = [], + indexes = [], + ...restOptions + } = options; + this.options = options; + this.database = database; + this.modelOptions = { + modelName: name, + tableName: name, + sequelize: database.sequelize, + ...restOptions, + }; + this.modelAttributes = {}; + // 在 set fields 之前 model init 的原因是因为关系字段可能需要用到 model 的相关配置 + this.addIndexes(indexes, 'modelOnly'); + // this.modelInit('modelOnly'); + this.setFields(fields); + } + + public modelInit(reinitialize: Reinitialize = false) { + if (reinitialize || !this.Model) { + this.Model = this.options.model || class extends Model {}; + this.Model.database = this.database; + // 关系的建立是在 model.init 之后,在配置中表字段(Column)和关系(Relation)都在 fields, + // 所以需要单独提炼出 associations 字段,并在 Model.init 之后执行 Model.associate + // @ts-ignore + this.Model.associate = (models: {[key: string]: ModelCtor}) => { + for (const [key, association] of this.associating) { + const { type, target } = association.getAssociationArguments(); + if (this.database.isDefined(target)) { + const TargetModel = this.database.getModel(target); + // 如果关系表在之后才定义,未设置 targetKey 时,targetKey 默认值需要在 target model 初始化之后才能取到 + if (association instanceof BelongsTo || association instanceof BelongsToMany) { + association.updateOptionsAfterTargetModelBeDefined(); + } + this.Model[type](TargetModel, association.getAssociationOptions()); + // 建立关系之后,需要删除待处理的 associating,避免重复和提高效率 + this.associating.delete(key); + } + } + } + } + + this.Model.init(this.getModelAttributes(), this.getModelOptions()); + + if (reinitialize === true) { + this.associating = new Map(this.associations); + // 需要额外处理 associating 的情况 + // 建立表关系需要遍历多个 Model,所以在这里需要标记哪些已定义的 Model 需要建立表关系 + if (this.associating.size > 0) { + this.database.associating.add(this.options.name); + } else { + this.database.associating.delete(this.options.name); + } + this.database.associate(); + } + } + + public getName(): string { + return this.options.name; + } + + public getTableName(): string { + return this.modelOptions.tableName; + } + + public getOptions(): TableOptions { + return this.options; + } + + public getModel(): ModelCtor { + return this.database.getModel(this.getName()); + } + + public getModelAttributes(): ModelAttributes { + return this.modelAttributes; + } + + public getModelOptions(): InitOptions { + const { underscored = true } = this.modelOptions; + return { + underscored, + createdAt: Utils.underscoredIf('createdAt', underscored), + updatedAt: Utils.underscoredIf('updatedAt', underscored), + indexes: Array.from(this.indexes.values()), + // freezeTableName: true, + ...this.modelOptions, + }; + } + + /** + * 获取相关 table names,一般用于 sync + */ + public getRelatedTableNames(): Set { + const names = new Set(); + names.add(this.options.name); + for (const association of this.associations.values()) { + const target = association.getTarget(); + names.add(target); + if (association instanceof BelongsToMany) { + names.add(association.getThroughName()); + } + } + return names; + } + + public getAssociations() { + return this.associations; + } + + public getFields() { + return this.fields; + } + + public setFields(fields: Array) { + this.fields.clear(); + this.associating.clear(); + this.associations.clear(); + for (const key in fields) { + this.addField(fields[key], false); + } + this.modelInit(true); + } + + public hasField(name: string) { + return this.fields.has(name); + } + + public getField(name: string) { + return this.fields.get(name); + } + + /** + * 添加字段 + * + * @param options + * @param reinitialize + */ + public addField(options: FieldOptions, reinitialize: Reinitialize = true) { + const { name, index } = options; + const field = buildField(options, { + sourceTable: this, + database: this.database, + }); + this.fields.set(name, field); + if (field instanceof Relation) { + // 关系字段先放到 associating 里待处理,等相关 target model 初始化之后,再通过 associate 建立关系 + this.associating.set(name, field); + this.associations.set(name, field); + } else { + if (index === true) { + this.addIndex(name, false); + } else if (typeof index === 'object') { + this.addIndex({ + fields: [name], + ...index, + }, false); + } + this.modelAttributes[name] = field.getAttributeOptions(); + } + this.modelInit(reinitialize); + return field; + } + + /** + * 添加索引 + * + * @param options + * @param reinitialize + */ + public addIndex(options: string | ModelIndexesOptions, reinitialize: Reinitialize = true) { + if (typeof options === 'string') { + options = { + fields: [options], + }; + } + // @ts-ignore + const index = Utils.nameIndex(options, this.modelOptions.tableName); + this.indexes.set(index.name, { + type: '', + parser: null, + ...index, + }); + this.modelInit(reinitialize); + } + + /** + * 批量添加索引 + * + * @param indexes + * @param reinitialize + */ + public addIndexes(indexes: Array, reinitialize: Reinitialize = true) { + for (const index in indexes) { + this.addIndex(indexes[index], false); + } + this.modelInit(reinitialize); + } + + /** + * 扩展(实验性 API) + * + * @param options + */ + public extend(options: TableOptions) { + const { fields = [], indexes = [], ...restOptions } = options; + this.modelOptions = { + ...this.modelOptions, + ...restOptions as any, + }; + // @ts-ignore + this.options = Utils.merge(this.options, restOptions); + for (const key in fields) { + this.addField(fields[key], false); + } + this.addIndexes(indexes, false); + this.modelInit(true); + } + + /** + * 相关表字段更新 + * + * @param options + */ + public async sync(options: SyncOptions = {}) { + const tables = []; + for (const name of this.getRelatedTableNames()) { + tables.push(name); + } + return this.database.sync({ + ...options, + tables, + }); + } +} + +export default Table; \ No newline at end of file diff --git a/packages/database/src/utils.ts b/packages/database/src/utils.ts new file mode 100644 index 000000000..ab6dbb9ac --- /dev/null +++ b/packages/database/src/utils.ts @@ -0,0 +1,315 @@ +import { Op, Utils, Sequelize } from 'sequelize'; +import Model, { ModelCtor } from './model'; +import _ from 'lodash'; + +const op = new Map(); + +for (const key in Op) { + op.set(key, Op[key]); + const val = Utils.underscoredIf(key, true); + op.set(val, Op[key]); + op.set(val.replace(/_/g, ''), Op[key]); +} + +interface ToWhereContext { + Model?: ModelCtor | Model | typeof Model; + associations?: any; + dialect?: string; + ctx?: any; +} + +export function toWhere(options: any, context: ToWhereContext = {}) { + if (options === null || Array.isArray(options) || typeof options !== 'object') { + return options; + } + const { Model, associations = {}, ctx, dialect } = context; + const items = {}; + // 先处理「点号」的问题 + for (const key in options) { + _.set(items, key, options[key]); + } + const values = {}; + for (const key in items) { + if (associations[key]) { + values['$__include'] = values['$__include'] || {} + values['$__include'][key] = toWhere(items[key], { + ...context, + Model: associations[key].target, + associations: associations[key].target.associations, + }); + } + else if (Model && Model.options.scopes && Model.options.scopes[key]) { + values['$__scopes'] = values['$__scopes'] || []; + const scope = Model.options.scopes[key]; + if (typeof scope === 'function') { + values['$__scopes'].push({ method: [key, items[key], ctx] }); + } else { + values['$__scopes'].push(key); + } + } + else { + values[op.has(key) ? op.get(key) : key] = toWhere(items[key], context); + } + } + return values; +} + +interface ToIncludeContext { + Model?: ModelCtor | Model | typeof Model; + sourceAlias?: string; + associations?: any; + dialect?: string; + ctx?: any +} + +export function toInclude(options: any, context: ToIncludeContext = {}) { + const { fields = [] } = options; + const { Model, sourceAlias, associations = {}, ctx, dialect } = context; + + let where = options.where || {}; + + if (options.filter) { + where = toWhere(options.filter, { + Model, + associations, + ctx, + }) || {}; + } + + const includeWhere = Utils.cloneDeep(where.$__include||{}); + const scopes = Utils.cloneDeep(where.$__scopes||[]); + + delete where.$__include; + delete where.$__scopes; + + const attributes = { + only: [], + except: [], + appends: [], + }; + + const include = new Map(); + const children = new Map(); + + const items = Array.isArray(fields) ? { only: fields } : fields; + + let sort = options.sort; + + if (sort && typeof sort === 'string') { + sort = sort.split(','); + } + + const order = []; + + if (Array.isArray(sort) && sort.length > 0) { + items.appends = items.appends || []; + sort.forEach(key => { + if (Array.isArray(key)) { + order.push(key); + } else { + const direction = key.indexOf('-') === 0 ? 'DESC' : 'ASC'; + const field = key.replace(/^-/, ''); + items.appends.push(field); + // TODO:暂时只支持 postgresql + order.push([Sequelize.literal(dialect === 'mysql' ? `\`${field}\`` : `"${field}"`), direction]); + } + }); + } + + if (Array.isArray(items.only) && items.only.length > 0) { + items.only.forEach(field => { + const arr: Array = Array.isArray(field) ? Utils.cloneDeep(field) : field.split('.'); + const col = arr.shift(); + // 内嵌的情况 + if (arr.length > 0) { + if (!children.has(col)) { + children.set(col, { + fields: { + only: [arr], + except: [], + appends: [], + }, + }); + } else { + children.get(col).fields.only.push(arr); + } + return; + } + // 关系字段 + if (associations[col]) { + const includeItem: any = { + association: col, + }; + if (includeWhere[col]) { + includeItem.where = includeWhere[col]; + } + include.set(col, includeItem); + return; + } + const matches: Array = /(.+)\_count$/.exec(col); + if (matches && associations[matches[1]]) { + attributes.only.push(Model.withCountAttribute({ + association: matches[1], + sourceAlias: sourceAlias + })); + } else { + attributes.only.push(col); + } + }); + } + + if (Array.isArray(items.appends) && items.appends.length > 0) { + items.appends.forEach(field => { + const arr: Array = Array.isArray(field) ? Utils.cloneDeep(field) : field.split('.'); + const col = arr.shift(); + // 内嵌的情况 + if (arr.length > 0) { + if (!children.has(col)) { + children.set(col, { + fields: { + only: [], + except: [], + appends: [arr], + }, + }); + } else { + children.get(col).fields.appends.push(arr); + } + return; + } + // 关系字段 + if (associations[col]) { + const includeItem: any = { + association: col, + }; + if (includeWhere[col]) { + includeItem.where = includeWhere[col]; + } + include.set(col, includeItem); + return; + } + const matches: Array = /(.+)\_count$/.exec(col); + if (matches && associations[matches[1]]) { + attributes.appends.push(Model.withCountAttribute({ + association: matches[1], + sourceAlias: sourceAlias + })); + } else { + attributes.appends.push(col); + } + }); + } + + if (Array.isArray(items.except) && items.except.length > 0) { + items.except.forEach(field => { + const arr: Array = Array.isArray(field) ? Utils.cloneDeep(field) : field.split('.'); + const col = arr.shift(); + // 内嵌的情况 + if (arr.length > 0) { + if (!children.has(col)) { + children.set(col, { + where: includeWhere[col], + fields: { + only: [], + except: [arr], + appends: [], + }, + }); + } else { + children.get(col).fields.except.push(arr); + } + return; + } + // 黑名单里只有字段 + attributes.except.push(col); + }); + } + + for (const whereKey in includeWhere) { + if (children.has(whereKey)) { + children.get(whereKey).where = includeWhere[whereKey]; + } else { + children.set(whereKey, { + association: whereKey, + fields: [], + where: includeWhere[whereKey], + }); + } + } + + for (const [key, child] of children) { + const result = toInclude(child, { + ...context, + Model: associations[key].target, + sourceAlias: key, + associations: associations[key].target.associations, + }); + const item: any = { + association: key, + } + if (result.attributes) { + item.attributes = result.attributes; + } + if (result.include) { + item.include = result.include; + } + if (result.where) { + item.where = result.where; + } + if (result.scopes) { + item.model = associations[key].target.scope(result.scopes); + } + include.set(key, item); + } + + const data:any = {}; + + // 存在黑名单时 + if (attributes.except.length > 0) { + data.attributes = { + include: attributes.appends, + exclude: attributes.except, + }; + } + // 存在白名单时 + else if (attributes.only.length > 0) { + data.attributes = [...attributes.only, ...attributes.appends]; + } + // 只有附加字段时 + else if (attributes.appends.length > 0) { + data.attributes = { + include: attributes.appends, + }; + } + + if (include.size > 0) { + if (!data.attributes) { + data.attributes = []; + } + data.include = Array.from(include.values()); + } + + if (Object.keys(where).length > 0) { + data.where = where; + } + + if (scopes.length > 0) { + data.scopes = scopes; + } + + if (order.length > 0) { + data.order = order; + } + + return data; +} + +export function requireModule(module: any) { + if (typeof module === 'string') { + module = require(module); + } + if (typeof module !== 'object') { + return module; + } + return module.__esModule ? module.default : module; +} diff --git a/packages/father-build/.local b/packages/father-build/.local new file mode 100644 index 000000000..9cbae257c --- /dev/null +++ b/packages/father-build/.local @@ -0,0 +1 @@ +Used in bin/father.js to determine if it is in the local debug state. diff --git a/packages/father-build/.npmignore b/packages/father-build/.npmignore new file mode 100644 index 000000000..75230a7a6 --- /dev/null +++ b/packages/father-build/.npmignore @@ -0,0 +1,4 @@ +/src/fixtures +/src/**/*.test.ts +/lib/**/*.test.js +/.local diff --git a/packages/father-build/README.md b/packages/father-build/README.md new file mode 100644 index 000000000..181a2fceb --- /dev/null +++ b/packages/father-build/README.md @@ -0,0 +1,4 @@ +# father-build + +See our [main repo](https://github.com/umijs/father) for more information. + diff --git a/packages/father-build/bin/father-build.js b/packages/father-build/bin/father-build.js new file mode 100755 index 000000000..f201b7350 --- /dev/null +++ b/packages/father-build/bin/father-build.js @@ -0,0 +1,61 @@ +#!/usr/bin/env node + +const { existsSync } = require('fs'); +const { join } = require('path'); +const yParser = require('yargs-parser'); +const chalk = require('chalk'); +const signale = require('signale'); + +// print version and @local +const args = yParser(process.argv.slice(2)); +if (args.v || args.version) { + console.log(require('../package').version); + if (existsSync(join(__dirname, '../.local'))) { + console.log(chalk.cyan('@local')); + } + process.exit(0); +} + +// Notify update when process exits +const updater = require('update-notifier'); +const pkg = require('../package.json'); +updater({ pkg }).notify({ defer: true }); + +function stripEmptyKeys(obj) { + Object.keys(obj).forEach((key) => { + if (!obj[key] || (Array.isArray(obj[key]) && !obj[key].length)) { + delete obj[key]; + } + }); + return obj; +} + +function build() { + // Parse buildArgs from cli + const buildArgs = stripEmptyKeys({ + esm: args.esm && { type: args.esm === true ? 'rollup' : args.esm }, + cjs: args.cjs && { type: args.cjs === true ? 'rollup' : args.cjs }, + umd: args.umd && { name: args.umd === true ? undefined : args.umd }, + file: args.file, + target: args.target, + entry: args._, + }); + + if (buildArgs.file && buildArgs.entry && buildArgs.entry.length > 1) { + signale.error(new Error( + `Cannot specify file when have multiple entries (${buildArgs.entry.join(', ')})` + )); + process.exit(1); + } + + require('../lib/build').default({ + cwd: args.root || process.cwd(), + watch: args.w || args.watch, + buildArgs, + }).catch(e => { + signale.error(e); + process.exit(1); + }); +} + +build(); diff --git a/packages/father-build/package.json b/packages/father-build/package.json new file mode 100644 index 000000000..12198d247 --- /dev/null +++ b/packages/father-build/package.json @@ -0,0 +1,80 @@ +{ + "name": "@nocobase/father-build", + "version": "0.3.0-alpha.0", + "description": "Library build tool based on rollup.", + "main": "lib/index.js", + "bin": { + "father-build": "./bin/father-build.js" + }, + "scripts": { + "build": "umi-tools build" + }, + "typings": "./index.d.ts", + "dependencies": { + "@babel/core": "7.4.5", + "@babel/plugin-proposal-class-properties": "7.4.4", + "@babel/plugin-proposal-decorators": "7.4.4", + "@babel/plugin-proposal-do-expressions": "7.2.0", + "@babel/plugin-proposal-export-default-from": "7.2.0", + "@babel/plugin-proposal-export-namespace-from": "7.2.0", + "@babel/plugin-proposal-nullish-coalescing-operator": "7.7.4", + "@babel/plugin-proposal-optional-chaining": "7.7.4", + "@babel/plugin-syntax-dynamic-import": "7.2.0", + "@babel/plugin-transform-modules-commonjs": "7.5.0", + "@babel/plugin-transform-runtime": "7.4.4", + "@babel/preset-env": "7.4.5", + "@babel/preset-react": "7.0.0", + "@babel/preset-typescript": "7.3.3", + "@babel/register": "7.4.4", + "@svgr/rollup": "^4.3.0", + "ajv": "6.10.0", + "autoprefixer": "9.6.0", + "babel-plugin-istanbul": "^5.2.0", + "babel-plugin-react-require": "3.1.1", + "chalk": "2.4.2", + "chokidar": "^3.0.2", + "glob": "^7.1.4", + "gulp-if": "2.0.2", + "gulp-less": "^4.0.1", + "gulp-plumber": "^1.2.1", + "gulp-typescript": "5.0.1", + "less": "3.9.0", + "less-plugin-npm-import": "2.1.0", + "lodash": "4.17.19", + "rimraf": "2.6.3", + "rollup": "1.27.8", + "rollup-plugin-babel": "4.3.3", + "rollup-plugin-commonjs": "10.0.0", + "rollup-plugin-inject": "3.0.1", + "rollup-plugin-json": "4.0.0", + "rollup-plugin-node-resolve": "5.0.1", + "rollup-plugin-postcss-umi": "2.0.3", + "rollup-plugin-replace": "2.2.0", + "rollup-plugin-terser": "5.1.3", + "rollup-plugin-typescript2": "0.25.3", + "rollup-plugin-url": "^2.2.2", + "signale": "1.4.0", + "slash2": "2.0.0", + "temp-dir": "2.0.0", + "through2": "3.0.1", + "ts-loader": "^6.0.2", + "typescript": "^3.7.3", + "update-notifier": "3.0.0", + "vinyl-fs": "3.0.3", + "yargs-parser": "13.1.2" + }, + "repository": { + "type": "git", + "url": "http://github.com/umijs/father" + }, + "homepage": "http://github.com/umijs/father", + "bugs": "http://github.com/umijs/father/issues", + "authors": [ + "chencheng (https://github.com/sorrycc)" + ], + "license": "MIT", + "devDependencies": { + "@types/gulp-plumber": "^0.0.32", + "umi-tools": "^0.4.0" + } +} diff --git a/packages/father-build/src/babel.ts b/packages/father-build/src/babel.ts new file mode 100644 index 000000000..634ef0e55 --- /dev/null +++ b/packages/father-build/src/babel.ts @@ -0,0 +1,238 @@ +import { join, extname, relative } from "path"; +import { existsSync, readFileSync, statSync } from "fs"; +import vfs from "vinyl-fs"; +import signale from "signale"; +import lodash from "lodash"; +import rimraf from "rimraf"; +import through from "through2"; +import slash from "slash2"; +import * as chokidar from "chokidar"; +import * as babel from "@babel/core"; +import gulpTs from "gulp-typescript"; +import gulpLess from "gulp-less"; +import gulpPlumber from 'gulp-plumber'; +import gulpIf from "gulp-if"; +import chalk from "chalk"; +import getBabelConfig from "./getBabelConfig"; +import { IBundleOptions } from "./types"; +import * as ts from "typescript"; + +interface IBabelOpts { + cwd: string; + rootPath?: string; + type: "esm" | "cjs"; + target?: "browser" | "node"; + log?: (string) => void; + watch?: boolean; + importLibToEs?: boolean; + bundleOpts: IBundleOptions; +} + +interface ITransformOpts { + file: { + contents: string; + path: string; + }; + type: "esm" | "cjs"; +} + +export default async function(opts: IBabelOpts) { + const { + cwd, + rootPath, + type, + watch, + importLibToEs, + log, + bundleOpts: { + target = "browser", + runtimeHelpers, + extraBabelPresets = [], + extraBabelPlugins = [], + browserFiles = [], + nodeFiles = [], + nodeVersion, + disableTypeCheck, + cjs, + include, + lessInBabelMode + } + } = opts; + const srcPath = join(cwd, "src"); + const targetDir = type === "esm" ? "es" : "lib"; + const targetPath = join(cwd, targetDir); + + log(chalk.gray(`Clean ${targetDir} directory`)); + rimraf.sync(targetPath); + + function transform(opts: ITransformOpts) { + const { file, type } = opts; + const { opts: babelOpts, isBrowser } = getBabelConfig({ + target, + type, + typescript: true, + runtimeHelpers, + filePath: slash(relative(cwd, file.path)), + browserFiles, + nodeFiles, + nodeVersion, + lazy: cjs && (cjs as any).lazy, + lessInBabelMode + }); + if (importLibToEs && type === "esm") { + babelOpts.plugins.push(require.resolve("../lib/importLibToEs")); + } + babelOpts.presets.push(...extraBabelPresets); + babelOpts.plugins.push(...extraBabelPlugins); + + const relFile = slash(file.path).replace(`${cwd}/`, ""); + log( + `Transform to ${type} for ${chalk[isBrowser ? "yellow" : "blue"]( + relFile + )}` + ); + + return babel.transform(file.contents, { + ...babelOpts, + filename: file.path, + // 不读取外部的babel.config.js配置文件,全采用babelOpts中的babel配置来构建 + configFile: false, + }).code; + } + + /** + * tsconfig.json is not valid json file + * https://github.com/Microsoft/TypeScript/issues/20384 + */ + function parseTsconfig(path: string) { + const readFile = (path: string) => readFileSync(path, "utf-8"); + const result = ts.readConfigFile(path, readFile); + if (result.error) { + return; + } + return result.config; + } + + function getTsconfigCompilerOptions(path: string) { + const config = parseTsconfig(path); + return config ? config.compilerOptions : undefined; + } + + function getTSConfig() { + const tsconfigPath = join(cwd, "tsconfig.json"); + const templateTsconfigPath = join(__dirname, "../template/tsconfig.json"); + + if (existsSync(tsconfigPath)) { + return getTsconfigCompilerOptions(tsconfigPath) || {}; + } + if (rootPath && existsSync(join(rootPath, "tsconfig.json"))) { + return getTsconfigCompilerOptions(join(rootPath, "tsconfig.json")) || {}; + } + return getTsconfigCompilerOptions(templateTsconfigPath) || {}; + } + + function createStream(src) { + const tsConfig = getTSConfig(); + const babelTransformRegexp = disableTypeCheck ? /\.(t|j)sx?$/ : /\.jsx?$/; + + function isTsFile(path) { + return /\.tsx?$/.test(path) && !path.endsWith(".d.ts"); + } + + function isTransform(path) { + return babelTransformRegexp.test(path) && !path.endsWith(".d.ts"); + } + + return vfs + .src(src, { + allowEmpty: true, + base: srcPath + }) + .pipe(watch ? gulpPlumber() : through.obj()) + .pipe( + gulpIf(f => !disableTypeCheck && isTsFile(f.path), gulpTs(tsConfig)) + ) + .pipe( + gulpIf( + f => lessInBabelMode && /\.less$/.test(f.path), + gulpLess(lessInBabelMode || {}) + ) + ) + .pipe( + gulpIf( + f => isTransform(f.path), + through.obj((file, env, cb) => { + try { + file.contents = Buffer.from( + transform({ + file, + type + }) + ); + // .jsx -> .js + file.path = file.path.replace(extname(file.path), ".js"); + cb(null, file); + } catch (e) { + signale.error(`Compiled faild: ${file.path}`); + console.log(e); + cb(null); + } + }) + ) + ) + .pipe(vfs.dest(targetPath)); + } + + return new Promise(resolve => { + const patterns = include ? [ + join(srcPath, include as any), + ] : [ + join(srcPath, "**/*"), + `!${join(srcPath, "**/fixtures{,/**}")}`, + `!${join(srcPath, "**/demos{,/**}")}`, + `!${join(srcPath, "**/__test__{,/**}")}`, + `!${join(srcPath, "**/*.mdx")}`, + `!${join(srcPath, "**/*.md")}`, + `!${join(srcPath, "**/*.+(test|e2e|spec).+(js|jsx|ts|tsx)")}` + ]; + createStream(patterns).on("end", () => { + if (watch) { + log( + chalk.magenta( + `Start watching ${slash(srcPath).replace( + `${cwd}/`, + "" + )} directory...` + ) + ); + const watcher = chokidar.watch(patterns, { + ignoreInitial: true + }); + + const files = []; + function compileFiles() { + while (files.length) { + createStream(files.pop()); + } + } + + const debouncedCompileFiles = lodash.debounce(compileFiles, 1000); + watcher.on("all", (event, fullPath) => { + const relPath = fullPath.replace(srcPath, ""); + log( + `[${event}] ${slash(join(srcPath, relPath)).replace(`${cwd}/`, "")}` + ); + if (!existsSync(fullPath)) return; + if (statSync(fullPath).isFile()) { + if (!files.includes(fullPath)) files.push(fullPath); + debouncedCompileFiles(); + } + }); + process.once("SIGINT", () => { + watcher.close(); + }); + } + resolve(); + }); + }); +} diff --git a/packages/father-build/src/build.test.ts b/packages/father-build/src/build.test.ts new file mode 100644 index 000000000..50c350cfc --- /dev/null +++ b/packages/father-build/src/build.test.ts @@ -0,0 +1,54 @@ +import { join, basename } from 'path'; +import { existsSync, readdirSync, renameSync, statSync } from 'fs'; +import mkdirp from 'mkdirp'; +import rimraf from 'rimraf'; +import build from './build'; + +function moveEsLibToDist(cwd) { + ['es', 'lib'].forEach(dir => { + const absDirPath = join(cwd, dir); + const absDistPath = join(cwd, 'dist'); + if (existsSync(absDirPath)) { + mkdirp.sync(absDistPath); + renameSync(absDirPath, join(absDistPath, dir)); + } + }); +} + +describe('father build', () => { + const rootConfigMapping = { + 'lerna-root-config-override': { cjs: 'rollup', esm: false }, + }; + + require('test-build-result')({ + root: join(__dirname, './fixtures/build'), + build({ cwd }) { + process.chdir(cwd); + rimraf.sync(join(cwd, 'dist')); + return build({ cwd, rootConfig: rootConfigMapping[basename(cwd)] }).then(() => { + // babel + moveEsLibToDist(cwd); + + // lerna + if (existsSync(join(cwd, 'lerna.json'))) { + mkdirp.sync(join(cwd, 'dist')); + const pkgs = readdirSync(join(cwd, 'packages')); + for (let pkg of pkgs) { + // TODO: hard code + if (pkg === '@hoo') { + pkg = '@hoo/bar'; + } + const pkgPath = join(cwd, 'packages', pkg); + if (!statSync(pkgPath).isDirectory()) continue; + moveEsLibToDist(pkgPath); + renameSync( + join(pkgPath, 'dist'), + // @foo/bar -> bar + join(cwd, 'dist', pkg.split('/').slice(-1).join('')) + ); + } + } + }); + }, + }); +}); diff --git a/packages/father-build/src/build.ts b/packages/father-build/src/build.ts new file mode 100644 index 000000000..871d4b51e --- /dev/null +++ b/packages/father-build/src/build.ts @@ -0,0 +1,238 @@ +import { existsSync, readdirSync, readFileSync, statSync } from 'fs'; +import { join } from 'path'; +import rimraf from 'rimraf'; +import * as assert from 'assert'; +import { merge } from 'lodash'; +import signale from 'signale'; +import chalk from 'chalk'; +import { IOpts, IBundleOptions, IBundleTypeOutput, ICjs, IEsm } from './types'; +import babel from './babel'; +import rollup from './rollup'; +import registerBabel from './registerBabel'; +import { getExistFile } from './utils'; +import getUserConfig, { CONFIG_FILES } from './getUserConfig'; +import randomColor from "./randomColor"; + +export function getBundleOpts(opts: IOpts): IBundleOptions[] { + const { cwd, buildArgs = {}, rootConfig = {} } = opts; + const entry = getExistFile({ + cwd, + files: ['src/index.tsx', 'src/index.ts', 'src/index.jsx', 'src/index.js'], + returnRelative: true, + }); + const userConfig = getUserConfig({ cwd }); + const userConfigs = Array.isArray(userConfig) ? userConfig : [userConfig]; + return (userConfigs as any).map(userConfig => { + const bundleOpts = merge( + { + entry, + }, + rootConfig, + userConfig, + buildArgs, + ); + + // Support config esm: 'rollup' and cjs: 'rollup' + if (typeof bundleOpts.esm === 'string') { + bundleOpts.esm = { type: bundleOpts.esm }; + } + if (typeof bundleOpts.cjs === 'string') { + bundleOpts.cjs = { type: bundleOpts.cjs }; + } + + return bundleOpts; + }); +} + +function validateBundleOpts(bundleOpts: IBundleOptions, { cwd, rootPath }) { + if (bundleOpts.runtimeHelpers) { + const pkgPath = join(cwd, 'package.json'); + assert.ok(existsSync(pkgPath), `@babel/runtime dependency is required to use runtimeHelpers`); + const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8')); + assert.ok( + (pkg.dependencies || {})['@babel/runtime'], + `@babel/runtime dependency is required to use runtimeHelpers`, + ); + } + if (bundleOpts.cjs && (bundleOpts.cjs as ICjs).lazy && (bundleOpts.cjs as ICjs).type === 'rollup') { + throw new Error(` +cjs.lazy don't support rollup. + `.trim()); + } + if (!bundleOpts.esm && !bundleOpts.cjs && !bundleOpts.umd) { + throw new Error( + ` +None format of ${chalk.cyan( + 'cjs | esm | umd', + )} is configured, checkout https://github.com/umijs/father for usage details. +`.trim(), + ); + } + if (bundleOpts.entry) { + const tsConfigPath = join(cwd, 'tsconfig.json'); + const tsConfig = existsSync(tsConfigPath) + || (rootPath && existsSync(join(rootPath, 'tsconfig.json'))); + if ( + !tsConfig && ( + (Array.isArray(bundleOpts.entry) && bundleOpts.entry.some(isTypescriptFile)) || + (!Array.isArray(bundleOpts.entry) && isTypescriptFile(bundleOpts.entry)) + ) + ) { + signale.info( + `Project using ${chalk.cyan('typescript')} but tsconfig.json not exists. Use default config.` + ); + } + } +} + +function isTypescriptFile(filePath) { + return filePath.endsWith('.ts') || filePath.endsWith('.tsx') +} + +interface IExtraBuildOpts { + pkg?: string; +} + +export async function build(opts: IOpts, extraOpts: IExtraBuildOpts = {}) { + const { cwd, rootPath, watch } = opts; + const { pkg } = extraOpts; + + // register babel for config files + registerBabel({ + cwd, + only: CONFIG_FILES, + }); + + function log(msg) { + console.log(`${pkg ? `${randomColor(`${pkg}`)}: ` : ''}${msg}`); + } + + // Get user config + const bundleOptsArray = getBundleOpts(opts); + for (const bundleOpts of bundleOptsArray) { + validateBundleOpts(bundleOpts, { cwd, rootPath }); + + // Clean dist + log(chalk.gray(`Clean dist directory`)); + rimraf.sync(join(cwd, 'dist')); + + // Build umd + if (bundleOpts.umd) { + log(`Build umd`); + await rollup({ + cwd, + log, + type: 'umd', + entry: bundleOpts.entry, + watch, + bundleOpts, + }); + } + + // Build cjs + if (bundleOpts.cjs) { + const cjs = bundleOpts.cjs as IBundleTypeOutput; + log(`Build cjs with ${cjs.type}`); + if (cjs.type === 'babel') { + await babel({ cwd, rootPath, watch, type: 'cjs', log, bundleOpts }); + } else { + await rollup({ + cwd, + log, + type: 'cjs', + entry: bundleOpts.entry, + watch, + bundleOpts, + }); + } + } + + // Build esm + if (bundleOpts.esm) { + const esm = bundleOpts.esm as IEsm; + log(`Build esm with ${esm.type}`); + const importLibToEs = esm && esm.importLibToEs; + if (esm && esm.type === 'babel') { + await babel({ cwd, rootPath, watch, type: 'esm', importLibToEs, log, bundleOpts }); + } else { + await rollup({ + cwd, + log, + type: 'esm', + entry: bundleOpts.entry, + importLibToEs, + watch, + bundleOpts, + }); + } + } + } +} + +export async function buildForLerna(opts: IOpts) { + const { cwd } = opts; + + // register babel for config files + registerBabel({ + cwd, + only: CONFIG_FILES, + }); + + const userConfig = merge(getUserConfig({ cwd }), opts.rootConfig || {}); + let pkgs = readdirSync(join(cwd, 'packages')); + + // support define pkgs in lerna + if (userConfig.pkgs) { + pkgs = userConfig.pkgs; + } + + // 支持 scope + pkgs = pkgs.reduce((memo, pkg) => { + const pkgPath = join(cwd, 'packages', pkg); + if (statSync(pkgPath).isDirectory()) { + if (pkg.startsWith('@')) { + readdirSync(join(cwd, 'packages', pkg)).filter(subPkg => { + if (statSync(join(cwd, 'packages', pkg, subPkg)).isDirectory()) { + memo = memo.concat(`${pkg}/${subPkg}`); + } + }); + } else { + memo = memo.concat(pkg); + } + } + return memo; + }, []); + + for (const pkg of pkgs) { + if (process.env.PACKAGE && pkg !== process.env.PACKAGE) continue; + // build error when .DS_Store includes in packages root + const pkgPath = join(cwd, 'packages', pkg); + assert.ok( + existsSync(join(pkgPath, 'package.json')), + `package.json not found in packages/${pkg}`, + ); + process.chdir(pkgPath); + await build( + { + // eslint-disable-line + ...opts, + buildArgs: opts.buildArgs, + rootConfig: userConfig, + cwd: pkgPath, + rootPath: cwd, + }, + { + pkg, + }, + ); + } +} + +export default async function(opts: IOpts) { + const useLerna = existsSync(join(opts.cwd, 'lerna.json')); + if (useLerna && process.env.LERNA !== 'none') { + await buildForLerna(opts); + } else { + await build(opts); + } +} diff --git a/packages/father-build/src/fixtures/build/babel-browser-files/.fatherrc.js b/packages/father-build/src/fixtures/build/babel-browser-files/.fatherrc.js new file mode 100644 index 000000000..3cd11ad83 --- /dev/null +++ b/packages/father-build/src/fixtures/build/babel-browser-files/.fatherrc.js @@ -0,0 +1,8 @@ + +export default { + esm: 'babel', + target: 'node', + browserFiles: [ + 'src/browser.js', + ], +} diff --git a/packages/father-build/src/fixtures/build/babel-browser-files/expected/es/browser.js b/packages/father-build/src/fixtures/build/babel-browser-files/expected/es/browser.js new file mode 100644 index 000000000..22dc0e15f --- /dev/null +++ b/packages/father-build/src/fixtures/build/babel-browser-files/expected/es/browser.js @@ -0,0 +1 @@ +var b = 'foo'; \ No newline at end of file diff --git a/packages/father-build/src/fixtures/build/babel-browser-files/expected/es/node.js b/packages/father-build/src/fixtures/build/babel-browser-files/expected/es/node.js new file mode 100644 index 000000000..ed389c023 --- /dev/null +++ b/packages/father-build/src/fixtures/build/babel-browser-files/expected/es/node.js @@ -0,0 +1 @@ +const a = 'foo'; \ No newline at end of file diff --git a/packages/father-build/src/fixtures/build/babel-browser-files/src/browser.js b/packages/father-build/src/fixtures/build/babel-browser-files/src/browser.js new file mode 100644 index 000000000..a3677a7e4 --- /dev/null +++ b/packages/father-build/src/fixtures/build/babel-browser-files/src/browser.js @@ -0,0 +1,2 @@ + +const b = 'foo'; diff --git a/packages/father-build/src/fixtures/build/babel-browser-files/src/node.js b/packages/father-build/src/fixtures/build/babel-browser-files/src/node.js new file mode 100644 index 000000000..528eb6b5a --- /dev/null +++ b/packages/father-build/src/fixtures/build/babel-browser-files/src/node.js @@ -0,0 +1,2 @@ + +const a = 'foo'; diff --git a/packages/father-build/src/fixtures/build/babel-cjs-lazy/.fatherrc.js b/packages/father-build/src/fixtures/build/babel-cjs-lazy/.fatherrc.js new file mode 100644 index 000000000..37192c105 --- /dev/null +++ b/packages/father-build/src/fixtures/build/babel-cjs-lazy/.fatherrc.js @@ -0,0 +1,8 @@ + +export default { + target: 'node', + cjs: { type: 'babel', lazy: true }, + browserFiles: [ + 'src/foo.js', + ], +}; diff --git a/packages/father-build/src/fixtures/build/babel-cjs-lazy/src/foo.js b/packages/father-build/src/fixtures/build/babel-cjs-lazy/src/foo.js new file mode 100644 index 000000000..fafe4ba5f --- /dev/null +++ b/packages/father-build/src/fixtures/build/babel-cjs-lazy/src/foo.js @@ -0,0 +1,3 @@ +import bar from 'bar'; + +bar(); diff --git a/packages/father-build/src/fixtures/build/babel-cjs-lazy/src/index.js b/packages/father-build/src/fixtures/build/babel-cjs-lazy/src/index.js new file mode 100644 index 000000000..fafe4ba5f --- /dev/null +++ b/packages/father-build/src/fixtures/build/babel-cjs-lazy/src/index.js @@ -0,0 +1,3 @@ +import bar from 'bar'; + +bar(); diff --git a/packages/father-build/src/fixtures/build/babel-extra-babel-presets-and-plugins/.fatherrc.js b/packages/father-build/src/fixtures/build/babel-extra-babel-presets-and-plugins/.fatherrc.js new file mode 100644 index 000000000..b90b773fa --- /dev/null +++ b/packages/father-build/src/fixtures/build/babel-extra-babel-presets-and-plugins/.fatherrc.js @@ -0,0 +1,10 @@ + +export default { + esm: { type: 'babel' }, + extraBabelPresets: [ + require.resolve('./preset'), + ], + extraBabelPlugins: [ + require.resolve('./p2'), + ], +}; diff --git a/packages/father-build/src/fixtures/build/babel-extra-babel-presets-and-plugins/expected/es/index.js b/packages/father-build/src/fixtures/build/babel-extra-babel-presets-and-plugins/expected/es/index.js new file mode 100644 index 000000000..dd712d79e --- /dev/null +++ b/packages/father-build/src/fixtures/build/babel-extra-babel-presets-and-plugins/expected/es/index.js @@ -0,0 +1,2 @@ +console.log("p1", "p2", 1); +alert(2); \ No newline at end of file diff --git a/packages/father-build/src/fixtures/build/babel-extra-babel-presets-and-plugins/p1.js b/packages/father-build/src/fixtures/build/babel-extra-babel-presets-and-plugins/p1.js new file mode 100644 index 000000000..a3a3e24e1 --- /dev/null +++ b/packages/father-build/src/fixtures/build/babel-extra-babel-presets-and-plugins/p1.js @@ -0,0 +1,20 @@ + +module.exports = function ({ types: t }) { + function isConsoleLog(node) { + const { callee, callee: { object, property } } = node; + return t.isMemberExpression(callee) + && t.isIdentifier(object) && object.name === 'console' + && t.isIdentifier(property) && property.name === 'log'; + } + + return { + visitor: { + CallExpression(path, state) { + const { node, node: { callee, callee: { object, property } } } = path; + if (isConsoleLog(node)) { + node.arguments.unshift(t.stringLiteral('p1')); + } + }, + }, + }; +} diff --git a/packages/father-build/src/fixtures/build/babel-extra-babel-presets-and-plugins/p2.js b/packages/father-build/src/fixtures/build/babel-extra-babel-presets-and-plugins/p2.js new file mode 100644 index 000000000..297ac66d1 --- /dev/null +++ b/packages/father-build/src/fixtures/build/babel-extra-babel-presets-and-plugins/p2.js @@ -0,0 +1,20 @@ + +module.exports = function ({ types: t }) { + function isConsoleLog(node) { + const { callee, callee: { object, property } } = node; + return t.isMemberExpression(callee) + && t.isIdentifier(object) && object.name === 'console' + && t.isIdentifier(property) && property.name === 'log'; + } + + return { + visitor: { + CallExpression(path, state) { + const { node, node: { callee, callee: { object, property } } } = path; + if (isConsoleLog(node)) { + node.arguments.unshift(t.stringLiteral('p2')); + } + }, + }, + }; +} diff --git a/packages/father-build/src/fixtures/build/babel-extra-babel-presets-and-plugins/preset.js b/packages/father-build/src/fixtures/build/babel-extra-babel-presets-and-plugins/preset.js new file mode 100644 index 000000000..07ccbb29c --- /dev/null +++ b/packages/father-build/src/fixtures/build/babel-extra-babel-presets-and-plugins/preset.js @@ -0,0 +1,8 @@ + +module.exports = function () { + return { + plugins: [ + require.resolve('./p1'), + ], + }; +} diff --git a/packages/father-build/src/fixtures/build/babel-extra-babel-presets-and-plugins/src/index.js b/packages/father-build/src/fixtures/build/babel-extra-babel-presets-and-plugins/src/index.js new file mode 100644 index 000000000..923799068 --- /dev/null +++ b/packages/father-build/src/fixtures/build/babel-extra-babel-presets-and-plugins/src/index.js @@ -0,0 +1,3 @@ + +console.log(1); +alert(2); diff --git a/packages/father-build/src/fixtures/build/babel-importLibToEs/.fatherrc.js b/packages/father-build/src/fixtures/build/babel-importLibToEs/.fatherrc.js new file mode 100644 index 000000000..f1ef9292b --- /dev/null +++ b/packages/father-build/src/fixtures/build/babel-importLibToEs/.fatherrc.js @@ -0,0 +1,5 @@ + +export default { + cjs: { type: 'babel' }, + esm: { type: 'babel', importLibToEs: true }, +}; diff --git a/packages/father-build/src/fixtures/build/babel-importLibToEs/expected/es/index.js b/packages/father-build/src/fixtures/build/babel-importLibToEs/expected/es/index.js new file mode 100644 index 000000000..d10ef2731 --- /dev/null +++ b/packages/father-build/src/fixtures/build/babel-importLibToEs/expected/es/index.js @@ -0,0 +1,2 @@ +import foo from "foo/es/foo"; +console.log(foo()); \ No newline at end of file diff --git a/packages/father-build/src/fixtures/build/babel-importLibToEs/src/index.js b/packages/father-build/src/fixtures/build/babel-importLibToEs/src/index.js new file mode 100644 index 000000000..a8105c428 --- /dev/null +++ b/packages/father-build/src/fixtures/build/babel-importLibToEs/src/index.js @@ -0,0 +1,3 @@ +import foo from 'foo/lib/foo'; + +console.log(foo()); diff --git a/packages/father-build/src/fixtures/build/babel-less-to-css-forbid/.fatherrc.js b/packages/father-build/src/fixtures/build/babel-less-to-css-forbid/.fatherrc.js new file mode 100644 index 000000000..ead5605aa --- /dev/null +++ b/packages/father-build/src/fixtures/build/babel-less-to-css-forbid/.fatherrc.js @@ -0,0 +1,6 @@ + +export default { + cjs: { type: 'babel' }, + esm: { type: 'babel' }, + lessInBabelMode: false, +}; diff --git a/packages/father-build/src/fixtures/build/babel-less-to-css-forbid/expected/es/foo.js b/packages/father-build/src/fixtures/build/babel-less-to-css-forbid/expected/es/foo.js new file mode 100644 index 000000000..85ffde227 --- /dev/null +++ b/packages/father-build/src/fixtures/build/babel-less-to-css-forbid/expected/es/foo.js @@ -0,0 +1,6 @@ + +import "./index.less"; +import "./foo.module.less"; +export default function () { + return 'foo'; +} \ No newline at end of file diff --git a/packages/father-build/src/fixtures/build/babel-less-to-css-forbid/expected/es/foo.module.less b/packages/father-build/src/fixtures/build/babel-less-to-css-forbid/expected/es/foo.module.less new file mode 100644 index 000000000..213d0676a --- /dev/null +++ b/packages/father-build/src/fixtures/build/babel-less-to-css-forbid/expected/es/foo.module.less @@ -0,0 +1,3 @@ +.foo { + color: green; +} diff --git a/packages/father-build/src/fixtures/build/babel-less-to-css-forbid/expected/es/index.less b/packages/father-build/src/fixtures/build/babel-less-to-css-forbid/expected/es/index.less new file mode 100644 index 000000000..f151aa5e7 --- /dev/null +++ b/packages/father-build/src/fixtures/build/babel-less-to-css-forbid/expected/es/index.less @@ -0,0 +1,6 @@ + +@link-color: green; + +.foo { + color: @link-color; +} diff --git a/packages/father-build/src/fixtures/build/babel-less-to-css-forbid/src/foo.js b/packages/father-build/src/fixtures/build/babel-less-to-css-forbid/src/foo.js new file mode 100644 index 000000000..e1cab23ea --- /dev/null +++ b/packages/father-build/src/fixtures/build/babel-less-to-css-forbid/src/foo.js @@ -0,0 +1,6 @@ +import "./index.less"; +import "./foo.module.less"; + +export default function () { + return 'foo'; +} diff --git a/packages/father-build/src/fixtures/build/babel-less-to-css-forbid/src/foo.module.less b/packages/father-build/src/fixtures/build/babel-less-to-css-forbid/src/foo.module.less new file mode 100644 index 000000000..213d0676a --- /dev/null +++ b/packages/father-build/src/fixtures/build/babel-less-to-css-forbid/src/foo.module.less @@ -0,0 +1,3 @@ +.foo { + color: green; +} diff --git a/packages/father-build/src/fixtures/build/babel-less-to-css-forbid/src/index.less b/packages/father-build/src/fixtures/build/babel-less-to-css-forbid/src/index.less new file mode 100644 index 000000000..f151aa5e7 --- /dev/null +++ b/packages/father-build/src/fixtures/build/babel-less-to-css-forbid/src/index.less @@ -0,0 +1,6 @@ + +@link-color: green; + +.foo { + color: @link-color; +} diff --git a/packages/father-build/src/fixtures/build/babel-less-to-css/.fatherrc.js b/packages/father-build/src/fixtures/build/babel-less-to-css/.fatherrc.js new file mode 100644 index 000000000..c2ed104b4 --- /dev/null +++ b/packages/father-build/src/fixtures/build/babel-less-to-css/.fatherrc.js @@ -0,0 +1,6 @@ + +export default { + cjs: { type: 'babel' }, + esm: { type: 'babel' }, + lessInBabelMode: true, +}; diff --git a/packages/father-build/src/fixtures/build/babel-less-to-css/expected/es/foo.js b/packages/father-build/src/fixtures/build/babel-less-to-css/expected/es/foo.js new file mode 100644 index 000000000..a70263317 --- /dev/null +++ b/packages/father-build/src/fixtures/build/babel-less-to-css/expected/es/foo.js @@ -0,0 +1,6 @@ + +import "./index.css"; +import "./foo.module.css"; +export default function () { + return 'foo'; +} \ No newline at end of file diff --git a/packages/father-build/src/fixtures/build/babel-less-to-css/expected/es/foo.module.css b/packages/father-build/src/fixtures/build/babel-less-to-css/expected/es/foo.module.css new file mode 100644 index 000000000..213d0676a --- /dev/null +++ b/packages/father-build/src/fixtures/build/babel-less-to-css/expected/es/foo.module.css @@ -0,0 +1,3 @@ +.foo { + color: green; +} diff --git a/packages/father-build/src/fixtures/build/babel-less-to-css/expected/es/index.css b/packages/father-build/src/fixtures/build/babel-less-to-css/expected/es/index.css new file mode 100644 index 000000000..213d0676a --- /dev/null +++ b/packages/father-build/src/fixtures/build/babel-less-to-css/expected/es/index.css @@ -0,0 +1,3 @@ +.foo { + color: green; +} diff --git a/packages/father-build/src/fixtures/build/babel-less-to-css/src/foo.js b/packages/father-build/src/fixtures/build/babel-less-to-css/src/foo.js new file mode 100644 index 000000000..e1cab23ea --- /dev/null +++ b/packages/father-build/src/fixtures/build/babel-less-to-css/src/foo.js @@ -0,0 +1,6 @@ +import "./index.less"; +import "./foo.module.less"; + +export default function () { + return 'foo'; +} diff --git a/packages/father-build/src/fixtures/build/babel-less-to-css/src/foo.module.less b/packages/father-build/src/fixtures/build/babel-less-to-css/src/foo.module.less new file mode 100644 index 000000000..213d0676a --- /dev/null +++ b/packages/father-build/src/fixtures/build/babel-less-to-css/src/foo.module.less @@ -0,0 +1,3 @@ +.foo { + color: green; +} diff --git a/packages/father-build/src/fixtures/build/babel-less-to-css/src/index.less b/packages/father-build/src/fixtures/build/babel-less-to-css/src/index.less new file mode 100644 index 000000000..f151aa5e7 --- /dev/null +++ b/packages/father-build/src/fixtures/build/babel-less-to-css/src/index.less @@ -0,0 +1,6 @@ + +@link-color: green; + +.foo { + color: @link-color; +} diff --git a/packages/father-build/src/fixtures/build/babel-node-files/.fatherrc.js b/packages/father-build/src/fixtures/build/babel-node-files/.fatherrc.js new file mode 100644 index 000000000..ea494d59f --- /dev/null +++ b/packages/father-build/src/fixtures/build/babel-node-files/.fatherrc.js @@ -0,0 +1,8 @@ + +export default { + esm: 'babel', + target: 'browser', + nodeFiles: [ + 'src/node.js', + ], +} diff --git a/packages/father-build/src/fixtures/build/babel-node-files/expected/es/browser.js b/packages/father-build/src/fixtures/build/babel-node-files/expected/es/browser.js new file mode 100644 index 000000000..22dc0e15f --- /dev/null +++ b/packages/father-build/src/fixtures/build/babel-node-files/expected/es/browser.js @@ -0,0 +1 @@ +var b = 'foo'; \ No newline at end of file diff --git a/packages/father-build/src/fixtures/build/babel-node-files/expected/es/node.js b/packages/father-build/src/fixtures/build/babel-node-files/expected/es/node.js new file mode 100644 index 000000000..ed389c023 --- /dev/null +++ b/packages/father-build/src/fixtures/build/babel-node-files/expected/es/node.js @@ -0,0 +1 @@ +const a = 'foo'; \ No newline at end of file diff --git a/packages/father-build/src/fixtures/build/babel-node-files/src/browser.js b/packages/father-build/src/fixtures/build/babel-node-files/src/browser.js new file mode 100644 index 000000000..a3677a7e4 --- /dev/null +++ b/packages/father-build/src/fixtures/build/babel-node-files/src/browser.js @@ -0,0 +1,2 @@ + +const b = 'foo'; diff --git a/packages/father-build/src/fixtures/build/babel-node-files/src/node.js b/packages/father-build/src/fixtures/build/babel-node-files/src/node.js new file mode 100644 index 000000000..528eb6b5a --- /dev/null +++ b/packages/father-build/src/fixtures/build/babel-node-files/src/node.js @@ -0,0 +1,2 @@ + +const a = 'foo'; diff --git a/packages/father-build/src/fixtures/build/babel-node-tsx-jsx/.fatherrc.js b/packages/father-build/src/fixtures/build/babel-node-tsx-jsx/.fatherrc.js new file mode 100644 index 000000000..d59f32117 --- /dev/null +++ b/packages/father-build/src/fixtures/build/babel-node-tsx-jsx/.fatherrc.js @@ -0,0 +1,5 @@ + +export default { + esm: 'babel', + target: 'node', +} diff --git a/packages/father-build/src/fixtures/build/babel-node-tsx-jsx/expected/es/B.d.ts b/packages/father-build/src/fixtures/build/babel-node-tsx-jsx/expected/es/B.d.ts new file mode 100644 index 000000000..2ef04fb23 --- /dev/null +++ b/packages/father-build/src/fixtures/build/babel-node-tsx-jsx/expected/es/B.d.ts @@ -0,0 +1 @@ +declare const b = "foo"; diff --git a/packages/father-build/src/fixtures/build/babel-node-tsx-jsx/expected/es/B.js b/packages/father-build/src/fixtures/build/babel-node-tsx-jsx/expected/es/B.js new file mode 100644 index 000000000..22dc0e15f --- /dev/null +++ b/packages/father-build/src/fixtures/build/babel-node-tsx-jsx/expected/es/B.js @@ -0,0 +1 @@ +var b = 'foo'; \ No newline at end of file diff --git a/packages/father-build/src/fixtures/build/babel-node-tsx-jsx/expected/es/C.js b/packages/father-build/src/fixtures/build/babel-node-tsx-jsx/expected/es/C.js new file mode 100644 index 000000000..22dc0e15f --- /dev/null +++ b/packages/father-build/src/fixtures/build/babel-node-tsx-jsx/expected/es/C.js @@ -0,0 +1 @@ +var b = 'foo'; \ No newline at end of file diff --git a/packages/father-build/src/fixtures/build/babel-node-tsx-jsx/expected/es/node.js b/packages/father-build/src/fixtures/build/babel-node-tsx-jsx/expected/es/node.js new file mode 100644 index 000000000..ed389c023 --- /dev/null +++ b/packages/father-build/src/fixtures/build/babel-node-tsx-jsx/expected/es/node.js @@ -0,0 +1 @@ +const a = 'foo'; \ No newline at end of file diff --git a/packages/father-build/src/fixtures/build/babel-node-tsx-jsx/src/B.tsx b/packages/father-build/src/fixtures/build/babel-node-tsx-jsx/src/B.tsx new file mode 100644 index 000000000..a3677a7e4 --- /dev/null +++ b/packages/father-build/src/fixtures/build/babel-node-tsx-jsx/src/B.tsx @@ -0,0 +1,2 @@ + +const b = 'foo'; diff --git a/packages/father-build/src/fixtures/build/babel-node-tsx-jsx/src/C.jsx b/packages/father-build/src/fixtures/build/babel-node-tsx-jsx/src/C.jsx new file mode 100644 index 000000000..a3677a7e4 --- /dev/null +++ b/packages/father-build/src/fixtures/build/babel-node-tsx-jsx/src/C.jsx @@ -0,0 +1,2 @@ + +const b = 'foo'; diff --git a/packages/father-build/src/fixtures/build/babel-node-tsx-jsx/src/node.js b/packages/father-build/src/fixtures/build/babel-node-tsx-jsx/src/node.js new file mode 100644 index 000000000..528eb6b5a --- /dev/null +++ b/packages/father-build/src/fixtures/build/babel-node-tsx-jsx/src/node.js @@ -0,0 +1,2 @@ + +const a = 'foo'; diff --git a/packages/father-build/src/fixtures/build/babel-node-tsx-jsx/tsconfig.json b/packages/father-build/src/fixtures/build/babel-node-tsx-jsx/tsconfig.json new file mode 100644 index 000000000..6db6275c3 --- /dev/null +++ b/packages/father-build/src/fixtures/build/babel-node-tsx-jsx/tsconfig.json @@ -0,0 +1,9 @@ +{ + "compilerOptions": { + "target": "esnext", + "moduleResolution": "node", + "jsx": "preserve", + "esModuleInterop": true, + "declaration": true + } +} diff --git a/packages/father-build/src/fixtures/build/babel-normal/.fatherrc.js b/packages/father-build/src/fixtures/build/babel-normal/.fatherrc.js new file mode 100644 index 000000000..19390b36b --- /dev/null +++ b/packages/father-build/src/fixtures/build/babel-normal/.fatherrc.js @@ -0,0 +1,5 @@ + +export default { + cjs: { type: 'babel' }, + esm: { type: 'babel' }, +}; diff --git a/packages/father-build/src/fixtures/build/babel-normal/expected/es/foo.js b/packages/father-build/src/fixtures/build/babel-normal/expected/es/foo.js new file mode 100644 index 000000000..29a891a23 --- /dev/null +++ b/packages/father-build/src/fixtures/build/babel-normal/expected/es/foo.js @@ -0,0 +1,3 @@ +export default function () { + return 'foo'; +} \ No newline at end of file diff --git a/packages/father-build/src/fixtures/build/babel-normal/expected/es/index.js b/packages/father-build/src/fixtures/build/babel-normal/expected/es/index.js new file mode 100644 index 000000000..8b2cbd724 --- /dev/null +++ b/packages/father-build/src/fixtures/build/babel-normal/expected/es/index.js @@ -0,0 +1,3 @@ +import 'bar'; +import foo from './foo'; +console.log(foo()); \ No newline at end of file diff --git a/packages/father-build/src/fixtures/build/babel-normal/expected/es/types.d.ts b/packages/father-build/src/fixtures/build/babel-normal/expected/es/types.d.ts new file mode 100644 index 000000000..e69de29bb diff --git a/packages/father-build/src/fixtures/build/babel-normal/src/__test__/__snapshots__/index.test.js.snap b/packages/father-build/src/fixtures/build/babel-normal/src/__test__/__snapshots__/index.test.js.snap new file mode 100644 index 000000000..7c645e42f --- /dev/null +++ b/packages/father-build/src/fixtures/build/babel-normal/src/__test__/__snapshots__/index.test.js.snap @@ -0,0 +1 @@ +export default {}; \ No newline at end of file diff --git a/packages/father-build/src/fixtures/build/babel-normal/src/__test__/index.test.js b/packages/father-build/src/fixtures/build/babel-normal/src/__test__/index.test.js new file mode 100644 index 000000000..ff8b4c563 --- /dev/null +++ b/packages/father-build/src/fixtures/build/babel-normal/src/__test__/index.test.js @@ -0,0 +1 @@ +export default {}; diff --git a/packages/father-build/src/fixtures/build/babel-normal/src/bar.md b/packages/father-build/src/fixtures/build/babel-normal/src/bar.md new file mode 100644 index 000000000..d93e603dc --- /dev/null +++ b/packages/father-build/src/fixtures/build/babel-normal/src/bar.md @@ -0,0 +1 @@ +# bar diff --git a/packages/father-build/src/fixtures/build/babel-normal/src/bar.mdx b/packages/father-build/src/fixtures/build/babel-normal/src/bar.mdx new file mode 100644 index 000000000..d93e603dc --- /dev/null +++ b/packages/father-build/src/fixtures/build/babel-normal/src/bar.mdx @@ -0,0 +1 @@ +# bar diff --git a/packages/father-build/src/fixtures/build/babel-normal/src/demos/foo.js b/packages/father-build/src/fixtures/build/babel-normal/src/demos/foo.js new file mode 100644 index 000000000..eb59b1b29 --- /dev/null +++ b/packages/father-build/src/fixtures/build/babel-normal/src/demos/foo.js @@ -0,0 +1,3 @@ +export default function() { + return "foo"; +} diff --git a/packages/father-build/src/fixtures/build/babel-normal/src/fixtures/example/package.json b/packages/father-build/src/fixtures/build/babel-normal/src/fixtures/example/package.json new file mode 100644 index 000000000..9e26dfeeb --- /dev/null +++ b/packages/father-build/src/fixtures/build/babel-normal/src/fixtures/example/package.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/packages/father-build/src/fixtures/build/babel-normal/src/foo.e2e.ts b/packages/father-build/src/fixtures/build/babel-normal/src/foo.e2e.ts new file mode 100644 index 000000000..e69de29bb diff --git a/packages/father-build/src/fixtures/build/babel-normal/src/foo.js b/packages/father-build/src/fixtures/build/babel-normal/src/foo.js new file mode 100644 index 000000000..8fcf68e1b --- /dev/null +++ b/packages/father-build/src/fixtures/build/babel-normal/src/foo.js @@ -0,0 +1,4 @@ + +export default function () { + return 'foo'; +} diff --git a/packages/father-build/src/fixtures/build/babel-normal/src/foo.spec.ts b/packages/father-build/src/fixtures/build/babel-normal/src/foo.spec.ts new file mode 100644 index 000000000..e69de29bb diff --git a/packages/father-build/src/fixtures/build/babel-normal/src/foo.test.js b/packages/father-build/src/fixtures/build/babel-normal/src/foo.test.js new file mode 100644 index 000000000..e69de29bb diff --git a/packages/father-build/src/fixtures/build/babel-normal/src/foo.test.jsx b/packages/father-build/src/fixtures/build/babel-normal/src/foo.test.jsx new file mode 100644 index 000000000..e69de29bb diff --git a/packages/father-build/src/fixtures/build/babel-normal/src/foo.test.ts b/packages/father-build/src/fixtures/build/babel-normal/src/foo.test.ts new file mode 100644 index 000000000..e69de29bb diff --git a/packages/father-build/src/fixtures/build/babel-normal/src/foo.test.tsx b/packages/father-build/src/fixtures/build/babel-normal/src/foo.test.tsx new file mode 100644 index 000000000..e69de29bb diff --git a/packages/father-build/src/fixtures/build/babel-normal/src/index.js b/packages/father-build/src/fixtures/build/babel-normal/src/index.js new file mode 100644 index 000000000..9d80d06dc --- /dev/null +++ b/packages/father-build/src/fixtures/build/babel-normal/src/index.js @@ -0,0 +1,4 @@ +import 'bar'; +import foo from './foo'; + +console.log(foo()); diff --git a/packages/father-build/src/fixtures/build/babel-normal/src/types.d.ts b/packages/father-build/src/fixtures/build/babel-normal/src/types.d.ts new file mode 100644 index 000000000..e69de29bb diff --git a/packages/father-build/src/fixtures/build/babel-runtimeHelpers/.fatherrc.js b/packages/father-build/src/fixtures/build/babel-runtimeHelpers/.fatherrc.js new file mode 100644 index 000000000..6d1ce8487 --- /dev/null +++ b/packages/father-build/src/fixtures/build/babel-runtimeHelpers/.fatherrc.js @@ -0,0 +1,6 @@ + +export default { + runtimeHelpers: true, + esm: { type: 'babel' }, + cjs: { type: 'babel' }, +} diff --git a/packages/father-build/src/fixtures/build/babel-runtimeHelpers/expected/es/index.js b/packages/father-build/src/fixtures/build/babel-runtimeHelpers/expected/es/index.js new file mode 100644 index 000000000..9a5518f11 --- /dev/null +++ b/packages/father-build/src/fixtures/build/babel-runtimeHelpers/expected/es/index.js @@ -0,0 +1,21 @@ +import _objectSpread from "@babel/runtime/helpers/esm/objectSpread2"; +import _classCallCheck from "@babel/runtime/helpers/esm/classCallCheck"; +import _createClass from "@babel/runtime/helpers/esm/createClass"; + +var A = /*#__PURE__*/function () { + function A() { + _classCallCheck(this, A); + } + + _createClass(A, [{ + key: "foo", + value: function foo() {} + }]); + + return A; +}(); + +new A().foo(); +var a = {}; + +var b = _objectSpread({}, a); \ No newline at end of file diff --git a/packages/father-build/src/fixtures/build/babel-runtimeHelpers/package.json b/packages/father-build/src/fixtures/build/babel-runtimeHelpers/package.json new file mode 100644 index 000000000..9c4a09846 --- /dev/null +++ b/packages/father-build/src/fixtures/build/babel-runtimeHelpers/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "@babel/runtime": "7.10.4" + } +} diff --git a/packages/father-build/src/fixtures/build/babel-runtimeHelpers/src/index.js b/packages/father-build/src/fixtures/build/babel-runtimeHelpers/src/index.js new file mode 100644 index 000000000..5f0d5578c --- /dev/null +++ b/packages/father-build/src/fixtures/build/babel-runtimeHelpers/src/index.js @@ -0,0 +1,9 @@ + +class A { + foo() {} +} + +(new A()).foo(); + +const a = {}; +const b = {...a}; \ No newline at end of file diff --git a/packages/father-build/src/fixtures/build/babel-syntax/.fatherrc.js b/packages/father-build/src/fixtures/build/babel-syntax/.fatherrc.js new file mode 100644 index 000000000..8b9450222 --- /dev/null +++ b/packages/father-build/src/fixtures/build/babel-syntax/.fatherrc.js @@ -0,0 +1,4 @@ + +export default { + esm: { type: 'rollup' }, +}; diff --git a/packages/father-build/src/fixtures/build/babel-syntax/expected/index.esm.js b/packages/father-build/src/fixtures/build/babel-syntax/expected/index.esm.js new file mode 100644 index 000000000..c74948696 --- /dev/null +++ b/packages/father-build/src/fixtures/build/babel-syntax/expected/index.esm.js @@ -0,0 +1,84 @@ +import React from 'react'; + +function _classCallCheck(instance, Constructor) { + if (!(instance instanceof Constructor)) { + throw new TypeError("Cannot call a class as a function"); + } +} + +function _objectWithoutPropertiesLoose(source, excluded) { + if (source == null) return {}; + var target = {}; + var sourceKeys = Object.keys(source); + var key, i; + + for (i = 0; i < sourceKeys.length; i++) { + key = sourceKeys[i]; + if (excluded.indexOf(key) >= 0) continue; + target[key] = source[key]; + } + + return target; +} + +function _objectWithoutProperties(source, excluded) { + if (source == null) return {}; + + var target = _objectWithoutPropertiesLoose(source, excluded); + + var key, i; + + if (Object.getOwnPropertySymbols) { + var sourceSymbolKeys = Object.getOwnPropertySymbols(source); + + for (i = 0; i < sourceSymbolKeys.length; i++) { + key = sourceSymbolKeys[i]; + if (excluded.indexOf(key) >= 0) continue; + if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; + target[key] = source[key]; + } + } + + return target; +} + +function a () { + alert('a'); +} + +function b1() { + alert('b1'); +} +function b2() { + alert('b2'); +} + +var b = /*#__PURE__*/Object.freeze({ + __proto__: null, + b1: b1, + b2: b2 +}); + +var _class; +// babel-plugin-react-require +var Foo = function Foo() { + return /*#__PURE__*/React.createElement("div", null); +}; // Don't support multiple chunks for now +// @babel/plugin-syntax-dynamic-import +// import('./a'); +// object-rest-spread + +var _bar = bar, + foo = _bar.foo, + z = _objectWithoutProperties(_bar, ["foo"]); + +console.log(z); // @babel/plugin-proposal-decorators + class + +var A = foo(_class = function A() { + _classCallCheck(this, A); +}) || _class; // export default from + +var a$1 = x > 10 ? 'big' : 'small'; +console.log(a$1); // export namespace from + +export { A, Foo, a, b }; diff --git a/packages/father-build/src/fixtures/build/babel-syntax/package.json b/packages/father-build/src/fixtures/build/babel-syntax/package.json new file mode 100644 index 000000000..1368588bd --- /dev/null +++ b/packages/father-build/src/fixtures/build/babel-syntax/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "react": "^16.8.4" + } +} diff --git a/packages/father-build/src/fixtures/build/babel-syntax/src/a.js b/packages/father-build/src/fixtures/build/babel-syntax/src/a.js new file mode 100644 index 000000000..4e005dbee --- /dev/null +++ b/packages/father-build/src/fixtures/build/babel-syntax/src/a.js @@ -0,0 +1,4 @@ + +export default function () { + alert('a'); +} diff --git a/packages/father-build/src/fixtures/build/babel-syntax/src/b.js b/packages/father-build/src/fixtures/build/babel-syntax/src/b.js new file mode 100644 index 000000000..c03767c13 --- /dev/null +++ b/packages/father-build/src/fixtures/build/babel-syntax/src/b.js @@ -0,0 +1,8 @@ + +export function b1() { + alert('b1'); +} + +export function b2() { + alert('b2'); +} diff --git a/packages/father-build/src/fixtures/build/babel-syntax/src/index.js b/packages/father-build/src/fixtures/build/babel-syntax/src/index.js new file mode 100644 index 000000000..aec1e847b --- /dev/null +++ b/packages/father-build/src/fixtures/build/babel-syntax/src/index.js @@ -0,0 +1,32 @@ + +// babel-plugin-react-require +export const Foo = () =>
; + +// Don't support multiple chunks for now +// @babel/plugin-syntax-dynamic-import +// import('./a'); + +// object-rest-spread +const { foo, ...z } = bar; +console.log(z); + +// @babel/plugin-proposal-decorators + class +@foo +export class A {} + +// export default from +export a from './a'; + +// do expression +let a = do { + if(x > 10) { + 'big'; + } else { + 'small'; + } +}; +console.log(a); + +// export namespace from +export * as b from './b'; + diff --git a/packages/father-build/src/fixtures/build/babel-ts-3.7-features/.fatherrc.js b/packages/father-build/src/fixtures/build/babel-ts-3.7-features/.fatherrc.js new file mode 100644 index 000000000..0637dcfc5 --- /dev/null +++ b/packages/father-build/src/fixtures/build/babel-ts-3.7-features/.fatherrc.js @@ -0,0 +1,4 @@ +export default { + cjs: { type: 'babel' }, + esm: { type: 'babel' }, +}; diff --git a/packages/father-build/src/fixtures/build/babel-ts-3.7-features/expected/es/index.d.ts b/packages/father-build/src/fixtures/build/babel-ts-3.7-features/expected/es/index.d.ts new file mode 100644 index 000000000..daaf9ca46 --- /dev/null +++ b/packages/father-build/src/fixtures/build/babel-ts-3.7-features/expected/es/index.d.ts @@ -0,0 +1,2 @@ +export declare const optionalChaining: string; +export declare const nullishCoalescing: string | boolean; diff --git a/packages/father-build/src/fixtures/build/babel-ts-3.7-features/expected/es/index.js b/packages/father-build/src/fixtures/build/babel-ts-3.7-features/expected/es/index.js new file mode 100644 index 000000000..640b9525a --- /dev/null +++ b/packages/father-build/src/fixtures/build/babel-ts-3.7-features/expected/es/index.js @@ -0,0 +1,6 @@ +var _foo$test; + +var foo = {}; +export var optionalChaining = foo === null || foo === void 0 ? void 0 : (_foo$test = foo.test) === null || _foo$test === void 0 ? void 0 : _foo$test.abc; +var bar = false; +export var nullishCoalescing = bar !== null && bar !== void 0 ? bar : 'default'; diff --git a/packages/father-build/src/fixtures/build/babel-ts-3.7-features/src/index.ts b/packages/father-build/src/fixtures/build/babel-ts-3.7-features/src/index.ts new file mode 100644 index 000000000..5ed0adeb7 --- /dev/null +++ b/packages/father-build/src/fixtures/build/babel-ts-3.7-features/src/index.ts @@ -0,0 +1,11 @@ +type Foo = { + test?: { + abc?: string; + } +} +const foo: Foo = {}; + +export const optionalChaining = foo?.test?.abc; + +const bar = false; +export const nullishCoalescing = bar ?? 'default'; diff --git a/packages/father-build/src/fixtures/build/babel-typescript-disable-typecheck/.fatherrc.js b/packages/father-build/src/fixtures/build/babel-typescript-disable-typecheck/.fatherrc.js new file mode 100644 index 000000000..166359112 --- /dev/null +++ b/packages/father-build/src/fixtures/build/babel-typescript-disable-typecheck/.fatherrc.js @@ -0,0 +1,6 @@ + +export default { + cjs: { type: 'babel' }, + esm: { type: 'babel' }, + disableTypeCheck: true, +}; diff --git a/packages/father-build/src/fixtures/build/babel-typescript-disable-typecheck/expected/es/index.js b/packages/father-build/src/fixtures/build/babel-typescript-disable-typecheck/expected/es/index.js new file mode 100644 index 000000000..da1c4ebf5 --- /dev/null +++ b/packages/father-build/src/fixtures/build/babel-typescript-disable-typecheck/expected/es/index.js @@ -0,0 +1,3 @@ +export default function foo(opts) { + return opts.foo ? 'foo' : 'bar'; +} \ No newline at end of file diff --git a/packages/father-build/src/fixtures/build/babel-typescript-disable-typecheck/src/index.ts b/packages/father-build/src/fixtures/build/babel-typescript-disable-typecheck/src/index.ts new file mode 100644 index 000000000..3a0363ce2 --- /dev/null +++ b/packages/father-build/src/fixtures/build/babel-typescript-disable-typecheck/src/index.ts @@ -0,0 +1,7 @@ +interface IOpts { + foo: boolean; +} + +export default function foo(opts: IOpts): string { + return opts.foo ? 'foo' : 'bar'; +} diff --git a/packages/father-build/src/fixtures/build/babel-typescript-disable-typecheck/tsconfig.json b/packages/father-build/src/fixtures/build/babel-typescript-disable-typecheck/tsconfig.json new file mode 100644 index 000000000..d322bf935 --- /dev/null +++ b/packages/father-build/src/fixtures/build/babel-typescript-disable-typecheck/tsconfig.json @@ -0,0 +1,8 @@ +{ + "compilerOptions": { + "target": "esnext", + "moduleResolution": "node", + "jsx": "preserve", + "esModuleInterop": true + } +} diff --git a/packages/father-build/src/fixtures/build/babel-typescript-template/.fatherrc.js b/packages/father-build/src/fixtures/build/babel-typescript-template/.fatherrc.js new file mode 100644 index 000000000..19390b36b --- /dev/null +++ b/packages/father-build/src/fixtures/build/babel-typescript-template/.fatherrc.js @@ -0,0 +1,5 @@ + +export default { + cjs: { type: 'babel' }, + esm: { type: 'babel' }, +}; diff --git a/packages/father-build/src/fixtures/build/babel-typescript-template/expected/es/index.d.ts b/packages/father-build/src/fixtures/build/babel-typescript-template/expected/es/index.d.ts new file mode 100644 index 000000000..9f688a7e2 --- /dev/null +++ b/packages/father-build/src/fixtures/build/babel-typescript-template/expected/es/index.d.ts @@ -0,0 +1,5 @@ +interface IOpts { + foo: boolean; +} +export default function foo(opts: IOpts): string; +export {}; diff --git a/packages/father-build/src/fixtures/build/babel-typescript-template/expected/es/index.js b/packages/father-build/src/fixtures/build/babel-typescript-template/expected/es/index.js new file mode 100644 index 000000000..da1c4ebf5 --- /dev/null +++ b/packages/father-build/src/fixtures/build/babel-typescript-template/expected/es/index.js @@ -0,0 +1,3 @@ +export default function foo(opts) { + return opts.foo ? 'foo' : 'bar'; +} \ No newline at end of file diff --git a/packages/father-build/src/fixtures/build/babel-typescript-template/src/index.ts b/packages/father-build/src/fixtures/build/babel-typescript-template/src/index.ts new file mode 100644 index 000000000..3a0363ce2 --- /dev/null +++ b/packages/father-build/src/fixtures/build/babel-typescript-template/src/index.ts @@ -0,0 +1,7 @@ +interface IOpts { + foo: boolean; +} + +export default function foo(opts: IOpts): string { + return opts.foo ? 'foo' : 'bar'; +} diff --git a/packages/father-build/src/fixtures/build/babel-typescript/.fatherrc.js b/packages/father-build/src/fixtures/build/babel-typescript/.fatherrc.js new file mode 100644 index 000000000..19390b36b --- /dev/null +++ b/packages/father-build/src/fixtures/build/babel-typescript/.fatherrc.js @@ -0,0 +1,5 @@ + +export default { + cjs: { type: 'babel' }, + esm: { type: 'babel' }, +}; diff --git a/packages/father-build/src/fixtures/build/babel-typescript/expected/es/index.d.ts b/packages/father-build/src/fixtures/build/babel-typescript/expected/es/index.d.ts new file mode 100644 index 000000000..9f688a7e2 --- /dev/null +++ b/packages/father-build/src/fixtures/build/babel-typescript/expected/es/index.d.ts @@ -0,0 +1,5 @@ +interface IOpts { + foo: boolean; +} +export default function foo(opts: IOpts): string; +export {}; diff --git a/packages/father-build/src/fixtures/build/babel-typescript/expected/es/index.js b/packages/father-build/src/fixtures/build/babel-typescript/expected/es/index.js new file mode 100644 index 000000000..da1c4ebf5 --- /dev/null +++ b/packages/father-build/src/fixtures/build/babel-typescript/expected/es/index.js @@ -0,0 +1,3 @@ +export default function foo(opts) { + return opts.foo ? 'foo' : 'bar'; +} \ No newline at end of file diff --git a/packages/father-build/src/fixtures/build/babel-typescript/src/index.ts b/packages/father-build/src/fixtures/build/babel-typescript/src/index.ts new file mode 100644 index 000000000..3a0363ce2 --- /dev/null +++ b/packages/father-build/src/fixtures/build/babel-typescript/src/index.ts @@ -0,0 +1,7 @@ +interface IOpts { + foo: boolean; +} + +export default function foo(opts: IOpts): string { + return opts.foo ? 'foo' : 'bar'; +} diff --git a/packages/father-build/src/fixtures/build/babel-typescript/tsconfig.json b/packages/father-build/src/fixtures/build/babel-typescript/tsconfig.json new file mode 100644 index 000000000..fd3ce93b7 --- /dev/null +++ b/packages/father-build/src/fixtures/build/babel-typescript/tsconfig.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + // tsconfig is not valid josn file + "target": "esnext", + "moduleResolution": "node", + "jsx": "preserve", + "esModuleInterop": true, + "declaration": true + } +} diff --git a/packages/father-build/src/fixtures/build/config-array/.fatherrc.js b/packages/father-build/src/fixtures/build/config-array/.fatherrc.js new file mode 100644 index 000000000..695d856f1 --- /dev/null +++ b/packages/father-build/src/fixtures/build/config-array/.fatherrc.js @@ -0,0 +1,13 @@ + +export default [ + { + cjs: 'babel', + }, + { + entry: 'ui/index.js', + umd: { + name: 'foo', + minFile: false, + }, + }, +]; diff --git a/packages/father-build/src/fixtures/build/config-array/expected/index.umd.js b/packages/father-build/src/fixtures/build/config-array/expected/index.umd.js new file mode 100644 index 000000000..563d8b286 --- /dev/null +++ b/packages/father-build/src/fixtures/build/config-array/expected/index.umd.js @@ -0,0 +1,13 @@ +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : + typeof define === 'function' && define.amd ? define(factory) : + (global = global || self, global.foo = factory()); +}(this, (function () { 'use strict'; + + var index = (function () { + return 1; + }); + + return index; + +}))); diff --git a/packages/father-build/src/fixtures/build/config-array/src/index.js b/packages/father-build/src/fixtures/build/config-array/src/index.js new file mode 100644 index 000000000..be26f6dda --- /dev/null +++ b/packages/father-build/src/fixtures/build/config-array/src/index.js @@ -0,0 +1,2 @@ + +export default () => 1; diff --git a/packages/father-build/src/fixtures/build/config-array/ui/index.js b/packages/father-build/src/fixtures/build/config-array/ui/index.js new file mode 100644 index 000000000..807a7975a --- /dev/null +++ b/packages/father-build/src/fixtures/build/config-array/ui/index.js @@ -0,0 +1 @@ +export default () => 1; diff --git a/packages/father-build/src/fixtures/build/lerna-root-config-override/.fatherrc.js b/packages/father-build/src/fixtures/build/lerna-root-config-override/.fatherrc.js new file mode 100644 index 000000000..2f10183a3 --- /dev/null +++ b/packages/father-build/src/fixtures/build/lerna-root-config-override/.fatherrc.js @@ -0,0 +1,4 @@ + +export default { + esm: { type: 'rollup' }, +} diff --git a/packages/father-build/src/fixtures/build/lerna-root-config-override/expected/bar/index.js b/packages/father-build/src/fixtures/build/lerna-root-config-override/expected/bar/index.js new file mode 100644 index 000000000..2b1938520 --- /dev/null +++ b/packages/father-build/src/fixtures/build/lerna-root-config-override/expected/bar/index.js @@ -0,0 +1,3 @@ +'use strict'; + +console.log('bar'); diff --git a/packages/father-build/src/fixtures/build/lerna-root-config-override/expected/foo/index.js b/packages/father-build/src/fixtures/build/lerna-root-config-override/expected/foo/index.js new file mode 100644 index 000000000..aece69e8b --- /dev/null +++ b/packages/father-build/src/fixtures/build/lerna-root-config-override/expected/foo/index.js @@ -0,0 +1,3 @@ +'use strict'; + +console.log('foo'); diff --git a/packages/father-build/src/fixtures/build/lerna-root-config-override/lerna.json b/packages/father-build/src/fixtures/build/lerna-root-config-override/lerna.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/packages/father-build/src/fixtures/build/lerna-root-config-override/lerna.json @@ -0,0 +1 @@ +{} diff --git a/packages/father-build/src/fixtures/build/lerna-root-config-override/packages/bar/package.json b/packages/father-build/src/fixtures/build/lerna-root-config-override/packages/bar/package.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/packages/father-build/src/fixtures/build/lerna-root-config-override/packages/bar/package.json @@ -0,0 +1 @@ +{} diff --git a/packages/father-build/src/fixtures/build/lerna-root-config-override/packages/bar/src/index.js b/packages/father-build/src/fixtures/build/lerna-root-config-override/packages/bar/src/index.js new file mode 100644 index 000000000..9638d025d --- /dev/null +++ b/packages/father-build/src/fixtures/build/lerna-root-config-override/packages/bar/src/index.js @@ -0,0 +1,2 @@ + +console.log('bar'); diff --git a/packages/father-build/src/fixtures/build/lerna-root-config-override/packages/foo/package.json b/packages/father-build/src/fixtures/build/lerna-root-config-override/packages/foo/package.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/packages/father-build/src/fixtures/build/lerna-root-config-override/packages/foo/package.json @@ -0,0 +1 @@ +{} diff --git a/packages/father-build/src/fixtures/build/lerna-root-config-override/packages/foo/src/index.js b/packages/father-build/src/fixtures/build/lerna-root-config-override/packages/foo/src/index.js new file mode 100644 index 000000000..695557eab --- /dev/null +++ b/packages/father-build/src/fixtures/build/lerna-root-config-override/packages/foo/src/index.js @@ -0,0 +1,2 @@ + +console.log('foo'); diff --git a/packages/father-build/src/fixtures/build/lerna-root-config/.fatherrc.js b/packages/father-build/src/fixtures/build/lerna-root-config/.fatherrc.js new file mode 100644 index 000000000..2f10183a3 --- /dev/null +++ b/packages/father-build/src/fixtures/build/lerna-root-config/.fatherrc.js @@ -0,0 +1,4 @@ + +export default { + esm: { type: 'rollup' }, +} diff --git a/packages/father-build/src/fixtures/build/lerna-root-config/expected/bar/index.esm.js b/packages/father-build/src/fixtures/build/lerna-root-config/expected/bar/index.esm.js new file mode 100644 index 000000000..8cc7aa3e9 --- /dev/null +++ b/packages/father-build/src/fixtures/build/lerna-root-config/expected/bar/index.esm.js @@ -0,0 +1 @@ +console.log('bar'); diff --git a/packages/father-build/src/fixtures/build/lerna-root-config/expected/foo/index.esm.js b/packages/father-build/src/fixtures/build/lerna-root-config/expected/foo/index.esm.js new file mode 100644 index 000000000..81afa3157 --- /dev/null +++ b/packages/father-build/src/fixtures/build/lerna-root-config/expected/foo/index.esm.js @@ -0,0 +1 @@ +console.log('foo'); diff --git a/packages/father-build/src/fixtures/build/lerna-root-config/lerna.json b/packages/father-build/src/fixtures/build/lerna-root-config/lerna.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/packages/father-build/src/fixtures/build/lerna-root-config/lerna.json @@ -0,0 +1 @@ +{} diff --git a/packages/father-build/src/fixtures/build/lerna-root-config/packages/bar/package.json b/packages/father-build/src/fixtures/build/lerna-root-config/packages/bar/package.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/packages/father-build/src/fixtures/build/lerna-root-config/packages/bar/package.json @@ -0,0 +1 @@ +{} diff --git a/packages/father-build/src/fixtures/build/lerna-root-config/packages/bar/src/index.js b/packages/father-build/src/fixtures/build/lerna-root-config/packages/bar/src/index.js new file mode 100644 index 000000000..9638d025d --- /dev/null +++ b/packages/father-build/src/fixtures/build/lerna-root-config/packages/bar/src/index.js @@ -0,0 +1,2 @@ + +console.log('bar'); diff --git a/packages/father-build/src/fixtures/build/lerna-root-config/packages/foo/package.json b/packages/father-build/src/fixtures/build/lerna-root-config/packages/foo/package.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/packages/father-build/src/fixtures/build/lerna-root-config/packages/foo/package.json @@ -0,0 +1 @@ +{} diff --git a/packages/father-build/src/fixtures/build/lerna-root-config/packages/foo/src/index.js b/packages/father-build/src/fixtures/build/lerna-root-config/packages/foo/src/index.js new file mode 100644 index 000000000..695557eab --- /dev/null +++ b/packages/father-build/src/fixtures/build/lerna-root-config/packages/foo/src/index.js @@ -0,0 +1,2 @@ + +console.log('foo'); diff --git a/packages/father-build/src/fixtures/build/lerna-root-scope/.fatherrc.js b/packages/father-build/src/fixtures/build/lerna-root-scope/.fatherrc.js new file mode 100644 index 000000000..5c4c510bf --- /dev/null +++ b/packages/father-build/src/fixtures/build/lerna-root-scope/.fatherrc.js @@ -0,0 +1,3 @@ +export default { + esm: { type: 'babel' }, +} diff --git a/packages/father-build/src/fixtures/build/lerna-root-scope/expected/bar/index.esm.js b/packages/father-build/src/fixtures/build/lerna-root-scope/expected/bar/index.esm.js new file mode 100644 index 000000000..8cc7aa3e9 --- /dev/null +++ b/packages/father-build/src/fixtures/build/lerna-root-scope/expected/bar/index.esm.js @@ -0,0 +1 @@ +console.log('bar'); diff --git a/packages/father-build/src/fixtures/build/lerna-root-scope/expected/foo/index.esm.js b/packages/father-build/src/fixtures/build/lerna-root-scope/expected/foo/index.esm.js new file mode 100644 index 000000000..81afa3157 --- /dev/null +++ b/packages/father-build/src/fixtures/build/lerna-root-scope/expected/foo/index.esm.js @@ -0,0 +1 @@ +console.log('foo'); diff --git a/packages/father-build/src/fixtures/build/lerna-root-scope/lerna.json b/packages/father-build/src/fixtures/build/lerna-root-scope/lerna.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/packages/father-build/src/fixtures/build/lerna-root-scope/lerna.json @@ -0,0 +1 @@ +{} diff --git a/packages/father-build/src/fixtures/build/lerna-root-scope/packages/bar/.fatherrc.js b/packages/father-build/src/fixtures/build/lerna-root-scope/packages/bar/.fatherrc.js new file mode 100644 index 000000000..030fee174 --- /dev/null +++ b/packages/father-build/src/fixtures/build/lerna-root-scope/packages/bar/.fatherrc.js @@ -0,0 +1,3 @@ +export default { + esm: { type: 'rollup' }, +} diff --git a/packages/father-build/src/fixtures/build/lerna-root-scope/packages/bar/package.json b/packages/father-build/src/fixtures/build/lerna-root-scope/packages/bar/package.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/packages/father-build/src/fixtures/build/lerna-root-scope/packages/bar/package.json @@ -0,0 +1 @@ +{} diff --git a/packages/father-build/src/fixtures/build/lerna-root-scope/packages/bar/src/index.js b/packages/father-build/src/fixtures/build/lerna-root-scope/packages/bar/src/index.js new file mode 100644 index 000000000..9638d025d --- /dev/null +++ b/packages/father-build/src/fixtures/build/lerna-root-scope/packages/bar/src/index.js @@ -0,0 +1,2 @@ + +console.log('bar'); diff --git a/packages/father-build/src/fixtures/build/lerna-root-scope/packages/foo/.fatherrc.js b/packages/father-build/src/fixtures/build/lerna-root-scope/packages/foo/.fatherrc.js new file mode 100644 index 000000000..030fee174 --- /dev/null +++ b/packages/father-build/src/fixtures/build/lerna-root-scope/packages/foo/.fatherrc.js @@ -0,0 +1,3 @@ +export default { + esm: { type: 'rollup' }, +} diff --git a/packages/father-build/src/fixtures/build/lerna-root-scope/packages/foo/package.json b/packages/father-build/src/fixtures/build/lerna-root-scope/packages/foo/package.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/packages/father-build/src/fixtures/build/lerna-root-scope/packages/foo/package.json @@ -0,0 +1 @@ +{} diff --git a/packages/father-build/src/fixtures/build/lerna-root-scope/packages/foo/src/index.js b/packages/father-build/src/fixtures/build/lerna-root-scope/packages/foo/src/index.js new file mode 100644 index 000000000..695557eab --- /dev/null +++ b/packages/father-build/src/fixtures/build/lerna-root-scope/packages/foo/src/index.js @@ -0,0 +1,2 @@ + +console.log('foo'); diff --git a/packages/father-build/src/fixtures/build/lerna-root-tsconfig/.fatherrc.js b/packages/father-build/src/fixtures/build/lerna-root-tsconfig/.fatherrc.js new file mode 100644 index 000000000..2875def12 --- /dev/null +++ b/packages/father-build/src/fixtures/build/lerna-root-tsconfig/.fatherrc.js @@ -0,0 +1,4 @@ + +export default { + esm: { type: 'babel' }, +} diff --git a/packages/father-build/src/fixtures/build/lerna-root-tsconfig/expected/bar/es/index.js b/packages/father-build/src/fixtures/build/lerna-root-tsconfig/expected/bar/es/index.js new file mode 100644 index 000000000..11f6ee783 --- /dev/null +++ b/packages/father-build/src/fixtures/build/lerna-root-tsconfig/expected/bar/es/index.js @@ -0,0 +1,3 @@ +export default (function () { + console.log('bar'); +}); \ No newline at end of file diff --git a/packages/father-build/src/fixtures/build/lerna-root-tsconfig/expected/foo/es/index.js b/packages/father-build/src/fixtures/build/lerna-root-tsconfig/expected/foo/es/index.js new file mode 100644 index 000000000..1f28f54e8 --- /dev/null +++ b/packages/father-build/src/fixtures/build/lerna-root-tsconfig/expected/foo/es/index.js @@ -0,0 +1,3 @@ +export default (function () { + console.log('foo'); +}); \ No newline at end of file diff --git a/packages/father-build/src/fixtures/build/lerna-root-tsconfig/lerna.json b/packages/father-build/src/fixtures/build/lerna-root-tsconfig/lerna.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/packages/father-build/src/fixtures/build/lerna-root-tsconfig/lerna.json @@ -0,0 +1 @@ +{} diff --git a/packages/father-build/src/fixtures/build/lerna-root-tsconfig/packages/bar/package.json b/packages/father-build/src/fixtures/build/lerna-root-tsconfig/packages/bar/package.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/packages/father-build/src/fixtures/build/lerna-root-tsconfig/packages/bar/package.json @@ -0,0 +1 @@ +{} diff --git a/packages/father-build/src/fixtures/build/lerna-root-tsconfig/packages/bar/src/index.ts b/packages/father-build/src/fixtures/build/lerna-root-tsconfig/packages/bar/src/index.ts new file mode 100644 index 000000000..f053c5b21 --- /dev/null +++ b/packages/father-build/src/fixtures/build/lerna-root-tsconfig/packages/bar/src/index.ts @@ -0,0 +1,4 @@ + +export default () => { + console.log('bar'); +}; diff --git a/packages/father-build/src/fixtures/build/lerna-root-tsconfig/packages/foo/package.json b/packages/father-build/src/fixtures/build/lerna-root-tsconfig/packages/foo/package.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/packages/father-build/src/fixtures/build/lerna-root-tsconfig/packages/foo/package.json @@ -0,0 +1 @@ +{} diff --git a/packages/father-build/src/fixtures/build/lerna-root-tsconfig/packages/foo/src/index.ts b/packages/father-build/src/fixtures/build/lerna-root-tsconfig/packages/foo/src/index.ts new file mode 100644 index 000000000..32ecfb320 --- /dev/null +++ b/packages/father-build/src/fixtures/build/lerna-root-tsconfig/packages/foo/src/index.ts @@ -0,0 +1,4 @@ + +export default () => { + console.log('foo'); +}; diff --git a/packages/father-build/src/fixtures/build/lerna-root-tsconfig/tsconfig.json b/packages/father-build/src/fixtures/build/lerna-root-tsconfig/tsconfig.json new file mode 100644 index 000000000..38fe9e814 --- /dev/null +++ b/packages/father-build/src/fixtures/build/lerna-root-tsconfig/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "declaration": false, + "target": "esnext", + "module": "esnext", + "moduleResolution": "node", + "jsx": "react", + "esModuleInterop": true, + "allowSyntheticDefaultImports": true + }, + "exclude": ["node_modules", "es", "lib", "dist"] +} diff --git a/packages/father-build/src/fixtures/build/lerna-scope-packages/expected/bar/index.esm.js b/packages/father-build/src/fixtures/build/lerna-scope-packages/expected/bar/index.esm.js new file mode 100644 index 000000000..8cc7aa3e9 --- /dev/null +++ b/packages/father-build/src/fixtures/build/lerna-scope-packages/expected/bar/index.esm.js @@ -0,0 +1 @@ +console.log('bar'); diff --git a/packages/father-build/src/fixtures/build/lerna-scope-packages/expected/foo/index.esm.js b/packages/father-build/src/fixtures/build/lerna-scope-packages/expected/foo/index.esm.js new file mode 100644 index 000000000..81afa3157 --- /dev/null +++ b/packages/father-build/src/fixtures/build/lerna-scope-packages/expected/foo/index.esm.js @@ -0,0 +1 @@ +console.log('foo'); diff --git a/packages/father-build/src/fixtures/build/lerna-scope-packages/lerna.json b/packages/father-build/src/fixtures/build/lerna-scope-packages/lerna.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/packages/father-build/src/fixtures/build/lerna-scope-packages/lerna.json @@ -0,0 +1 @@ +{} diff --git a/packages/father-build/src/fixtures/build/lerna-scope-packages/packages/@hoo/bar/.fatherrc.js b/packages/father-build/src/fixtures/build/lerna-scope-packages/packages/@hoo/bar/.fatherrc.js new file mode 100644 index 000000000..2f10183a3 --- /dev/null +++ b/packages/father-build/src/fixtures/build/lerna-scope-packages/packages/@hoo/bar/.fatherrc.js @@ -0,0 +1,4 @@ + +export default { + esm: { type: 'rollup' }, +} diff --git a/packages/father-build/src/fixtures/build/lerna-scope-packages/packages/@hoo/bar/package.json b/packages/father-build/src/fixtures/build/lerna-scope-packages/packages/@hoo/bar/package.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/packages/father-build/src/fixtures/build/lerna-scope-packages/packages/@hoo/bar/package.json @@ -0,0 +1 @@ +{} diff --git a/packages/father-build/src/fixtures/build/lerna-scope-packages/packages/@hoo/bar/src/index.js b/packages/father-build/src/fixtures/build/lerna-scope-packages/packages/@hoo/bar/src/index.js new file mode 100644 index 000000000..9638d025d --- /dev/null +++ b/packages/father-build/src/fixtures/build/lerna-scope-packages/packages/@hoo/bar/src/index.js @@ -0,0 +1,2 @@ + +console.log('bar'); diff --git a/packages/father-build/src/fixtures/build/lerna-scope-packages/packages/foo/.fatherrc.js b/packages/father-build/src/fixtures/build/lerna-scope-packages/packages/foo/.fatherrc.js new file mode 100644 index 000000000..2f10183a3 --- /dev/null +++ b/packages/father-build/src/fixtures/build/lerna-scope-packages/packages/foo/.fatherrc.js @@ -0,0 +1,4 @@ + +export default { + esm: { type: 'rollup' }, +} diff --git a/packages/father-build/src/fixtures/build/lerna-scope-packages/packages/foo/package.json b/packages/father-build/src/fixtures/build/lerna-scope-packages/packages/foo/package.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/packages/father-build/src/fixtures/build/lerna-scope-packages/packages/foo/package.json @@ -0,0 +1 @@ +{} diff --git a/packages/father-build/src/fixtures/build/lerna-scope-packages/packages/foo/src/index.js b/packages/father-build/src/fixtures/build/lerna-scope-packages/packages/foo/src/index.js new file mode 100644 index 000000000..695557eab --- /dev/null +++ b/packages/father-build/src/fixtures/build/lerna-scope-packages/packages/foo/src/index.js @@ -0,0 +1,2 @@ + +console.log('foo'); diff --git a/packages/father-build/src/fixtures/build/lerna/expected/bar/index.esm.js b/packages/father-build/src/fixtures/build/lerna/expected/bar/index.esm.js new file mode 100644 index 000000000..8cc7aa3e9 --- /dev/null +++ b/packages/father-build/src/fixtures/build/lerna/expected/bar/index.esm.js @@ -0,0 +1 @@ +console.log('bar'); diff --git a/packages/father-build/src/fixtures/build/lerna/expected/foo/index.esm.js b/packages/father-build/src/fixtures/build/lerna/expected/foo/index.esm.js new file mode 100644 index 000000000..81afa3157 --- /dev/null +++ b/packages/father-build/src/fixtures/build/lerna/expected/foo/index.esm.js @@ -0,0 +1 @@ +console.log('foo'); diff --git a/packages/father-build/src/fixtures/build/lerna/lerna.json b/packages/father-build/src/fixtures/build/lerna/lerna.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/packages/father-build/src/fixtures/build/lerna/lerna.json @@ -0,0 +1 @@ +{} diff --git a/packages/father-build/src/fixtures/build/lerna/packages/bar/.fatherrc.js b/packages/father-build/src/fixtures/build/lerna/packages/bar/.fatherrc.js new file mode 100644 index 000000000..2f10183a3 --- /dev/null +++ b/packages/father-build/src/fixtures/build/lerna/packages/bar/.fatherrc.js @@ -0,0 +1,4 @@ + +export default { + esm: { type: 'rollup' }, +} diff --git a/packages/father-build/src/fixtures/build/lerna/packages/bar/package.json b/packages/father-build/src/fixtures/build/lerna/packages/bar/package.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/packages/father-build/src/fixtures/build/lerna/packages/bar/package.json @@ -0,0 +1 @@ +{} diff --git a/packages/father-build/src/fixtures/build/lerna/packages/bar/src/index.js b/packages/father-build/src/fixtures/build/lerna/packages/bar/src/index.js new file mode 100644 index 000000000..9638d025d --- /dev/null +++ b/packages/father-build/src/fixtures/build/lerna/packages/bar/src/index.js @@ -0,0 +1,2 @@ + +console.log('bar'); diff --git a/packages/father-build/src/fixtures/build/lerna/packages/foo/.fatherrc.js b/packages/father-build/src/fixtures/build/lerna/packages/foo/.fatherrc.js new file mode 100644 index 000000000..2f10183a3 --- /dev/null +++ b/packages/father-build/src/fixtures/build/lerna/packages/foo/.fatherrc.js @@ -0,0 +1,4 @@ + +export default { + esm: { type: 'rollup' }, +} diff --git a/packages/father-build/src/fixtures/build/lerna/packages/foo/package.json b/packages/father-build/src/fixtures/build/lerna/packages/foo/package.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/packages/father-build/src/fixtures/build/lerna/packages/foo/package.json @@ -0,0 +1 @@ +{} diff --git a/packages/father-build/src/fixtures/build/lerna/packages/foo/src/index.js b/packages/father-build/src/fixtures/build/lerna/packages/foo/src/index.js new file mode 100644 index 000000000..695557eab --- /dev/null +++ b/packages/father-build/src/fixtures/build/lerna/packages/foo/src/index.js @@ -0,0 +1,2 @@ + +console.log('foo'); diff --git a/packages/father-build/src/fixtures/build/require-node_modules-in-sub-directory/.fatherrc.js b/packages/father-build/src/fixtures/build/require-node_modules-in-sub-directory/.fatherrc.js new file mode 100644 index 000000000..6e6bd69f5 --- /dev/null +++ b/packages/father-build/src/fixtures/build/require-node_modules-in-sub-directory/.fatherrc.js @@ -0,0 +1,6 @@ + +export default { + umd: { + minFile: false, + }, +}; diff --git a/packages/father-build/src/fixtures/build/require-node_modules-in-sub-directory/expected/index.umd.js b/packages/father-build/src/fixtures/build/require-node_modules-in-sub-directory/expected/index.umd.js new file mode 100644 index 000000000..138b8ca0b --- /dev/null +++ b/packages/father-build/src/fixtures/build/require-node_modules-in-sub-directory/expected/index.umd.js @@ -0,0 +1,33 @@ +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : + typeof define === 'function' && define.amd ? define(factory) : + (global = global || self, global.foo = factory()); +}(this, (function () { 'use strict'; + + var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {}; + + function createCommonjsModule(fn, module) { + return module = { exports: {} }, fn(module, module.exports), module.exports; + } + + var lib = createCommonjsModule(function (module) { + (function (root, factory) { + if ( module.exports) { + module.exports = factory(); + } else { + root.timing2 = factory(); + } + }(typeof self !== 'undefined' ? self : commonjsGlobal, function () { + return { + timing2: 'timing2', + }; + })); + }); + + function index () { + console.log(lib); + } + + return index; + +}))); diff --git a/packages/father-build/src/fixtures/build/require-node_modules-in-sub-directory/package.json b/packages/father-build/src/fixtures/build/require-node_modules-in-sub-directory/package.json new file mode 100644 index 000000000..bde99de92 --- /dev/null +++ b/packages/father-build/src/fixtures/build/require-node_modules-in-sub-directory/package.json @@ -0,0 +1,3 @@ +{ + "name": "foo" +} diff --git a/packages/father-build/src/fixtures/build/require-node_modules-in-sub-directory/packages/fooo/package.json b/packages/father-build/src/fixtures/build/require-node_modules-in-sub-directory/packages/fooo/package.json new file mode 100644 index 000000000..de662967e --- /dev/null +++ b/packages/father-build/src/fixtures/build/require-node_modules-in-sub-directory/packages/fooo/package.json @@ -0,0 +1,9 @@ +{ + "name": "fooo", + "dependencies": { + "timing2": "^0.3.3" + }, + "scripts": { + "build": "father build --umd" + } +} diff --git a/packages/father-build/src/fixtures/build/require-node_modules-in-sub-directory/packages/fooo/src/index.js b/packages/father-build/src/fixtures/build/require-node_modules-in-sub-directory/packages/fooo/src/index.js new file mode 100644 index 000000000..ce3b69ab5 --- /dev/null +++ b/packages/father-build/src/fixtures/build/require-node_modules-in-sub-directory/packages/fooo/src/index.js @@ -0,0 +1,5 @@ +import timing2 from 'timing2'; + +export default function() { + console.log(timing2); +}; diff --git a/packages/father-build/src/fixtures/build/require-node_modules-in-sub-directory/src/index.js b/packages/father-build/src/fixtures/build/require-node_modules-in-sub-directory/src/index.js new file mode 100644 index 000000000..4785cef57 --- /dev/null +++ b/packages/father-build/src/fixtures/build/require-node_modules-in-sub-directory/src/index.js @@ -0,0 +1 @@ +export default from '../packages/fooo/src/index'; diff --git a/packages/father-build/src/fixtures/build/rollup-commonjs-deps-named-exports/.fatherrc.js b/packages/father-build/src/fixtures/build/rollup-commonjs-deps-named-exports/.fatherrc.js new file mode 100644 index 000000000..63ddc5bdc --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-commonjs-deps-named-exports/.fatherrc.js @@ -0,0 +1,12 @@ + +export default { + umd: { + minFile: false, + }, + namedExports: { + // 用 require.resolve 是为了跑测试时能找到文件 + // 项目里用 `foo` 或 `node_modules/foo/index.js` 就好 + // ref: https://github.com/rollup/rollup-plugin-commonjs#custom-named-exports + [`${require.resolve('foo')}`]: ['a', 'b'], + }, +}; diff --git a/packages/father-build/src/fixtures/build/rollup-commonjs-deps-named-exports/expected/index.umd.js b/packages/father-build/src/fixtures/build/rollup-commonjs-deps-named-exports/expected/index.umd.js new file mode 100644 index 000000000..9009d0263 --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-commonjs-deps-named-exports/expected/index.umd.js @@ -0,0 +1,26 @@ +(function (factory) { + typeof define === 'function' && define.amd ? define(factory) : + factory(); +}((function () { 'use strict'; + + function createCommonjsModule(fn, module) { + return module = { exports: {} }, fn(module, module.exports), module.exports; + } + + var foo_1 = createCommonjsModule(function (module, exports) { + var foo = exports; + + foo.a = function () { + return 'a'; + }; + foo.b = function () { + return 'b'; + }; + }); + var foo_2 = foo_1.a; + var foo_3 = foo_1.b; + + console.log(foo_2()); + console.log(foo_3()); + +}))); diff --git a/packages/father-build/src/fixtures/build/rollup-commonjs-deps-named-exports/package.json b/packages/father-build/src/fixtures/build/rollup-commonjs-deps-named-exports/package.json new file mode 100644 index 000000000..040bc3a1e --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-commonjs-deps-named-exports/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "foo": "0.1.0" + } +} diff --git a/packages/father-build/src/fixtures/build/rollup-commonjs-deps-named-exports/src/index.js b/packages/father-build/src/fixtures/build/rollup-commonjs-deps-named-exports/src/index.js new file mode 100644 index 000000000..8fa897e84 --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-commonjs-deps-named-exports/src/index.js @@ -0,0 +1,4 @@ +import { a, b } from 'foo'; + +console.log(a()); +console.log(b()); diff --git a/packages/father-build/src/fixtures/build/rollup-commonjs-deps/.fatherrc.js b/packages/father-build/src/fixtures/build/rollup-commonjs-deps/.fatherrc.js new file mode 100644 index 000000000..6e6bd69f5 --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-commonjs-deps/.fatherrc.js @@ -0,0 +1,6 @@ + +export default { + umd: { + minFile: false, + }, +}; diff --git a/packages/father-build/src/fixtures/build/rollup-commonjs-deps/expected/index.umd.js b/packages/father-build/src/fixtures/build/rollup-commonjs-deps/expected/index.umd.js new file mode 100644 index 000000000..1c422d885 --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-commonjs-deps/expected/index.umd.js @@ -0,0 +1,20 @@ +(function (factory) { + typeof define === 'function' && define.amd ? define(factory) : + factory(); +}((function () { 'use strict'; + + var foo = { + a: function () { + return 'a'; + }, + b: function () { + return 'b'; + }, + }; + var foo_1 = foo.a; + var foo_2 = foo.b; + + console.log(foo_1()); + console.log(foo_2()); + +}))); diff --git a/packages/father-build/src/fixtures/build/rollup-commonjs-deps/package.json b/packages/father-build/src/fixtures/build/rollup-commonjs-deps/package.json new file mode 100644 index 000000000..040bc3a1e --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-commonjs-deps/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "foo": "0.1.0" + } +} diff --git a/packages/father-build/src/fixtures/build/rollup-commonjs-deps/src/index.js b/packages/father-build/src/fixtures/build/rollup-commonjs-deps/src/index.js new file mode 100644 index 000000000..8fa897e84 --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-commonjs-deps/src/index.js @@ -0,0 +1,4 @@ +import { a, b } from 'foo'; + +console.log(a()); +console.log(b()); diff --git a/packages/father-build/src/fixtures/build/rollup-config-alias/.fatherrc.js b/packages/father-build/src/fixtures/build/rollup-config-alias/.fatherrc.js new file mode 100644 index 000000000..85815c788 --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-config-alias/.fatherrc.js @@ -0,0 +1,4 @@ + +export default { + esm: 'rollup', +} diff --git a/packages/father-build/src/fixtures/build/rollup-config-alias/expected/index.esm.js b/packages/father-build/src/fixtures/build/rollup-config-alias/expected/index.esm.js new file mode 100644 index 000000000..586fcf9a5 --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-config-alias/expected/index.esm.js @@ -0,0 +1,6 @@ +var foo = 'a'; +function index () { + console.log(foo); +} + +export default index; diff --git a/packages/father-build/src/fixtures/build/rollup-config-alias/src/index.js b/packages/father-build/src/fixtures/build/rollup-config-alias/src/index.js new file mode 100644 index 000000000..80c65e1f0 --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-config-alias/src/index.js @@ -0,0 +1,6 @@ + +const foo = 'a'; + +export default function () { + console.log(foo); +} diff --git a/packages/father-build/src/fixtures/build/rollup-css-autoprefixer/.fatherrc.js b/packages/father-build/src/fixtures/build/rollup-css-autoprefixer/.fatherrc.js new file mode 100644 index 000000000..5f43af600 --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-css-autoprefixer/.fatherrc.js @@ -0,0 +1,10 @@ + +export default { + esm: { type: 'rollup' }, + autoprefixer: { + overrideBrowserslist: [ + 'ie>8', + 'Safari >= 6', + ], + }, +} diff --git a/packages/father-build/src/fixtures/build/rollup-css-autoprefixer/expected/index.esm.js b/packages/father-build/src/fixtures/build/rollup-css-autoprefixer/expected/index.esm.js new file mode 100644 index 000000000..769b44241 --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-css-autoprefixer/expected/index.esm.js @@ -0,0 +1,31 @@ +function styleInject(css, ref) { + if ( ref === void 0 ) ref = {}; + var insertAt = ref.insertAt; + + if (!css || typeof document === 'undefined') { return; } + + var head = document.head || document.getElementsByTagName('head')[0]; + var style = document.createElement('style'); + style.type = 'text/css'; + + if (insertAt === 'top') { + if (head.firstChild) { + head.insertBefore(style, head.firstChild); + } else { + head.appendChild(style); + } + } else { + head.appendChild(style); + } + + if (style.styleSheet) { + style.styleSheet.cssText = css; + } else { + style.appendChild(document.createTextNode(css)); + } +} + +var css = "\n.a {\n display: -webkit-box;\n display: -webkit-flex;\n display: -ms-flexbox;\n display: flex;\n}\n"; +styleInject(css); + +console.log('foo'); diff --git a/packages/father-build/src/fixtures/build/rollup-css-autoprefixer/src/foo.css b/packages/father-build/src/fixtures/build/rollup-css-autoprefixer/src/foo.css new file mode 100644 index 000000000..b4d332f68 --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-css-autoprefixer/src/foo.css @@ -0,0 +1,4 @@ + +.a { + display: flex; +} diff --git a/packages/father-build/src/fixtures/build/rollup-css-autoprefixer/src/index.js b/packages/father-build/src/fixtures/build/rollup-css-autoprefixer/src/index.js new file mode 100644 index 000000000..9a761d97a --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-css-autoprefixer/src/index.js @@ -0,0 +1,3 @@ +import './foo.css'; + +console.log('foo'); diff --git a/packages/father-build/src/fixtures/build/rollup-css-extract/.fatherrc.js b/packages/father-build/src/fixtures/build/rollup-css-extract/.fatherrc.js new file mode 100644 index 000000000..c01e2a255 --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-css-extract/.fatherrc.js @@ -0,0 +1,5 @@ + +export default { + extractCSS: true, + esm: { type: 'rollup' }, +} diff --git a/packages/father-build/src/fixtures/build/rollup-css-extract/expected/index.esm.css b/packages/father-build/src/fixtures/build/rollup-css-extract/expected/index.esm.css new file mode 100644 index 000000000..ec0af869a --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-css-extract/expected/index.esm.css @@ -0,0 +1,2 @@ + +.a { color: green; } diff --git a/packages/father-build/src/fixtures/build/rollup-css-extract/expected/index.esm.js b/packages/father-build/src/fixtures/build/rollup-css-extract/expected/index.esm.js new file mode 100644 index 000000000..81afa3157 --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-css-extract/expected/index.esm.js @@ -0,0 +1 @@ +console.log('foo'); diff --git a/packages/father-build/src/fixtures/build/rollup-css-extract/src/foo.css b/packages/father-build/src/fixtures/build/rollup-css-extract/src/foo.css new file mode 100644 index 000000000..ec0af869a --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-css-extract/src/foo.css @@ -0,0 +1,2 @@ + +.a { color: green; } diff --git a/packages/father-build/src/fixtures/build/rollup-css-extract/src/index.js b/packages/father-build/src/fixtures/build/rollup-css-extract/src/index.js new file mode 100644 index 000000000..9a761d97a --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-css-extract/src/index.js @@ -0,0 +1,3 @@ +import './foo.css'; + +console.log('foo'); diff --git a/packages/father-build/src/fixtures/build/rollup-css-inject/.fatherrc.js b/packages/father-build/src/fixtures/build/rollup-css-inject/.fatherrc.js new file mode 100644 index 000000000..c39568546 --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-css-inject/.fatherrc.js @@ -0,0 +1,5 @@ + +export default { + injectCSS: false, + esm: { type: 'rollup' }, +} diff --git a/packages/father-build/src/fixtures/build/rollup-css-inject/expected/index.esm.js b/packages/father-build/src/fixtures/build/rollup-css-inject/expected/index.esm.js new file mode 100644 index 000000000..8bbb84e44 --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-css-inject/expected/index.esm.js @@ -0,0 +1,3 @@ +var css = "\n.a { color: green; }\n"; + +console.log(css); diff --git a/packages/father-build/src/fixtures/build/rollup-css-inject/src/foo.css b/packages/father-build/src/fixtures/build/rollup-css-inject/src/foo.css new file mode 100644 index 000000000..ec0af869a --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-css-inject/src/foo.css @@ -0,0 +1,2 @@ + +.a { color: green; } diff --git a/packages/father-build/src/fixtures/build/rollup-css-inject/src/index.js b/packages/father-build/src/fixtures/build/rollup-css-inject/src/index.js new file mode 100644 index 000000000..b2bb60ab0 --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-css-inject/src/index.js @@ -0,0 +1,3 @@ +import style from './foo.css'; + +console.log(style); diff --git a/packages/father-build/src/fixtures/build/rollup-css-modules/.fatherrc.js b/packages/father-build/src/fixtures/build/rollup-css-modules/.fatherrc.js new file mode 100644 index 000000000..d75347b75 --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-css-modules/.fatherrc.js @@ -0,0 +1,5 @@ + +export default { + esm: { type: 'rollup' }, + cssModules: true, +} diff --git a/packages/father-build/src/fixtures/build/rollup-css-modules/expected/index.esm.js b/packages/father-build/src/fixtures/build/rollup-css-modules/expected/index.esm.js new file mode 100644 index 000000000..cd1c56cf6 --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-css-modules/expected/index.esm.js @@ -0,0 +1,32 @@ +function styleInject(css, ref) { + if ( ref === void 0 ) ref = {}; + var insertAt = ref.insertAt; + + if (!css || typeof document === 'undefined') { return; } + + var head = document.head || document.getElementsByTagName('head')[0]; + var style = document.createElement('style'); + style.type = 'text/css'; + + if (insertAt === 'top') { + if (head.firstChild) { + head.insertBefore(style, head.firstChild); + } else { + head.appendChild(style); + } + } else { + head.appendChild(style); + } + + if (style.styleSheet) { + style.styleSheet.cssText = css; + } else { + style.appendChild(document.createTextNode(css)); + } +} + +var css = "\n.foo_a__3z5g- { color: green; }\n"; +var styles = {"a":"foo_a__3z5g-"}; +styleInject(css); + +console.log('foo', styles.a); diff --git a/packages/father-build/src/fixtures/build/rollup-css-modules/src/foo.css b/packages/father-build/src/fixtures/build/rollup-css-modules/src/foo.css new file mode 100644 index 000000000..ec0af869a --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-css-modules/src/foo.css @@ -0,0 +1,2 @@ + +.a { color: green; } diff --git a/packages/father-build/src/fixtures/build/rollup-css-modules/src/index.js b/packages/father-build/src/fixtures/build/rollup-css-modules/src/index.js new file mode 100644 index 000000000..0ebd23438 --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-css-modules/src/index.js @@ -0,0 +1,3 @@ +import styles from './foo.css'; + +console.log('foo', styles.a); diff --git a/packages/father-build/src/fixtures/build/rollup-css/.fatherrc.js b/packages/father-build/src/fixtures/build/rollup-css/.fatherrc.js new file mode 100644 index 000000000..2f10183a3 --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-css/.fatherrc.js @@ -0,0 +1,4 @@ + +export default { + esm: { type: 'rollup' }, +} diff --git a/packages/father-build/src/fixtures/build/rollup-css/expected/index.esm.js b/packages/father-build/src/fixtures/build/rollup-css/expected/index.esm.js new file mode 100644 index 000000000..c023a0fed --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-css/expected/index.esm.js @@ -0,0 +1,31 @@ +function styleInject(css, ref) { + if ( ref === void 0 ) ref = {}; + var insertAt = ref.insertAt; + + if (!css || typeof document === 'undefined') { return; } + + var head = document.head || document.getElementsByTagName('head')[0]; + var style = document.createElement('style'); + style.type = 'text/css'; + + if (insertAt === 'top') { + if (head.firstChild) { + head.insertBefore(style, head.firstChild); + } else { + head.appendChild(style); + } + } else { + head.appendChild(style); + } + + if (style.styleSheet) { + style.styleSheet.cssText = css; + } else { + style.appendChild(document.createTextNode(css)); + } +} + +var css = "\n.a { color: green; }\n"; +styleInject(css); + +console.log('foo'); diff --git a/packages/father-build/src/fixtures/build/rollup-css/src/foo.css b/packages/father-build/src/fixtures/build/rollup-css/src/foo.css new file mode 100644 index 000000000..ec0af869a --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-css/src/foo.css @@ -0,0 +1,2 @@ + +.a { color: green; } diff --git a/packages/father-build/src/fixtures/build/rollup-css/src/index.js b/packages/father-build/src/fixtures/build/rollup-css/src/index.js new file mode 100644 index 000000000..9a761d97a --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-css/src/index.js @@ -0,0 +1,3 @@ +import './foo.css'; + +console.log('foo'); diff --git a/packages/father-build/src/fixtures/build/rollup-esm-minify/.fatherrc.js b/packages/father-build/src/fixtures/build/rollup-esm-minify/.fatherrc.js new file mode 100644 index 000000000..25ded9459 --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-esm-minify/.fatherrc.js @@ -0,0 +1,4 @@ + +export default { + esm: { type: 'rollup', minify: true }, +}; diff --git a/packages/father-build/src/fixtures/build/rollup-esm-minify/expected/index.esm.js b/packages/father-build/src/fixtures/build/rollup-esm-minify/expected/index.esm.js new file mode 100644 index 000000000..8639db5cd --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-esm-minify/expected/index.esm.js @@ -0,0 +1 @@ +export default function(){console.log("foo")} diff --git a/packages/father-build/src/fixtures/build/rollup-esm-minify/package.json b/packages/father-build/src/fixtures/build/rollup-esm-minify/package.json new file mode 100644 index 000000000..81bb2b631 --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-esm-minify/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "bar": "0.1.0" + } +} diff --git a/packages/father-build/src/fixtures/build/rollup-esm-minify/src/foo.js b/packages/father-build/src/fixtures/build/rollup-esm-minify/src/foo.js new file mode 100644 index 000000000..8fcf68e1b --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-esm-minify/src/foo.js @@ -0,0 +1,4 @@ + +export default function () { + return 'foo'; +} diff --git a/packages/father-build/src/fixtures/build/rollup-esm-minify/src/index.js b/packages/father-build/src/fixtures/build/rollup-esm-minify/src/index.js new file mode 100644 index 000000000..079e99f5a --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-esm-minify/src/index.js @@ -0,0 +1,5 @@ +import foo from './foo'; + +export default function () { + console.log(foo()); +} diff --git a/packages/father-build/src/fixtures/build/rollup-esm-mjs/.fatherrc.js b/packages/father-build/src/fixtures/build/rollup-esm-mjs/.fatherrc.js new file mode 100644 index 000000000..ed89f5d34 --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-esm-mjs/.fatherrc.js @@ -0,0 +1,4 @@ + +export default { + esm: { type: 'rollup', mjs: true }, +}; diff --git a/packages/father-build/src/fixtures/build/rollup-esm-mjs/expected/index.esm.js b/packages/father-build/src/fixtures/build/rollup-esm-mjs/expected/index.esm.js new file mode 100644 index 000000000..6890cce5b --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-esm-mjs/expected/index.esm.js @@ -0,0 +1,6 @@ +import 'bar'; + +function index () { +} + +export default index; diff --git a/packages/father-build/src/fixtures/build/rollup-esm-mjs/expected/index.mjs b/packages/father-build/src/fixtures/build/rollup-esm-mjs/expected/index.mjs new file mode 100644 index 000000000..ddfe15c5e --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-esm-mjs/expected/index.mjs @@ -0,0 +1 @@ +console.log("bar");export default function(){} diff --git a/packages/father-build/src/fixtures/build/rollup-esm-mjs/package.json b/packages/father-build/src/fixtures/build/rollup-esm-mjs/package.json new file mode 100644 index 000000000..81bb2b631 --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-esm-mjs/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "bar": "0.1.0" + } +} diff --git a/packages/father-build/src/fixtures/build/rollup-esm-mjs/src/foo.js b/packages/father-build/src/fixtures/build/rollup-esm-mjs/src/foo.js new file mode 100644 index 000000000..8fcf68e1b --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-esm-mjs/src/foo.js @@ -0,0 +1,4 @@ + +export default function () { + return 'foo'; +} diff --git a/packages/father-build/src/fixtures/build/rollup-esm-mjs/src/index.js b/packages/father-build/src/fixtures/build/rollup-esm-mjs/src/index.js new file mode 100644 index 000000000..01d696690 --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-esm-mjs/src/index.js @@ -0,0 +1,6 @@ +import 'bar'; +import foo from './foo'; + +export default function () { + foo(); +} diff --git a/packages/father-build/src/fixtures/build/rollup-externalsExclude/.fatherrc.js b/packages/father-build/src/fixtures/build/rollup-externalsExclude/.fatherrc.js new file mode 100644 index 000000000..8ab27f0f1 --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-externalsExclude/.fatherrc.js @@ -0,0 +1,11 @@ + +export default { + esm: { type: 'rollup' }, + cjs: { type: 'rollup' }, + extraExternals: [ + 'foo', + ], + externalsExclude: [ + 'foo/bar', + ], +}; diff --git a/packages/father-build/src/fixtures/build/rollup-externalsExclude/expected/index.esm.js b/packages/father-build/src/fixtures/build/rollup-externalsExclude/expected/index.esm.js new file mode 100644 index 000000000..60d2ac221 --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-externalsExclude/expected/index.esm.js @@ -0,0 +1,5 @@ +import 'foo'; + +(() => { + console.log('bar'); +})(); diff --git a/packages/father-build/src/fixtures/build/rollup-externalsExclude/expected/index.js b/packages/father-build/src/fixtures/build/rollup-externalsExclude/expected/index.js new file mode 100644 index 000000000..98f72615c --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-externalsExclude/expected/index.js @@ -0,0 +1,7 @@ +'use strict'; + +require('foo'); + +(() => { + console.log('bar'); +})(); diff --git a/packages/father-build/src/fixtures/build/rollup-externalsExclude/package.json b/packages/father-build/src/fixtures/build/rollup-externalsExclude/package.json new file mode 100644 index 000000000..6a3252ea8 --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-externalsExclude/package.json @@ -0,0 +1,3 @@ +{ + "version": "0.1.0" +} diff --git a/packages/father-build/src/fixtures/build/rollup-externalsExclude/src/index.js b/packages/father-build/src/fixtures/build/rollup-externalsExclude/src/index.js new file mode 100644 index 000000000..40d29331c --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-externalsExclude/src/index.js @@ -0,0 +1,2 @@ +import 'foo'; +import 'foo/bar'; diff --git a/packages/father-build/src/fixtures/build/rollup-extra-babel-presets-and-plugins/.fatherrc.js b/packages/father-build/src/fixtures/build/rollup-extra-babel-presets-and-plugins/.fatherrc.js new file mode 100644 index 000000000..0dfd93f4b --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-extra-babel-presets-and-plugins/.fatherrc.js @@ -0,0 +1,10 @@ + +export default { + esm: { type: 'rollup' }, + extraBabelPresets: [ + require.resolve('./preset'), + ], + extraBabelPlugins: [ + require.resolve('./p2'), + ], +}; diff --git a/packages/father-build/src/fixtures/build/rollup-extra-babel-presets-and-plugins/expected/index.esm.js b/packages/father-build/src/fixtures/build/rollup-extra-babel-presets-and-plugins/expected/index.esm.js new file mode 100644 index 000000000..0b60c0ede --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-extra-babel-presets-and-plugins/expected/index.esm.js @@ -0,0 +1,2 @@ +console.log("p1", "p2", 1); +alert(2); diff --git a/packages/father-build/src/fixtures/build/rollup-extra-babel-presets-and-plugins/p1.js b/packages/father-build/src/fixtures/build/rollup-extra-babel-presets-and-plugins/p1.js new file mode 100644 index 000000000..a3a3e24e1 --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-extra-babel-presets-and-plugins/p1.js @@ -0,0 +1,20 @@ + +module.exports = function ({ types: t }) { + function isConsoleLog(node) { + const { callee, callee: { object, property } } = node; + return t.isMemberExpression(callee) + && t.isIdentifier(object) && object.name === 'console' + && t.isIdentifier(property) && property.name === 'log'; + } + + return { + visitor: { + CallExpression(path, state) { + const { node, node: { callee, callee: { object, property } } } = path; + if (isConsoleLog(node)) { + node.arguments.unshift(t.stringLiteral('p1')); + } + }, + }, + }; +} diff --git a/packages/father-build/src/fixtures/build/rollup-extra-babel-presets-and-plugins/p2.js b/packages/father-build/src/fixtures/build/rollup-extra-babel-presets-and-plugins/p2.js new file mode 100644 index 000000000..297ac66d1 --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-extra-babel-presets-and-plugins/p2.js @@ -0,0 +1,20 @@ + +module.exports = function ({ types: t }) { + function isConsoleLog(node) { + const { callee, callee: { object, property } } = node; + return t.isMemberExpression(callee) + && t.isIdentifier(object) && object.name === 'console' + && t.isIdentifier(property) && property.name === 'log'; + } + + return { + visitor: { + CallExpression(path, state) { + const { node, node: { callee, callee: { object, property } } } = path; + if (isConsoleLog(node)) { + node.arguments.unshift(t.stringLiteral('p2')); + } + }, + }, + }; +} diff --git a/packages/father-build/src/fixtures/build/rollup-extra-babel-presets-and-plugins/preset.js b/packages/father-build/src/fixtures/build/rollup-extra-babel-presets-and-plugins/preset.js new file mode 100644 index 000000000..07ccbb29c --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-extra-babel-presets-and-plugins/preset.js @@ -0,0 +1,8 @@ + +module.exports = function () { + return { + plugins: [ + require.resolve('./p1'), + ], + }; +} diff --git a/packages/father-build/src/fixtures/build/rollup-extra-babel-presets-and-plugins/src/index.js b/packages/father-build/src/fixtures/build/rollup-extra-babel-presets-and-plugins/src/index.js new file mode 100644 index 000000000..923799068 --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-extra-babel-presets-and-plugins/src/index.js @@ -0,0 +1,3 @@ + +console.log(1); +alert(2); diff --git a/packages/father-build/src/fixtures/build/rollup-extraExternals/.fatherrc.js b/packages/father-build/src/fixtures/build/rollup-extraExternals/.fatherrc.js new file mode 100644 index 000000000..409af0a2c --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-extraExternals/.fatherrc.js @@ -0,0 +1,9 @@ + +export default { + esm: { type: 'rollup' }, + extraExternals: [ + '@ali/foo', + '@tmp', + 'foo', + ] +}; diff --git a/packages/father-build/src/fixtures/build/rollup-extraExternals/expected/index.esm.js b/packages/father-build/src/fixtures/build/rollup-extraExternals/expected/index.esm.js new file mode 100644 index 000000000..1a8c84dab --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-extraExternals/expected/index.esm.js @@ -0,0 +1,5 @@ +import 'foo'; +import '@ali/foo'; +import '@ali/foo/bar'; +import '@tmp/foo'; +import '@tmp/foo/bar'; diff --git a/packages/father-build/src/fixtures/build/rollup-extraExternals/package.json b/packages/father-build/src/fixtures/build/rollup-extraExternals/package.json new file mode 100644 index 000000000..6a3252ea8 --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-extraExternals/package.json @@ -0,0 +1,3 @@ +{ + "version": "0.1.0" +} diff --git a/packages/father-build/src/fixtures/build/rollup-extraExternals/src/index.js b/packages/father-build/src/fixtures/build/rollup-extraExternals/src/index.js new file mode 100644 index 000000000..3e307458f --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-extraExternals/src/index.js @@ -0,0 +1,6 @@ + +import 'foo'; +import '@ali/foo'; +import '@ali/foo/bar'; +import '@tmp/foo'; +import '@tmp/foo/bar'; diff --git a/packages/father-build/src/fixtures/build/rollup-extraRollupPlugins/.fatherrc.js b/packages/father-build/src/fixtures/build/rollup-extraRollupPlugins/.fatherrc.js new file mode 100644 index 000000000..f3438f39b --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-extraRollupPlugins/.fatherrc.js @@ -0,0 +1,10 @@ +import replace from 'rollup-plugin-replace'; + +export default { + esm: { type: 'rollup' }, + extraRollupPlugins: [ + replace({ + VERSION: JSON.stringify('1.0.0'), + }), + ], +}; diff --git a/packages/father-build/src/fixtures/build/rollup-extraRollupPlugins/expected/index.esm.js b/packages/father-build/src/fixtures/build/rollup-extraRollupPlugins/expected/index.esm.js new file mode 100644 index 000000000..22c71e692 --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-extraRollupPlugins/expected/index.esm.js @@ -0,0 +1 @@ +console.log("1.0.0"); diff --git a/packages/father-build/src/fixtures/build/rollup-extraRollupPlugins/package.json b/packages/father-build/src/fixtures/build/rollup-extraRollupPlugins/package.json new file mode 100644 index 000000000..6a3252ea8 --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-extraRollupPlugins/package.json @@ -0,0 +1,3 @@ +{ + "version": "0.1.0" +} diff --git a/packages/father-build/src/fixtures/build/rollup-extraRollupPlugins/src/index.js b/packages/father-build/src/fixtures/build/rollup-extraRollupPlugins/src/index.js new file mode 100644 index 000000000..2c9bf5559 --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-extraRollupPlugins/src/index.js @@ -0,0 +1,2 @@ + +console.log(VERSION); diff --git a/packages/father-build/src/fixtures/build/rollup-import-directory/.fatherrc.js b/packages/father-build/src/fixtures/build/rollup-import-directory/.fatherrc.js new file mode 100644 index 000000000..8b9450222 --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-import-directory/.fatherrc.js @@ -0,0 +1,4 @@ + +export default { + esm: { type: 'rollup' }, +}; diff --git a/packages/father-build/src/fixtures/build/rollup-import-directory/expected/index.esm.js b/packages/father-build/src/fixtures/build/rollup-import-directory/expected/index.esm.js new file mode 100644 index 000000000..81afa3157 --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-import-directory/expected/index.esm.js @@ -0,0 +1 @@ +console.log('foo'); diff --git a/packages/father-build/src/fixtures/build/rollup-import-directory/src/foo/index.js b/packages/father-build/src/fixtures/build/rollup-import-directory/src/foo/index.js new file mode 100644 index 000000000..695557eab --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-import-directory/src/foo/index.js @@ -0,0 +1,2 @@ + +console.log('foo'); diff --git a/packages/father-build/src/fixtures/build/rollup-import-directory/src/index.js b/packages/father-build/src/fixtures/build/rollup-import-directory/src/index.js new file mode 100644 index 000000000..ead12a719 --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-import-directory/src/index.js @@ -0,0 +1 @@ +import './foo'; diff --git a/packages/father-build/src/fixtures/build/rollup-importLibToEs/.fatherrc.js b/packages/father-build/src/fixtures/build/rollup-importLibToEs/.fatherrc.js new file mode 100644 index 000000000..2881eb584 --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-importLibToEs/.fatherrc.js @@ -0,0 +1,5 @@ + +export default { + cjs: { type: 'rollup' }, + esm: { type: 'rollup', importLibToEs: true }, +}; diff --git a/packages/father-build/src/fixtures/build/rollup-importLibToEs/expected/index.esm.js b/packages/father-build/src/fixtures/build/rollup-importLibToEs/expected/index.esm.js new file mode 100644 index 000000000..ae0559b15 --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-importLibToEs/expected/index.esm.js @@ -0,0 +1,5 @@ +function foo () { + return 'es/foo'; +} + +console.log(foo()); diff --git a/packages/father-build/src/fixtures/build/rollup-importLibToEs/expected/index.js b/packages/father-build/src/fixtures/build/rollup-importLibToEs/expected/index.js new file mode 100644 index 000000000..c4e33e771 --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-importLibToEs/expected/index.js @@ -0,0 +1,7 @@ +'use strict'; + +function foo () { + return 'lib/foo'; +} + +console.log(foo()); diff --git a/packages/father-build/src/fixtures/build/rollup-importLibToEs/src/index.js b/packages/father-build/src/fixtures/build/rollup-importLibToEs/src/index.js new file mode 100644 index 000000000..a8105c428 --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-importLibToEs/src/index.js @@ -0,0 +1,3 @@ +import foo from 'foo/lib/foo'; + +console.log(foo()); diff --git a/packages/father-build/src/fixtures/build/rollup-inject/.fatherrc.js b/packages/father-build/src/fixtures/build/rollup-inject/.fatherrc.js new file mode 100644 index 000000000..cc0759d33 --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-inject/.fatherrc.js @@ -0,0 +1,7 @@ + +export default { + esm: { type: 'rollup' }, + inject: { + 'window.foo': 'foo', + }, +}; diff --git a/packages/father-build/src/fixtures/build/rollup-inject/expected/index.esm.js b/packages/father-build/src/fixtures/build/rollup-inject/expected/index.esm.js new file mode 100644 index 000000000..2f87bded3 --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-inject/expected/index.esm.js @@ -0,0 +1,3 @@ +import $inject_window_foo from 'foo'; + +console.log($inject_window_foo); diff --git a/packages/father-build/src/fixtures/build/rollup-inject/package.json b/packages/father-build/src/fixtures/build/rollup-inject/package.json new file mode 100644 index 000000000..040bc3a1e --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-inject/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "foo": "0.1.0" + } +} diff --git a/packages/father-build/src/fixtures/build/rollup-inject/src/index.js b/packages/father-build/src/fixtures/build/rollup-inject/src/index.js new file mode 100644 index 000000000..4f632e0de --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-inject/src/index.js @@ -0,0 +1 @@ +console.log(window.foo); diff --git a/packages/father-build/src/fixtures/build/rollup-json/.fatherrc.js b/packages/father-build/src/fixtures/build/rollup-json/.fatherrc.js new file mode 100644 index 000000000..8b9450222 --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-json/.fatherrc.js @@ -0,0 +1,4 @@ + +export default { + esm: { type: 'rollup' }, +}; diff --git a/packages/father-build/src/fixtures/build/rollup-json/expected/index.esm.js b/packages/father-build/src/fixtures/build/rollup-json/expected/index.esm.js new file mode 100644 index 000000000..6ee3aced8 --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-json/expected/index.esm.js @@ -0,0 +1,6 @@ +var foo = "bar"; +var pkg = { + foo: foo +}; + +console.log(pkg); diff --git a/packages/father-build/src/fixtures/build/rollup-json/src/index.js b/packages/father-build/src/fixtures/build/rollup-json/src/index.js new file mode 100644 index 000000000..3ce7d4a36 --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-json/src/index.js @@ -0,0 +1,3 @@ +import pkg from './pkg.json'; + +console.log(pkg); diff --git a/packages/father-build/src/fixtures/build/rollup-json/src/pkg.json b/packages/father-build/src/fixtures/build/rollup-json/src/pkg.json new file mode 100644 index 000000000..c8c4105eb --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-json/src/pkg.json @@ -0,0 +1,3 @@ +{ + "foo": "bar" +} diff --git a/packages/father-build/src/fixtures/build/rollup-less-modules/.fatherrc.js b/packages/father-build/src/fixtures/build/rollup-less-modules/.fatherrc.js new file mode 100644 index 000000000..d75347b75 --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-less-modules/.fatherrc.js @@ -0,0 +1,5 @@ + +export default { + esm: { type: 'rollup' }, + cssModules: true, +} diff --git a/packages/father-build/src/fixtures/build/rollup-less-modules/expected/index.esm.js b/packages/father-build/src/fixtures/build/rollup-less-modules/expected/index.esm.js new file mode 100644 index 000000000..cd7d766f5 --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-less-modules/expected/index.esm.js @@ -0,0 +1,32 @@ +function styleInject(css, ref) { + if ( ref === void 0 ) ref = {}; + var insertAt = ref.insertAt; + + if (!css || typeof document === 'undefined') { return; } + + var head = document.head || document.getElementsByTagName('head')[0]; + var style = document.createElement('style'); + style.type = 'text/css'; + + if (insertAt === 'top') { + if (head.firstChild) { + head.insertBefore(style, head.firstChild); + } else { + head.appendChild(style); + } + } else { + head.appendChild(style); + } + + if (style.styleSheet) { + style.styleSheet.cssText = css; + } else { + style.appendChild(document.createTextNode(css)); + } +} + +var css = ".foo_a__1JvkR {\n color: green;\n}\n"; +var styles = {"a":"foo_a__1JvkR"}; +styleInject(css); + +console.log('foo', styles.a); diff --git a/packages/father-build/src/fixtures/build/rollup-less-modules/src/foo.less b/packages/father-build/src/fixtures/build/rollup-less-modules/src/foo.less new file mode 100644 index 000000000..ec0af869a --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-less-modules/src/foo.less @@ -0,0 +1,2 @@ + +.a { color: green; } diff --git a/packages/father-build/src/fixtures/build/rollup-less-modules/src/index.js b/packages/father-build/src/fixtures/build/rollup-less-modules/src/index.js new file mode 100644 index 000000000..9c442740f --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-less-modules/src/index.js @@ -0,0 +1,3 @@ +import styles from './foo.less'; + +console.log('foo', styles.a); diff --git a/packages/father-build/src/fixtures/build/rollup-less-node-modules/.fatherrc.js b/packages/father-build/src/fixtures/build/rollup-less-node-modules/.fatherrc.js new file mode 100644 index 000000000..2f10183a3 --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-less-node-modules/.fatherrc.js @@ -0,0 +1,4 @@ + +export default { + esm: { type: 'rollup' }, +} diff --git a/packages/father-build/src/fixtures/build/rollup-less-node-modules/expected/index.esm.js b/packages/father-build/src/fixtures/build/rollup-less-node-modules/expected/index.esm.js new file mode 100644 index 000000000..4c6523350 --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-less-node-modules/expected/index.esm.js @@ -0,0 +1,31 @@ +function styleInject(css, ref) { + if ( ref === void 0 ) ref = {}; + var insertAt = ref.insertAt; + + if (!css || typeof document === 'undefined') { return; } + + var head = document.head || document.getElementsByTagName('head')[0]; + var style = document.createElement('style'); + style.type = 'text/css'; + + if (insertAt === 'top') { + if (head.firstChild) { + head.insertBefore(style, head.firstChild); + } else { + head.appendChild(style); + } + } else { + head.appendChild(style); + } + + if (style.styleSheet) { + style.styleSheet.cssText = css; + } else { + style.appendChild(document.createTextNode(css)); + } +} + +var css = ".bar {\n color: red;\n}\n.a {\n color: green;\n}\n"; +styleInject(css); + +console.log('foo'); diff --git a/packages/father-build/src/fixtures/build/rollup-less-node-modules/src/foo.less b/packages/father-build/src/fixtures/build/rollup-less-node-modules/src/foo.less new file mode 100644 index 000000000..f49f40a45 --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-less-node-modules/src/foo.less @@ -0,0 +1,3 @@ +@import '~foo/bar.less'; + +.a { color: green; } diff --git a/packages/father-build/src/fixtures/build/rollup-less-node-modules/src/index.js b/packages/father-build/src/fixtures/build/rollup-less-node-modules/src/index.js new file mode 100644 index 000000000..eb9155cfc --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-less-node-modules/src/index.js @@ -0,0 +1,3 @@ +import './foo.less'; + +console.log('foo'); diff --git a/packages/father-build/src/fixtures/build/rollup-less/.fatherrc.js b/packages/father-build/src/fixtures/build/rollup-less/.fatherrc.js new file mode 100644 index 000000000..2f10183a3 --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-less/.fatherrc.js @@ -0,0 +1,4 @@ + +export default { + esm: { type: 'rollup' }, +} diff --git a/packages/father-build/src/fixtures/build/rollup-less/expected/index.esm.js b/packages/father-build/src/fixtures/build/rollup-less/expected/index.esm.js new file mode 100644 index 000000000..af4013445 --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-less/expected/index.esm.js @@ -0,0 +1,31 @@ +function styleInject(css, ref) { + if ( ref === void 0 ) ref = {}; + var insertAt = ref.insertAt; + + if (!css || typeof document === 'undefined') { return; } + + var head = document.head || document.getElementsByTagName('head')[0]; + var style = document.createElement('style'); + style.type = 'text/css'; + + if (insertAt === 'top') { + if (head.firstChild) { + head.insertBefore(style, head.firstChild); + } else { + head.appendChild(style); + } + } else { + head.appendChild(style); + } + + if (style.styleSheet) { + style.styleSheet.cssText = css; + } else { + style.appendChild(document.createTextNode(css)); + } +} + +var css = ".a {\n color: green;\n}\n"; +styleInject(css); + +console.log('foo'); diff --git a/packages/father-build/src/fixtures/build/rollup-less/src/foo.less b/packages/father-build/src/fixtures/build/rollup-less/src/foo.less new file mode 100644 index 000000000..ec0af869a --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-less/src/foo.less @@ -0,0 +1,2 @@ + +.a { color: green; } diff --git a/packages/father-build/src/fixtures/build/rollup-less/src/index.js b/packages/father-build/src/fixtures/build/rollup-less/src/index.js new file mode 100644 index 000000000..eb9155cfc --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-less/src/index.js @@ -0,0 +1,3 @@ +import './foo.less'; + +console.log('foo'); diff --git a/packages/father-build/src/fixtures/build/rollup-multiple-entry/.fatherrc.js b/packages/father-build/src/fixtures/build/rollup-multiple-entry/.fatherrc.js new file mode 100644 index 000000000..c49c9b152 --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-multiple-entry/.fatherrc.js @@ -0,0 +1,8 @@ + +export default { + entry: [ + './src/foo.js', + './src/bar.js', + ], + esm: { type: 'rollup' }, +}; diff --git a/packages/father-build/src/fixtures/build/rollup-multiple-entry/expected/bar.esm.js b/packages/father-build/src/fixtures/build/rollup-multiple-entry/expected/bar.esm.js new file mode 100644 index 000000000..8cc7aa3e9 --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-multiple-entry/expected/bar.esm.js @@ -0,0 +1 @@ +console.log('bar'); diff --git a/packages/father-build/src/fixtures/build/rollup-multiple-entry/expected/foo.esm.js b/packages/father-build/src/fixtures/build/rollup-multiple-entry/expected/foo.esm.js new file mode 100644 index 000000000..81afa3157 --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-multiple-entry/expected/foo.esm.js @@ -0,0 +1 @@ +console.log('foo'); diff --git a/packages/father-build/src/fixtures/build/rollup-multiple-entry/src/bar.js b/packages/father-build/src/fixtures/build/rollup-multiple-entry/src/bar.js new file mode 100644 index 000000000..9638d025d --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-multiple-entry/src/bar.js @@ -0,0 +1,2 @@ + +console.log('bar'); diff --git a/packages/father-build/src/fixtures/build/rollup-multiple-entry/src/foo.js b/packages/father-build/src/fixtures/build/rollup-multiple-entry/src/foo.js new file mode 100644 index 000000000..695557eab --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-multiple-entry/src/foo.js @@ -0,0 +1,2 @@ + +console.log('foo'); diff --git a/packages/father-build/src/fixtures/build/rollup-node-resolve/.fatherrc.js b/packages/father-build/src/fixtures/build/rollup-node-resolve/.fatherrc.js new file mode 100644 index 000000000..5d6b9128e --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-node-resolve/.fatherrc.js @@ -0,0 +1,9 @@ + +export default { + umd: { + minFile: false, + }, + nodeResolveOpts: { + browser: true, + }, +}; diff --git a/packages/father-build/src/fixtures/build/rollup-node-resolve/expected/index.umd.js b/packages/father-build/src/fixtures/build/rollup-node-resolve/expected/index.umd.js new file mode 100644 index 000000000..5e9006d51 --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-node-resolve/expected/index.umd.js @@ -0,0 +1,10 @@ +(function (factory) { + typeof define === 'function' && define.amd ? define(factory) : + factory(); +}((function () { 'use strict'; + + var browser = 'hello browser'; + + console.log(browser); + +}))); diff --git a/packages/father-build/src/fixtures/build/rollup-node-resolve/package.json b/packages/father-build/src/fixtures/build/rollup-node-resolve/package.json new file mode 100644 index 000000000..040bc3a1e --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-node-resolve/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "foo": "0.1.0" + } +} diff --git a/packages/father-build/src/fixtures/build/rollup-node-resolve/src/index.js b/packages/father-build/src/fixtures/build/rollup-node-resolve/src/index.js new file mode 100644 index 000000000..909687930 --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-node-resolve/src/index.js @@ -0,0 +1,3 @@ +import hello from 'foo'; + +console.log(hello); diff --git a/packages/father-build/src/fixtures/build/rollup-normal/.fatherrc.js b/packages/father-build/src/fixtures/build/rollup-normal/.fatherrc.js new file mode 100644 index 000000000..2ae3de8e2 --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-normal/.fatherrc.js @@ -0,0 +1,6 @@ + +export default { + cjs: { type: 'rollup' }, + esm: { type: 'rollup' }, + umd: {}, +}; diff --git a/packages/father-build/src/fixtures/build/rollup-normal/expected/index.esm.js b/packages/father-build/src/fixtures/build/rollup-normal/expected/index.esm.js new file mode 100644 index 000000000..ea63e10f2 --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-normal/expected/index.esm.js @@ -0,0 +1,7 @@ +import 'bar'; + +function foo () { + return 'foo'; +} + +console.log(foo()); diff --git a/packages/father-build/src/fixtures/build/rollup-normal/expected/index.js b/packages/father-build/src/fixtures/build/rollup-normal/expected/index.js new file mode 100644 index 000000000..c50247820 --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-normal/expected/index.js @@ -0,0 +1,9 @@ +'use strict'; + +require('bar'); + +function foo () { + return 'foo'; +} + +console.log(foo()); diff --git a/packages/father-build/src/fixtures/build/rollup-normal/expected/index.umd.js b/packages/father-build/src/fixtures/build/rollup-normal/expected/index.umd.js new file mode 100644 index 000000000..ca82ca23f --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-normal/expected/index.umd.js @@ -0,0 +1,12 @@ +(function (factory) { + typeof define === 'function' && define.amd ? define(['bar'], factory) : + factory(); +}((function () { 'use strict'; + + function foo () { + return 'foo'; + } + + console.log(foo()); + +}))); diff --git a/packages/father-build/src/fixtures/build/rollup-normal/expected/index.umd.min.js b/packages/father-build/src/fixtures/build/rollup-normal/expected/index.umd.min.js new file mode 100644 index 000000000..c654cc12f --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-normal/expected/index.umd.min.js @@ -0,0 +1 @@ +!function(n){"function"==typeof define&&define.amd?define(["bar"],n):n()}((function(){"use strict";console.log("foo")})); diff --git a/packages/father-build/src/fixtures/build/rollup-normal/package.json b/packages/father-build/src/fixtures/build/rollup-normal/package.json new file mode 100644 index 000000000..5ca44beaa --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-normal/package.json @@ -0,0 +1,5 @@ +{ + "peerDependencies": { + "bar": "0.1.0" + } +} diff --git a/packages/father-build/src/fixtures/build/rollup-normal/src/foo.js b/packages/father-build/src/fixtures/build/rollup-normal/src/foo.js new file mode 100644 index 000000000..8fcf68e1b --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-normal/src/foo.js @@ -0,0 +1,4 @@ + +export default function () { + return 'foo'; +} diff --git a/packages/father-build/src/fixtures/build/rollup-normal/src/index.js b/packages/father-build/src/fixtures/build/rollup-normal/src/index.js new file mode 100644 index 000000000..9d80d06dc --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-normal/src/index.js @@ -0,0 +1,4 @@ +import 'bar'; +import foo from './foo'; + +console.log(foo()); diff --git a/packages/father-build/src/fixtures/build/rollup-outputFilename-with-overrides/.fatherrc.js b/packages/father-build/src/fixtures/build/rollup-outputFilename-with-overrides/.fatherrc.js new file mode 100644 index 000000000..1bc453016 --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-outputFilename-with-overrides/.fatherrc.js @@ -0,0 +1,29 @@ + +export default { + entry: [ + 'src/index.js', + 'src/foo.js', + 'src/bar.js', + ], + umd: { + }, + cjs: { type: 'rollup' }, + esm: { type: 'rollup' }, + file: 'dva', + overridesByEntry: { + 'src/foo.js': { + file: 'dva.foo', + cjs: { type: 'rollup', file: 'dva.foo.cjs' }, + esm: { type: 'rollup', file: 'dva.foo.esm' }, + }, + 'src/bar.js': { + cjs: false, + esm: false, + umd: { file: 'dva.bar' }, + }, + 'src/index.js': { + cjs: false, + esm: false, + }, + }, +}; diff --git a/packages/father-build/src/fixtures/build/rollup-outputFilename-with-overrides/expected/dva.bar.js b/packages/father-build/src/fixtures/build/rollup-outputFilename-with-overrides/expected/dva.bar.js new file mode 100644 index 000000000..70ba81f61 --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-outputFilename-with-overrides/expected/dva.bar.js @@ -0,0 +1,8 @@ +(function (factory) { + typeof define === 'function' && define.amd ? define(factory) : + factory(); +}((function () { 'use strict'; + + console.log('bar'); + +}))); diff --git a/packages/father-build/src/fixtures/build/rollup-outputFilename-with-overrides/expected/dva.bar.min.js b/packages/father-build/src/fixtures/build/rollup-outputFilename-with-overrides/expected/dva.bar.min.js new file mode 100644 index 000000000..7f57ce5a8 --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-outputFilename-with-overrides/expected/dva.bar.min.js @@ -0,0 +1 @@ +!function(n){"function"==typeof define&&define.amd?define(n):n()}((function(){"use strict";console.log("bar")})); diff --git a/packages/father-build/src/fixtures/build/rollup-outputFilename-with-overrides/expected/dva.esm.js b/packages/father-build/src/fixtures/build/rollup-outputFilename-with-overrides/expected/dva.esm.js new file mode 100644 index 000000000..8cc7aa3e9 --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-outputFilename-with-overrides/expected/dva.esm.js @@ -0,0 +1 @@ +console.log('bar'); diff --git a/packages/father-build/src/fixtures/build/rollup-outputFilename-with-overrides/expected/dva.foo.cjs.js b/packages/father-build/src/fixtures/build/rollup-outputFilename-with-overrides/expected/dva.foo.cjs.js new file mode 100644 index 000000000..aece69e8b --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-outputFilename-with-overrides/expected/dva.foo.cjs.js @@ -0,0 +1,3 @@ +'use strict'; + +console.log('foo'); diff --git a/packages/father-build/src/fixtures/build/rollup-outputFilename-with-overrides/expected/dva.foo.esm.js b/packages/father-build/src/fixtures/build/rollup-outputFilename-with-overrides/expected/dva.foo.esm.js new file mode 100644 index 000000000..81afa3157 --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-outputFilename-with-overrides/expected/dva.foo.esm.js @@ -0,0 +1 @@ +console.log('foo'); diff --git a/packages/father-build/src/fixtures/build/rollup-outputFilename-with-overrides/expected/dva.foo.umd.js b/packages/father-build/src/fixtures/build/rollup-outputFilename-with-overrides/expected/dva.foo.umd.js new file mode 100644 index 000000000..62e3a909f --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-outputFilename-with-overrides/expected/dva.foo.umd.js @@ -0,0 +1,8 @@ +(function (factory) { + typeof define === 'function' && define.amd ? define(factory) : + factory(); +}((function () { 'use strict'; + + console.log('foo'); + +}))); diff --git a/packages/father-build/src/fixtures/build/rollup-outputFilename-with-overrides/expected/dva.foo.umd.min.js b/packages/father-build/src/fixtures/build/rollup-outputFilename-with-overrides/expected/dva.foo.umd.min.js new file mode 100644 index 000000000..e383c2dd0 --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-outputFilename-with-overrides/expected/dva.foo.umd.min.js @@ -0,0 +1 @@ +!function(n){"function"==typeof define&&define.amd?define(n):n()}((function(){"use strict";console.log("foo")})); diff --git a/packages/father-build/src/fixtures/build/rollup-outputFilename-with-overrides/expected/dva.js b/packages/father-build/src/fixtures/build/rollup-outputFilename-with-overrides/expected/dva.js new file mode 100644 index 000000000..2b1938520 --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-outputFilename-with-overrides/expected/dva.js @@ -0,0 +1,3 @@ +'use strict'; + +console.log('bar'); diff --git a/packages/father-build/src/fixtures/build/rollup-outputFilename-with-overrides/expected/dva.umd.js b/packages/father-build/src/fixtures/build/rollup-outputFilename-with-overrides/expected/dva.umd.js new file mode 100644 index 000000000..fd2bf1e89 --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-outputFilename-with-overrides/expected/dva.umd.js @@ -0,0 +1,8 @@ +(function (factory) { + typeof define === 'function' && define.amd ? define(factory) : + factory(); +}((function () { 'use strict'; + + console.log('index'); + +}))); diff --git a/packages/father-build/src/fixtures/build/rollup-outputFilename-with-overrides/expected/dva.umd.min.js b/packages/father-build/src/fixtures/build/rollup-outputFilename-with-overrides/expected/dva.umd.min.js new file mode 100644 index 000000000..a15d21f33 --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-outputFilename-with-overrides/expected/dva.umd.min.js @@ -0,0 +1 @@ +!function(n){"function"==typeof define&&define.amd?define(n):n()}((function(){"use strict";console.log("index")})); diff --git a/packages/father-build/src/fixtures/build/rollup-outputFilename-with-overrides/src/bar.js b/packages/father-build/src/fixtures/build/rollup-outputFilename-with-overrides/src/bar.js new file mode 100644 index 000000000..9638d025d --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-outputFilename-with-overrides/src/bar.js @@ -0,0 +1,2 @@ + +console.log('bar'); diff --git a/packages/father-build/src/fixtures/build/rollup-outputFilename-with-overrides/src/foo.js b/packages/father-build/src/fixtures/build/rollup-outputFilename-with-overrides/src/foo.js new file mode 100644 index 000000000..695557eab --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-outputFilename-with-overrides/src/foo.js @@ -0,0 +1,2 @@ + +console.log('foo'); diff --git a/packages/father-build/src/fixtures/build/rollup-outputFilename-with-overrides/src/index.js b/packages/father-build/src/fixtures/build/rollup-outputFilename-with-overrides/src/index.js new file mode 100644 index 000000000..112a16294 --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-outputFilename-with-overrides/src/index.js @@ -0,0 +1,2 @@ + +console.log('index'); diff --git a/packages/father-build/src/fixtures/build/rollup-replace/.fatherrc.js b/packages/father-build/src/fixtures/build/rollup-replace/.fatherrc.js new file mode 100644 index 000000000..5be9fca42 --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-replace/.fatherrc.js @@ -0,0 +1,7 @@ + +export default { + esm: { type: 'rollup' }, + replace: { + VERSION: JSON.stringify(require('./package').version), + }, +}; diff --git a/packages/father-build/src/fixtures/build/rollup-replace/expected/index.esm.js b/packages/father-build/src/fixtures/build/rollup-replace/expected/index.esm.js new file mode 100644 index 000000000..3bbfad94d --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-replace/expected/index.esm.js @@ -0,0 +1 @@ +console.log("0.1.0"); diff --git a/packages/father-build/src/fixtures/build/rollup-replace/package.json b/packages/father-build/src/fixtures/build/rollup-replace/package.json new file mode 100644 index 000000000..6a3252ea8 --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-replace/package.json @@ -0,0 +1,3 @@ +{ + "version": "0.1.0" +} diff --git a/packages/father-build/src/fixtures/build/rollup-replace/src/index.js b/packages/father-build/src/fixtures/build/rollup-replace/src/index.js new file mode 100644 index 000000000..2c9bf5559 --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-replace/src/index.js @@ -0,0 +1,2 @@ + +console.log(VERSION); diff --git a/packages/father-build/src/fixtures/build/rollup-resolve-jsx/.fatherrc.js b/packages/father-build/src/fixtures/build/rollup-resolve-jsx/.fatherrc.js new file mode 100644 index 000000000..8b9450222 --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-resolve-jsx/.fatherrc.js @@ -0,0 +1,4 @@ + +export default { + esm: { type: 'rollup' }, +}; diff --git a/packages/father-build/src/fixtures/build/rollup-resolve-jsx/expected/index.esm.js b/packages/father-build/src/fixtures/build/rollup-resolve-jsx/expected/index.esm.js new file mode 100644 index 000000000..949fb857b --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-resolve-jsx/expected/index.esm.js @@ -0,0 +1,11 @@ +import React from 'react'; + +var Foo = (function () { + return /*#__PURE__*/React.createElement("h1", null, "Foo"); +}); + +var index = (function () { + return /*#__PURE__*/React.createElement(Foo, null); +}); + +export default index; diff --git a/packages/father-build/src/fixtures/build/rollup-resolve-jsx/package.json b/packages/father-build/src/fixtures/build/rollup-resolve-jsx/package.json new file mode 100644 index 000000000..55ef290f1 --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-resolve-jsx/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "react": "16" + } +} diff --git a/packages/father-build/src/fixtures/build/rollup-resolve-jsx/src/Foo.jsx b/packages/father-build/src/fixtures/build/rollup-resolve-jsx/src/Foo.jsx new file mode 100644 index 000000000..b518bbcb2 --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-resolve-jsx/src/Foo.jsx @@ -0,0 +1,2 @@ + +export default () =>

Foo

; diff --git a/packages/father-build/src/fixtures/build/rollup-resolve-jsx/src/index.js b/packages/father-build/src/fixtures/build/rollup-resolve-jsx/src/index.js new file mode 100644 index 000000000..f61210cf6 --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-resolve-jsx/src/index.js @@ -0,0 +1,3 @@ +import Foo from './Foo'; + +export default () => ; diff --git a/packages/father-build/src/fixtures/build/rollup-runtimeHelpers/.fatherrc.js b/packages/father-build/src/fixtures/build/rollup-runtimeHelpers/.fatherrc.js new file mode 100644 index 000000000..b3cf64724 --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-runtimeHelpers/.fatherrc.js @@ -0,0 +1,6 @@ + +export default { + runtimeHelpers: true, + esm: { type: 'rollup' }, + cjs: { type: 'rollup' }, +} diff --git a/packages/father-build/src/fixtures/build/rollup-runtimeHelpers/expected/index.esm.js b/packages/father-build/src/fixtures/build/rollup-runtimeHelpers/expected/index.esm.js new file mode 100644 index 000000000..1814cb26f --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-runtimeHelpers/expected/index.esm.js @@ -0,0 +1,17 @@ +import _classCallCheck from '@babel/runtime/helpers/esm/classCallCheck'; +import _createClass from '@babel/runtime/helpers/esm/createClass'; + +var A = /*#__PURE__*/function () { + function A() { + _classCallCheck(this, A); + } + + _createClass(A, [{ + key: "foo", + value: function foo() {} + }]); + + return A; +}(); + +new A().foo(); diff --git a/packages/father-build/src/fixtures/build/rollup-runtimeHelpers/expected/index.js b/packages/father-build/src/fixtures/build/rollup-runtimeHelpers/expected/index.js new file mode 100644 index 000000000..73d0a72df --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-runtimeHelpers/expected/index.js @@ -0,0 +1,38 @@ +'use strict'; + +function _classCallCheck(instance, Constructor) { + if (!(instance instanceof Constructor)) { + throw new TypeError("Cannot call a class as a function"); + } +} + +function _defineProperties(target, props) { + for (var i = 0; i < props.length; i++) { + var descriptor = props[i]; + descriptor.enumerable = descriptor.enumerable || false; + descriptor.configurable = true; + if ("value" in descriptor) descriptor.writable = true; + Object.defineProperty(target, descriptor.key, descriptor); + } +} + +function _createClass(Constructor, protoProps, staticProps) { + if (protoProps) _defineProperties(Constructor.prototype, protoProps); + if (staticProps) _defineProperties(Constructor, staticProps); + return Constructor; +} + +var A = /*#__PURE__*/function () { + function A() { + _classCallCheck(this, A); + } + + _createClass(A, [{ + key: "foo", + value: function foo() {} + }]); + + return A; +}(); + +new A().foo(); diff --git a/packages/father-build/src/fixtures/build/rollup-runtimeHelpers/package.json b/packages/father-build/src/fixtures/build/rollup-runtimeHelpers/package.json new file mode 100644 index 000000000..689bf9cfb --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-runtimeHelpers/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "@babel/runtime": "0.1.0" + } +} diff --git a/packages/father-build/src/fixtures/build/rollup-runtimeHelpers/src/index.js b/packages/father-build/src/fixtures/build/rollup-runtimeHelpers/src/index.js new file mode 100644 index 000000000..0cff149b2 --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-runtimeHelpers/src/index.js @@ -0,0 +1,6 @@ + +class A { + foo() {} +} + +(new A()).foo(); diff --git a/packages/father-build/src/fixtures/build/rollup-sass/.fatherrc.js b/packages/father-build/src/fixtures/build/rollup-sass/.fatherrc.js new file mode 100644 index 000000000..2f10183a3 --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-sass/.fatherrc.js @@ -0,0 +1,4 @@ + +export default { + esm: { type: 'rollup' }, +} diff --git a/packages/father-build/src/fixtures/build/rollup-sass/expected/index.esm.js b/packages/father-build/src/fixtures/build/rollup-sass/expected/index.esm.js new file mode 100644 index 000000000..040c989fc --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-sass/expected/index.esm.js @@ -0,0 +1,31 @@ +function styleInject(css, ref) { + if ( ref === void 0 ) ref = {}; + var insertAt = ref.insertAt; + + if (!css || typeof document === 'undefined') { return; } + + var head = document.head || document.getElementsByTagName('head')[0]; + var style = document.createElement('style'); + style.type = 'text/css'; + + if (insertAt === 'top') { + if (head.firstChild) { + head.insertBefore(style, head.firstChild); + } else { + head.appendChild(style); + } + } else { + head.appendChild(style); + } + + if (style.styleSheet) { + style.styleSheet.cssText = css; + } else { + style.appendChild(document.createTextNode(css)); + } +} + +var css = ".a {\n color: green; }\n"; +styleInject(css); + +console.log('foo'); diff --git a/packages/father-build/src/fixtures/build/rollup-sass/src/foo.scss b/packages/father-build/src/fixtures/build/rollup-sass/src/foo.scss new file mode 100644 index 000000000..ec0af869a --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-sass/src/foo.scss @@ -0,0 +1,2 @@ + +.a { color: green; } diff --git a/packages/father-build/src/fixtures/build/rollup-sass/src/index.js b/packages/father-build/src/fixtures/build/rollup-sass/src/index.js new file mode 100644 index 000000000..28c5d8eb5 --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-sass/src/index.js @@ -0,0 +1,3 @@ +import './foo.scss'; + +console.log('foo'); diff --git a/packages/father-build/src/fixtures/build/rollup-svgr/.umirc.library.js b/packages/father-build/src/fixtures/build/rollup-svgr/.umirc.library.js new file mode 100644 index 000000000..030fee174 --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-svgr/.umirc.library.js @@ -0,0 +1,3 @@ +export default { + esm: { type: 'rollup' }, +} diff --git a/packages/father-build/src/fixtures/build/rollup-svgr/dist/index.esm.js b/packages/father-build/src/fixtures/build/rollup-svgr/dist/index.esm.js new file mode 100644 index 000000000..9763ee347 --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-svgr/dist/index.esm.js @@ -0,0 +1,22 @@ +import React from 'react'; + +function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); } + +var _ref = /*#__PURE__*/React.createElement("defs", null, /*#__PURE__*/React.createElement("style", null)); + +var _ref2 = /*#__PURE__*/React.createElement("path", { + d: "M656 512h160c8.8 0 16-7.2 16-16v-96c0-8.8-7.2-16-16-16H656c-8.8 0-16 7.2-16 16v22H346V320h86c8.8 0 16-7.2 16-16v-96c0-8.8-7.2-16-16-16H208c-8.8 0-16 7.2-16 16v96c0 8.8 7.2 16 16 16h86v378c0 17.7 14.3 32 32 32h314v22c0 8.8 7.2 16 16 16h160c8.8 0 16-7.2 16-16v-96c0-8.8-7.2-16-16-16H656c-8.8 0-16 7.2-16 16v22H346V474h294v22c0 8.8 7.2 16 16 16z" +}); + +var SvgMenu = function SvgMenu(props) { + return /*#__PURE__*/React.createElement("svg", _extends({ + className: "menu_svg__icon", + viewBox: "0 0 1024 1024", + width: 200, + height: 200 + }, props), _ref, _ref2); +}; + +var svgUrl = "data:image/svg+xml,%3C%3Fxml%20version%3D%221.0%22%20standalone%3D%22no%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20t%3D%221558949630117%22%20class%3D%22icon%22%20style%3D%22%22%20viewBox%3D%220%200%201024%201024%22%20version%3D%221.1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20%20%20%20%20p-id%3D%2233994%22%20%20%20%20%20width%3D%22200%22%20height%3D%22200%22%3E%20%20%3Cdefs%3E%20%20%20%20%3Cstyle%20type%3D%22text%2Fcss%22%3E%3C%2Fstyle%3E%20%20%3C%2Fdefs%3E%20%20%3Cpath%20%20%20%20d%3D%22M656%20512h160c8.8%200%2016-7.2%2016-16v-96c0-8.8-7.2-16-16-16H656c-8.8%200-16%207.2-16%2016v22H346V320h86c8.8%200%2016-7.2%2016-16v-96c0-8.8-7.2-16-16-16H208c-8.8%200-16%207.2-16%2016v96c0%208.8%207.2%2016%2016%2016h86v378c0%2017.7%2014.3%2032%2032%2032h314v22c0%208.8%207.2%2016%2016%2016h160c8.8%200%2016-7.2%2016-16v-96c0-8.8-7.2-16-16-16H656c-8.8%200-16%207.2-16%2016v22H346V474h294v22c0%208.8%207.2%2016%2016%2016z%22%20%20%20%20p-id%3D%2233995%22%3E%3C%2Fpath%3E%3C%2Fsvg%3E"; + +console.log(svgUrl, /*#__PURE__*/React.createElement(SvgMenu, null)); diff --git a/packages/father-build/src/fixtures/build/rollup-svgr/expected/index.esm.js b/packages/father-build/src/fixtures/build/rollup-svgr/expected/index.esm.js new file mode 100644 index 000000000..9763ee347 --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-svgr/expected/index.esm.js @@ -0,0 +1,22 @@ +import React from 'react'; + +function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); } + +var _ref = /*#__PURE__*/React.createElement("defs", null, /*#__PURE__*/React.createElement("style", null)); + +var _ref2 = /*#__PURE__*/React.createElement("path", { + d: "M656 512h160c8.8 0 16-7.2 16-16v-96c0-8.8-7.2-16-16-16H656c-8.8 0-16 7.2-16 16v22H346V320h86c8.8 0 16-7.2 16-16v-96c0-8.8-7.2-16-16-16H208c-8.8 0-16 7.2-16 16v96c0 8.8 7.2 16 16 16h86v378c0 17.7 14.3 32 32 32h314v22c0 8.8 7.2 16 16 16h160c8.8 0 16-7.2 16-16v-96c0-8.8-7.2-16-16-16H656c-8.8 0-16 7.2-16 16v22H346V474h294v22c0 8.8 7.2 16 16 16z" +}); + +var SvgMenu = function SvgMenu(props) { + return /*#__PURE__*/React.createElement("svg", _extends({ + className: "menu_svg__icon", + viewBox: "0 0 1024 1024", + width: 200, + height: 200 + }, props), _ref, _ref2); +}; + +var svgUrl = "data:image/svg+xml,%3C%3Fxml%20version%3D%221.0%22%20standalone%3D%22no%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20t%3D%221558949630117%22%20class%3D%22icon%22%20style%3D%22%22%20viewBox%3D%220%200%201024%201024%22%20version%3D%221.1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20%20%20%20%20p-id%3D%2233994%22%20%20%20%20%20width%3D%22200%22%20height%3D%22200%22%3E%20%20%3Cdefs%3E%20%20%20%20%3Cstyle%20type%3D%22text%2Fcss%22%3E%3C%2Fstyle%3E%20%20%3C%2Fdefs%3E%20%20%3Cpath%20%20%20%20d%3D%22M656%20512h160c8.8%200%2016-7.2%2016-16v-96c0-8.8-7.2-16-16-16H656c-8.8%200-16%207.2-16%2016v22H346V320h86c8.8%200%2016-7.2%2016-16v-96c0-8.8-7.2-16-16-16H208c-8.8%200-16%207.2-16%2016v96c0%208.8%207.2%2016%2016%2016h86v378c0%2017.7%2014.3%2032%2032%2032h314v22c0%208.8%207.2%2016%2016%2016h160c8.8%200%2016-7.2%2016-16v-96c0-8.8-7.2-16-16-16H656c-8.8%200-16%207.2-16%2016v22H346V474h294v22c0%208.8%207.2%2016%2016%2016z%22%20%20%20%20p-id%3D%2233995%22%3E%3C%2Fpath%3E%3C%2Fsvg%3E"; + +console.log(svgUrl, /*#__PURE__*/React.createElement(SvgMenu, null)); diff --git a/packages/father-build/src/fixtures/build/rollup-svgr/package.json b/packages/father-build/src/fixtures/build/rollup-svgr/package.json new file mode 100644 index 000000000..55ef290f1 --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-svgr/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "react": "16" + } +} diff --git a/packages/father-build/src/fixtures/build/rollup-svgr/src/index.jsx b/packages/father-build/src/fixtures/build/rollup-svgr/src/index.jsx new file mode 100644 index 000000000..769d90a58 --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-svgr/src/index.jsx @@ -0,0 +1,3 @@ +import svgUrl, { ReactComponent as MenuComponent } from './menu.svg'; + +console.log(svgUrl, ); diff --git a/packages/father-build/src/fixtures/build/rollup-svgr/src/menu.svg b/packages/father-build/src/fixtures/build/rollup-svgr/src/menu.svg new file mode 100644 index 000000000..bd7bb1417 --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-svgr/src/menu.svg @@ -0,0 +1,12 @@ + + + + + + + diff --git a/packages/father-build/src/fixtures/build/rollup-typescript-declarationDir/.fatherrc.ts b/packages/father-build/src/fixtures/build/rollup-typescript-declarationDir/.fatherrc.ts new file mode 100644 index 000000000..35309e6f8 --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-typescript-declarationDir/.fatherrc.ts @@ -0,0 +1,7 @@ +export default { + cjs: 'rollup', + esm: 'rollup', + typescriptOpts: { + useTsconfigDeclarationDir: true, + }, +}; diff --git a/packages/father-build/src/fixtures/build/rollup-typescript-declarationDir/expected/index.esm.js b/packages/father-build/src/fixtures/build/rollup-typescript-declarationDir/expected/index.esm.js new file mode 100644 index 000000000..a0ca5bfdf --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-typescript-declarationDir/expected/index.esm.js @@ -0,0 +1,5 @@ +function foo(opts) { + return opts.foo ? 'foo' : 'bar'; +} + +export default foo; diff --git a/packages/father-build/src/fixtures/build/rollup-typescript-declarationDir/expected/index.js b/packages/father-build/src/fixtures/build/rollup-typescript-declarationDir/expected/index.js new file mode 100644 index 000000000..39a5ac989 --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-typescript-declarationDir/expected/index.js @@ -0,0 +1,7 @@ +'use strict'; + +function foo(opts) { + return opts.foo ? 'foo' : 'bar'; +} + +module.exports = foo; diff --git a/packages/father-build/src/fixtures/build/rollup-typescript-declarationDir/expected/type/index.d.ts b/packages/father-build/src/fixtures/build/rollup-typescript-declarationDir/expected/type/index.d.ts new file mode 100644 index 000000000..9eea9c0f1 --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-typescript-declarationDir/expected/type/index.d.ts @@ -0,0 +1,5 @@ +interface IOpts { + foo: boolean; +} +export default function foo(opts: IOpts): string; +export {}; diff --git a/packages/father-build/src/fixtures/build/rollup-typescript-declarationDir/src/index.ts b/packages/father-build/src/fixtures/build/rollup-typescript-declarationDir/src/index.ts new file mode 100644 index 000000000..3a0363ce2 --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-typescript-declarationDir/src/index.ts @@ -0,0 +1,7 @@ +interface IOpts { + foo: boolean; +} + +export default function foo(opts: IOpts): string { + return opts.foo ? 'foo' : 'bar'; +} diff --git a/packages/father-build/src/fixtures/build/rollup-typescript-declarationDir/tsconfig.json b/packages/father-build/src/fixtures/build/rollup-typescript-declarationDir/tsconfig.json new file mode 100644 index 000000000..59dd44386 --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-typescript-declarationDir/tsconfig.json @@ -0,0 +1,9 @@ +{ + "compilerOptions": { + "target": "esnext", + "moduleResolution": "node", + "jsx": "preserve", + "esModuleInterop": true, + "declarationDir": "dist/type" + } +} diff --git a/packages/father-build/src/fixtures/build/rollup-typescript-declarationDir/typings.d.ts b/packages/father-build/src/fixtures/build/rollup-typescript-declarationDir/typings.d.ts new file mode 100644 index 000000000..211708df8 --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-typescript-declarationDir/typings.d.ts @@ -0,0 +1,7 @@ +declare module '*.less' { + interface IClassNames { + [className: string]: string; + } + const classNames: IClassNames; + export = classNames; +} diff --git a/packages/father-build/src/fixtures/build/rollup-typescript/.fatherrc.ts b/packages/father-build/src/fixtures/build/rollup-typescript/.fatherrc.ts new file mode 100644 index 000000000..3e130204e --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-typescript/.fatherrc.ts @@ -0,0 +1,4 @@ +export default { + cjs: 'rollup', + esm: 'rollup', +}; diff --git a/packages/father-build/src/fixtures/build/rollup-typescript/expected/index.d.ts b/packages/father-build/src/fixtures/build/rollup-typescript/expected/index.d.ts new file mode 100644 index 000000000..9eea9c0f1 --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-typescript/expected/index.d.ts @@ -0,0 +1,5 @@ +interface IOpts { + foo: boolean; +} +export default function foo(opts: IOpts): string; +export {}; diff --git a/packages/father-build/src/fixtures/build/rollup-typescript/expected/index.esm.js b/packages/father-build/src/fixtures/build/rollup-typescript/expected/index.esm.js new file mode 100644 index 000000000..a0ca5bfdf --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-typescript/expected/index.esm.js @@ -0,0 +1,5 @@ +function foo(opts) { + return opts.foo ? 'foo' : 'bar'; +} + +export default foo; diff --git a/packages/father-build/src/fixtures/build/rollup-typescript/expected/index.js b/packages/father-build/src/fixtures/build/rollup-typescript/expected/index.js new file mode 100644 index 000000000..39a5ac989 --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-typescript/expected/index.js @@ -0,0 +1,7 @@ +'use strict'; + +function foo(opts) { + return opts.foo ? 'foo' : 'bar'; +} + +module.exports = foo; diff --git a/packages/father-build/src/fixtures/build/rollup-typescript/src/index.ts b/packages/father-build/src/fixtures/build/rollup-typescript/src/index.ts new file mode 100644 index 000000000..3a0363ce2 --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-typescript/src/index.ts @@ -0,0 +1,7 @@ +interface IOpts { + foo: boolean; +} + +export default function foo(opts: IOpts): string { + return opts.foo ? 'foo' : 'bar'; +} diff --git a/packages/father-build/src/fixtures/build/rollup-typescript/tsconfig.json b/packages/father-build/src/fixtures/build/rollup-typescript/tsconfig.json new file mode 100644 index 000000000..d322bf935 --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-typescript/tsconfig.json @@ -0,0 +1,8 @@ +{ + "compilerOptions": { + "target": "esnext", + "moduleResolution": "node", + "jsx": "preserve", + "esModuleInterop": true + } +} diff --git a/packages/father-build/src/fixtures/build/rollup-typescript/typings.d.ts b/packages/father-build/src/fixtures/build/rollup-typescript/typings.d.ts new file mode 100644 index 000000000..211708df8 --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-typescript/typings.d.ts @@ -0,0 +1,7 @@ +declare module '*.less' { + interface IClassNames { + [className: string]: string; + } + const classNames: IClassNames; + export = classNames; +} diff --git a/packages/father-build/src/fixtures/build/rollup-umd-auto-name/.fatherrc.js b/packages/father-build/src/fixtures/build/rollup-umd-auto-name/.fatherrc.js new file mode 100644 index 000000000..da054b287 --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-umd-auto-name/.fatherrc.js @@ -0,0 +1,6 @@ + +export default { + umd: { + minFile: false + }, +} diff --git a/packages/father-build/src/fixtures/build/rollup-umd-auto-name/expected/index.umd.js b/packages/father-build/src/fixtures/build/rollup-umd-auto-name/expected/index.umd.js new file mode 100644 index 000000000..c5481b27e --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-umd-auto-name/expected/index.umd.js @@ -0,0 +1,13 @@ +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : + typeof define === 'function' && define.amd ? define(factory) : + (global = global || self, global.fooBar = factory()); +}(this, (function () { 'use strict'; + + function index () { + return 'foo'; + } + + return index; + +}))); diff --git a/packages/father-build/src/fixtures/build/rollup-umd-auto-name/package.json b/packages/father-build/src/fixtures/build/rollup-umd-auto-name/package.json new file mode 100644 index 000000000..a02a5e12d --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-umd-auto-name/package.json @@ -0,0 +1,3 @@ +{ + "name": "@umi/foo-bar" +} diff --git a/packages/father-build/src/fixtures/build/rollup-umd-auto-name/src/index.js b/packages/father-build/src/fixtures/build/rollup-umd-auto-name/src/index.js new file mode 100644 index 000000000..8fcf68e1b --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-umd-auto-name/src/index.js @@ -0,0 +1,4 @@ + +export default function () { + return 'foo'; +} diff --git a/packages/father-build/src/fixtures/build/rollup-umd-css-extract-minFile/.fatherrc.js b/packages/father-build/src/fixtures/build/rollup-umd-css-extract-minFile/.fatherrc.js new file mode 100644 index 000000000..e1ea4fca3 --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-umd-css-extract-minFile/.fatherrc.js @@ -0,0 +1,5 @@ + +export default { + extractCSS: true, + umd: {}, +} diff --git a/packages/father-build/src/fixtures/build/rollup-umd-css-extract-minFile/expected/index.umd.css b/packages/father-build/src/fixtures/build/rollup-umd-css-extract-minFile/expected/index.umd.css new file mode 100644 index 000000000..ec0af869a --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-umd-css-extract-minFile/expected/index.umd.css @@ -0,0 +1,2 @@ + +.a { color: green; } diff --git a/packages/father-build/src/fixtures/build/rollup-umd-css-extract-minFile/expected/index.umd.js b/packages/father-build/src/fixtures/build/rollup-umd-css-extract-minFile/expected/index.umd.js new file mode 100644 index 000000000..62e3a909f --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-umd-css-extract-minFile/expected/index.umd.js @@ -0,0 +1,8 @@ +(function (factory) { + typeof define === 'function' && define.amd ? define(factory) : + factory(); +}((function () { 'use strict'; + + console.log('foo'); + +}))); diff --git a/packages/father-build/src/fixtures/build/rollup-umd-css-extract-minFile/expected/index.umd.min.css b/packages/father-build/src/fixtures/build/rollup-umd-css-extract-minFile/expected/index.umd.min.css new file mode 100644 index 000000000..de2080200 --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-umd-css-extract-minFile/expected/index.umd.min.css @@ -0,0 +1 @@ +.a{color:green} \ No newline at end of file diff --git a/packages/father-build/src/fixtures/build/rollup-umd-css-extract-minFile/expected/index.umd.min.js b/packages/father-build/src/fixtures/build/rollup-umd-css-extract-minFile/expected/index.umd.min.js new file mode 100644 index 000000000..e383c2dd0 --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-umd-css-extract-minFile/expected/index.umd.min.js @@ -0,0 +1 @@ +!function(n){"function"==typeof define&&define.amd?define(n):n()}((function(){"use strict";console.log("foo")})); diff --git a/packages/father-build/src/fixtures/build/rollup-umd-css-extract-minFile/src/foo.css b/packages/father-build/src/fixtures/build/rollup-umd-css-extract-minFile/src/foo.css new file mode 100644 index 000000000..ec0af869a --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-umd-css-extract-minFile/src/foo.css @@ -0,0 +1,2 @@ + +.a { color: green; } diff --git a/packages/father-build/src/fixtures/build/rollup-umd-css-extract-minFile/src/index.js b/packages/father-build/src/fixtures/build/rollup-umd-css-extract-minFile/src/index.js new file mode 100644 index 000000000..9a761d97a --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-umd-css-extract-minFile/src/index.js @@ -0,0 +1,3 @@ +import './foo.css'; + +console.log('foo'); diff --git a/packages/father-build/src/fixtures/build/rollup-umd-globals/.fatherrc.js b/packages/father-build/src/fixtures/build/rollup-umd-globals/.fatherrc.js new file mode 100644 index 000000000..44cc7c460 --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-umd-globals/.fatherrc.js @@ -0,0 +1,9 @@ + +export default { + umd: { + minFile: false, + globals: { + foo: '$', + }, + }, +} diff --git a/packages/father-build/src/fixtures/build/rollup-umd-globals/expected/index.umd.js b/packages/father-build/src/fixtures/build/rollup-umd-globals/expected/index.umd.js new file mode 100644 index 000000000..8f3a2241a --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-umd-globals/expected/index.umd.js @@ -0,0 +1,11 @@ +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? factory(require('foo')) : + typeof define === 'function' && define.amd ? define(['foo'], factory) : + (global = global || self, factory(global.$)); +}(this, (function (foo) { 'use strict'; + + foo = foo && foo.hasOwnProperty('default') ? foo['default'] : foo; + + foo.bar(); + +}))); diff --git a/packages/father-build/src/fixtures/build/rollup-umd-globals/package.json b/packages/father-build/src/fixtures/build/rollup-umd-globals/package.json new file mode 100644 index 000000000..7390bd3d2 --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-umd-globals/package.json @@ -0,0 +1,5 @@ +{ + "peerDependencies": { + "foo": "0.1.0" + } +} diff --git a/packages/father-build/src/fixtures/build/rollup-umd-globals/src/index.js b/packages/father-build/src/fixtures/build/rollup-umd-globals/src/index.js new file mode 100644 index 000000000..5a079bee1 --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-umd-globals/src/index.js @@ -0,0 +1,3 @@ +import foo from 'foo'; + +foo.bar(); diff --git a/packages/father-build/src/fixtures/build/rollup-umd-name/.fatherrc.js b/packages/father-build/src/fixtures/build/rollup-umd-name/.fatherrc.js new file mode 100644 index 000000000..be174297f --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-umd-name/.fatherrc.js @@ -0,0 +1,7 @@ + +export default { + umd: { + minFile: false, + name: 'foo', + }, +} diff --git a/packages/father-build/src/fixtures/build/rollup-umd-name/expected/index.umd.js b/packages/father-build/src/fixtures/build/rollup-umd-name/expected/index.umd.js new file mode 100644 index 000000000..a4c43fee5 --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-umd-name/expected/index.umd.js @@ -0,0 +1,13 @@ +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : + typeof define === 'function' && define.amd ? define(factory) : + (global = global || self, global.foo = factory()); +}(this, (function () { 'use strict'; + + function index () { + return 'foo'; + } + + return index; + +}))); diff --git a/packages/father-build/src/fixtures/build/rollup-umd-name/src/index.js b/packages/father-build/src/fixtures/build/rollup-umd-name/src/index.js new file mode 100644 index 000000000..8fcf68e1b --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-umd-name/src/index.js @@ -0,0 +1,4 @@ + +export default function () { + return 'foo'; +} diff --git a/packages/father-build/src/fixtures/build/rollup-umd-overrides/.fatherrc.js b/packages/father-build/src/fixtures/build/rollup-umd-overrides/.fatherrc.js new file mode 100644 index 000000000..242848d42 --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-umd-overrides/.fatherrc.js @@ -0,0 +1,18 @@ + +export default { + entry: [ + 'src/index.js', + 'src/fetch.js', + ], + umd: { + minFile: false, + name: 'foo', + }, + overridesByEntry: { + 'src/fetch.js': { + umd: { + name: 'foo.fetch', + }, + }, + }, +} diff --git a/packages/father-build/src/fixtures/build/rollup-umd-overrides/expected/fetch.umd.js b/packages/father-build/src/fixtures/build/rollup-umd-overrides/expected/fetch.umd.js new file mode 100644 index 000000000..099c46c5b --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-umd-overrides/expected/fetch.umd.js @@ -0,0 +1,13 @@ +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : + typeof define === 'function' && define.amd ? define(factory) : + (global = global || self, (global.foo = global.foo || {}, global.foo.fetch = factory())); +}(this, (function () { 'use strict'; + + function fetch () { + return 'foo.fetch'; + } + + return fetch; + +}))); diff --git a/packages/father-build/src/fixtures/build/rollup-umd-overrides/expected/index.umd.js b/packages/father-build/src/fixtures/build/rollup-umd-overrides/expected/index.umd.js new file mode 100644 index 000000000..a4c43fee5 --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-umd-overrides/expected/index.umd.js @@ -0,0 +1,13 @@ +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : + typeof define === 'function' && define.amd ? define(factory) : + (global = global || self, global.foo = factory()); +}(this, (function () { 'use strict'; + + function index () { + return 'foo'; + } + + return index; + +}))); diff --git a/packages/father-build/src/fixtures/build/rollup-umd-overrides/src/fetch.js b/packages/father-build/src/fixtures/build/rollup-umd-overrides/src/fetch.js new file mode 100644 index 000000000..3fdd6c676 --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-umd-overrides/src/fetch.js @@ -0,0 +1,4 @@ + +export default function () { + return 'foo.fetch'; +} diff --git a/packages/father-build/src/fixtures/build/rollup-umd-overrides/src/index.js b/packages/father-build/src/fixtures/build/rollup-umd-overrides/src/index.js new file mode 100644 index 000000000..8fcf68e1b --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-umd-overrides/src/index.js @@ -0,0 +1,4 @@ + +export default function () { + return 'foo'; +} diff --git a/packages/father-build/src/fixtures/build/rollup-umd-sourcemap/.fatherrc.js b/packages/father-build/src/fixtures/build/rollup-umd-sourcemap/.fatherrc.js new file mode 100644 index 000000000..ff17b2079 --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-umd-sourcemap/.fatherrc.js @@ -0,0 +1,8 @@ + +export default { + umd: { + minFile: false, + name: 'foo', + sourcemap: true, + }, +} diff --git a/packages/father-build/src/fixtures/build/rollup-umd-sourcemap/expected/index.umd.js b/packages/father-build/src/fixtures/build/rollup-umd-sourcemap/expected/index.umd.js new file mode 100644 index 000000000..68394f083 --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-umd-sourcemap/expected/index.umd.js @@ -0,0 +1,14 @@ +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : + typeof define === 'function' && define.amd ? define(factory) : + (global = global || self, global.foo = factory()); +}(this, (function () { 'use strict'; + + function index () { + return 'foo'; + } + + return index; + +}))); +//# sourceMappingURL=index.umd.js.map diff --git a/packages/father-build/src/fixtures/build/rollup-umd-sourcemap/expected/index.umd.js.map b/packages/father-build/src/fixtures/build/rollup-umd-sourcemap/expected/index.umd.js.map new file mode 100644 index 000000000..bf6d50af4 --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-umd-sourcemap/expected/index.umd.js.map @@ -0,0 +1 @@ +{"version":3,"file":"index.umd.js","sources":["../src/index.js"],"sourcesContent":["\nexport default function () {\n return 'foo';\n}\n"],"names":[],"mappings":";;;;;;EACe,kBAAY;EACzB,SAAO,KAAP;EACD;;;;;;;;"} \ No newline at end of file diff --git a/packages/father-build/src/fixtures/build/rollup-umd-sourcemap/src/index.js b/packages/father-build/src/fixtures/build/rollup-umd-sourcemap/src/index.js new file mode 100644 index 000000000..8fcf68e1b --- /dev/null +++ b/packages/father-build/src/fixtures/build/rollup-umd-sourcemap/src/index.js @@ -0,0 +1,4 @@ + +export default function () { + return 'foo'; +} diff --git a/packages/father-build/src/fixtures/build/target-node-version/.fatherrc.js b/packages/father-build/src/fixtures/build/target-node-version/.fatherrc.js new file mode 100644 index 000000000..41a0b5982 --- /dev/null +++ b/packages/father-build/src/fixtures/build/target-node-version/.fatherrc.js @@ -0,0 +1,7 @@ + +export default { + cjs: 'babel', + esm: 'babel', + target: 'node', + nodeVersion: 8, +} diff --git a/packages/father-build/src/fixtures/build/target-node-version/expected/es/index.js b/packages/father-build/src/fixtures/build/target-node-version/expected/es/index.js new file mode 100644 index 000000000..ae6a5bdb2 --- /dev/null +++ b/packages/father-build/src/fixtures/build/target-node-version/expected/es/index.js @@ -0,0 +1,4 @@ +const foo = 'bar'; +export default async function () { + return foo; +} \ No newline at end of file diff --git a/packages/father-build/src/fixtures/build/target-node-version/src/index.js b/packages/father-build/src/fixtures/build/target-node-version/src/index.js new file mode 100644 index 000000000..8ab0b3a2d --- /dev/null +++ b/packages/father-build/src/fixtures/build/target-node-version/src/index.js @@ -0,0 +1,6 @@ + +const foo = 'bar'; + +export default async function() { + return foo; +} diff --git a/packages/father-build/src/fixtures/build/target-node/.fatherrc.js b/packages/father-build/src/fixtures/build/target-node/.fatherrc.js new file mode 100644 index 000000000..a56516e62 --- /dev/null +++ b/packages/father-build/src/fixtures/build/target-node/.fatherrc.js @@ -0,0 +1,6 @@ + +export default { + cjs: 'rollup', + esm: 'rollup', + target: 'node', +} diff --git a/packages/father-build/src/fixtures/build/target-node/expected/index.esm.js b/packages/father-build/src/fixtures/build/target-node/expected/index.esm.js new file mode 100644 index 000000000..3199bfb84 --- /dev/null +++ b/packages/father-build/src/fixtures/build/target-node/expected/index.esm.js @@ -0,0 +1,6 @@ +var foo = 'bar'; +function index () { + return foo; +} + +export default index; diff --git a/packages/father-build/src/fixtures/build/target-node/expected/index.js b/packages/father-build/src/fixtures/build/target-node/expected/index.js new file mode 100644 index 000000000..b1ccfd8a2 --- /dev/null +++ b/packages/father-build/src/fixtures/build/target-node/expected/index.js @@ -0,0 +1,8 @@ +'use strict'; + +const foo = 'bar'; +function index () { + return foo; +} + +module.exports = index; diff --git a/packages/father-build/src/fixtures/build/target-node/src/index.js b/packages/father-build/src/fixtures/build/target-node/src/index.js new file mode 100644 index 000000000..1354e696b --- /dev/null +++ b/packages/father-build/src/fixtures/build/target-node/src/index.js @@ -0,0 +1,6 @@ + +const foo = 'bar'; + +export default function() { + return foo; +} diff --git a/packages/father-build/src/getBabelConfig.ts b/packages/father-build/src/getBabelConfig.ts new file mode 100644 index 000000000..c1df15136 --- /dev/null +++ b/packages/father-build/src/getBabelConfig.ts @@ -0,0 +1,95 @@ +import { extname } from 'path'; +import { ModuleFormat } from 'rollup'; + +interface IGetBabelConfigOpts { + target: 'browser' | 'node'; + type?: ModuleFormat; + typescript?: boolean; + runtimeHelpers?: boolean; + filePath?: string; + browserFiles?: { + [value: string]: any; + }; + nodeVersion?: number; + nodeFiles?: { + [value: string]: any; + }; + lazy?: boolean; + lessInBabelMode?: boolean|{ + paths?: any[]; + plugins?: any[]; + }; +} + +function transformImportLess2Css() { + return { + name: 'transform-import-less-to-css', + visitor: { + ImportDeclaration(path, source) { + const re = /\.less$/; + if(re.test(path.node.source.value)){ + path.node.source.value = path.node.source.value.replace(re, '.css'); + } + } + } + } +} + +export default function(opts: IGetBabelConfigOpts) { + const { target, typescript, type, runtimeHelpers, filePath, browserFiles, nodeFiles, nodeVersion, lazy, lessInBabelMode } = opts; + let isBrowser = target === 'browser'; + // rollup 场景下不会传入 filePath + if (filePath) { + if (extname(filePath) === '.tsx' || extname(filePath) === '.jsx') { + isBrowser = true; + } else { + if (isBrowser) { + if (nodeFiles.includes(filePath)) isBrowser = false; + } else { + if (browserFiles.includes(filePath)) isBrowser = true; + } + } + } + const targets = isBrowser ? { browsers: ['last 2 versions', 'IE 10'] } : { node: nodeVersion || 6 }; + + return { + opts: { + presets: [ + ...(typescript ? [require.resolve('@babel/preset-typescript')] : []), + [require.resolve('@babel/preset-env'), { + targets, + modules: type === 'esm' ? false : 'auto' + }], + ...(isBrowser ? [require.resolve('@babel/preset-react')] : []), + ], + plugins: [ + ...((type === 'cjs' && lazy && !isBrowser) + ? [[require.resolve('@babel/plugin-transform-modules-commonjs'), { + lazy: true, + }]] + : []), + ...(lessInBabelMode ? [transformImportLess2Css] : []), + ...(isBrowser ? require.resolve('babel-plugin-react-require') : []), + require.resolve('@babel/plugin-syntax-dynamic-import'), + require.resolve('@babel/plugin-proposal-export-default-from'), + require.resolve('@babel/plugin-proposal-export-namespace-from'), + require.resolve('@babel/plugin-proposal-do-expressions'), + require.resolve('@babel/plugin-proposal-nullish-coalescing-operator'), + require.resolve('@babel/plugin-proposal-optional-chaining'), + [require.resolve('@babel/plugin-proposal-decorators'), { legacy: true }], + [require.resolve('@babel/plugin-proposal-class-properties'), { loose: true }], + ...(runtimeHelpers + ? [[require.resolve('@babel/plugin-transform-runtime'), { + useESModules: isBrowser && (type === 'esm'), + version: require('@babel/runtime/package.json').version, + }]] + : []), + ...(process.env.COVERAGE + ? [require.resolve('babel-plugin-istanbul')] + : [] + ) + ], + }, + isBrowser, + }; +} diff --git a/packages/father-build/src/getRollupConfig.ts b/packages/father-build/src/getRollupConfig.ts new file mode 100644 index 000000000..e0ef34202 --- /dev/null +++ b/packages/father-build/src/getRollupConfig.ts @@ -0,0 +1,317 @@ +import { basename, extname, join } from 'path'; +import { terser } from 'rollup-plugin-terser'; +import babel from 'rollup-plugin-babel'; +import replace from 'rollup-plugin-replace'; +import json from 'rollup-plugin-json'; +import nodeResolve from 'rollup-plugin-node-resolve'; +import typescript2 from 'rollup-plugin-typescript2'; +import commonjs from 'rollup-plugin-commonjs'; +import postcss from 'rollup-plugin-postcss-umi'; +import inject from 'rollup-plugin-inject'; +import { ModuleFormat, RollupOptions } from 'rollup'; +import { camelCase } from 'lodash'; +import tempDir from 'temp-dir'; +import autoprefixer from 'autoprefixer'; +import NpmImport from 'less-plugin-npm-import'; +import svgr from '@svgr/rollup'; +import url from 'rollup-plugin-url'; +import getBabelConfig from './getBabelConfig'; +import { IBundleOptions } from './types'; + +interface IGetRollupConfigOpts { + cwd: string; + entry: string; + type: ModuleFormat; + importLibToEs?: boolean; + bundleOpts: IBundleOptions; +} + +interface IPkg { + dependencies?: Object; + peerDependencies?: Object; + name?: string; +} + +export default function(opts: IGetRollupConfigOpts): RollupOptions[] { + const { type, entry, cwd, importLibToEs, bundleOpts } = opts; + const { + umd, + esm, + cjs, + file, + target = 'browser', + extractCSS = false, + injectCSS = true, + cssModules: modules, + extraPostCSSPlugins = [], + extraBabelPresets = [], + extraBabelPlugins = [], + extraRollupPlugins = [], + autoprefixer: autoprefixerOpts, + include = /node_modules/, + namedExports, + runtimeHelpers: runtimeHelpersOpts, + replace: replaceOpts, + inject: injectOpts, + extraExternals = [], + externalsExclude = [], + nodeVersion, + typescriptOpts, + nodeResolveOpts = {}, + disableTypeCheck, + lessInRollupMode = {}, + sassInRollupMode = {}, + } = bundleOpts; + const entryExt = extname(entry); + const name = file || basename(entry, entryExt); + const isTypeScript = entryExt === '.ts' || entryExt === '.tsx'; + const extensions = ['.js', '.jsx', '.ts', '.tsx', '.es6', '.es', '.mjs']; + + let pkg = {} as IPkg; + try { + pkg = require(join(cwd, 'package.json')); // eslint-disable-line + } catch (e) {} + + // cjs 不给浏览器用,所以无需 runtimeHelpers + const runtimeHelpers = type === 'cjs' ? false : runtimeHelpersOpts; + const babelOpts = { + ...(getBabelConfig({ + type, + target: type === 'esm' ? 'browser' : target, + // watch 模式下有几率走的 babel?原因未知。 + // ref: https://github.com/umijs/father/issues/158 + typescript: true, + runtimeHelpers, + nodeVersion, + }).opts), + runtimeHelpers, + exclude: /\/node_modules\//, + babelrc: false, + // ref: https://github.com/rollup/rollup-plugin-babel#usage + extensions, + }; + if (importLibToEs && type === 'esm') { + babelOpts.plugins.push(require.resolve('../lib/importLibToEs')); + } + babelOpts.presets.push(...extraBabelPresets); + babelOpts.plugins.push(...extraBabelPlugins); + + // rollup configs + const input = join(cwd, entry); + const format = type; + + // ref: https://rollupjs.org/guide/en#external + // 潜在问题:引用包的子文件时会报 warning,比如 @babel/runtime/helpers/esm/createClass + // 解决方案:可以用 function 处理 + const external = [ + ...Object.keys(pkg.dependencies || {}), + ...Object.keys(pkg.peerDependencies || {}), + ...extraExternals, + ]; + // umd 只要 external peerDependencies + const externalPeerDeps = [ + ...Object.keys(pkg.peerDependencies || {}), + ...extraExternals, + ]; + + function getPkgNameByid(id) { + const splitted = id.split('/'); + // @ 和 @tmp 是为了兼容 umi 的逻辑 + if (id.charAt(0) === '@' && splitted[0] !== '@' && splitted[0] !== '@tmp') { + return splitted + .slice(0, 2) + .join('/'); + } else { + return id.split('/')[0]; + } + } + + function testExternal(pkgs, excludes, id) { + if (excludes.includes(id)) { + return false; + } + return pkgs.includes(getPkgNameByid(id)); + } + + const terserOpts = { + compress: { + pure_getters: true, + unsafe: true, + unsafe_comps: true, + warnings: false, + }, + }; + + function getPlugins(opts = {} as { minCSS: boolean; }) { + const { minCSS } = opts; + return [ + url(), + svgr(), + postcss({ + extract: extractCSS, + inject: injectCSS, + modules, + minimize: !!minCSS, + use: [ + [ + 'less', + { + plugins: [new NpmImport({ prefix: '~' })], + javascriptEnabled: true, + ...lessInRollupMode, + }, + ], + [ + 'sass', + { + ...sassInRollupMode, + }, + ], + ], + plugins: [autoprefixer(autoprefixerOpts), ...extraPostCSSPlugins], + }), + ...(injectOpts ? [inject(injectOpts)] : []), + ...(replaceOpts && Object.keys(replaceOpts || {}).length ? [replace(replaceOpts)] : []), + nodeResolve({ + mainFields: ['module', 'jsnext:main', 'main'], + extensions, + ...nodeResolveOpts, + }), + ...(isTypeScript + ? [ + typescript2({ + cwd, + // @see https://github.com/ezolenko/rollup-plugin-typescript2/issues/105 + objectHashIgnoreUnknownHack: true, + // @see https://github.com/umijs/father/issues/61#issuecomment-544822774 + clean: true, + cacheRoot: `${tempDir}/.rollup_plugin_typescript2_cache`, + // TODO: 支持往上找 tsconfig.json + // 比如 lerna 的场景不需要每个 package 有个 tsconfig.json + tsconfig: join(cwd, 'tsconfig.json'), + tsconfigDefaults: { + compilerOptions: { + // Generate declaration files by default + declaration: true, + }, + }, + tsconfigOverride: { + compilerOptions: { + // Support dynamic import + target: 'esnext', + }, + }, + check: !disableTypeCheck, + ...(typescriptOpts || {}), + }), + ] + : []), + babel(babelOpts), + json(), + ...(extraRollupPlugins || []), + ]; + } + + switch (type) { + case 'esm': + return [ + { + input, + output: { + format, + file: join(cwd, `dist/${(esm && (esm as any).file) || `${name}.esm`}.js`), + }, + plugins: [...getPlugins(), ...(esm && (esm as any).minify ? [terser(terserOpts)] : [])], + external: testExternal.bind(null, external, externalsExclude), + }, + ...(esm && (esm as any).mjs + ? [ + { + input, + output: { + format, + file: join(cwd, `dist/${(esm && (esm as any).file) || `${name}`}.mjs`), + }, + plugins: [ + ...getPlugins(), + replace({ + 'process.env.NODE_ENV': JSON.stringify('production'), + }), + terser(terserOpts), + ], + external: testExternal.bind(null, externalPeerDeps, externalsExclude), + }, + ] + : []), + ]; + + case 'cjs': + return [ + { + input, + output: { + format, + file: join(cwd, `dist/${(cjs && (cjs as any).file) || name}.js`), + }, + plugins: [...getPlugins(), ...(cjs && (cjs as any).minify ? [terser(terserOpts)] : [])], + external: testExternal.bind(null, external, externalsExclude), + }, + ]; + + case 'umd': + // Add umd related plugins + const extraUmdPlugins = [ + commonjs({ + include, + namedExports, + }), + ]; + + return [ + { + input, + output: { + format, + sourcemap: umd && umd.sourcemap, + file: join(cwd, `dist/${(umd && umd.file) || `${name}.umd`}.js`), + globals: umd && umd.globals, + name: (umd && umd.name) || (pkg.name && camelCase(basename(pkg.name))), + }, + plugins: [ + ...getPlugins(), + ...extraUmdPlugins, + replace({ + 'process.env.NODE_ENV': JSON.stringify('development'), + }), + ], + external: testExternal.bind(null, externalPeerDeps, externalsExclude), + }, + ...(umd && umd.minFile === false + ? [] + : [ + { + input, + output: { + format, + sourcemap: umd && umd.sourcemap, + file: join(cwd, `dist/${(umd && umd.file) || `${name}.umd`}.min.js`), + globals: umd && umd.globals, + name: (umd && umd.name) || (pkg.name && camelCase(basename(pkg.name))), + }, + plugins: [ + ...getPlugins({ minCSS: true }), + ...extraUmdPlugins, + replace({ + 'process.env.NODE_ENV': JSON.stringify('production'), + }), + terser(terserOpts), + ], + external: testExternal.bind(null, externalPeerDeps, externalsExclude), + }, + ]), + ]; + + default: + throw new Error(`Unsupported type ${type}`); + } +} diff --git a/packages/father-build/src/getUserConfig.ts b/packages/father-build/src/getUserConfig.ts new file mode 100644 index 000000000..440edcad5 --- /dev/null +++ b/packages/father-build/src/getUserConfig.ts @@ -0,0 +1,58 @@ +import AJV from 'ajv'; +import slash from 'slash2'; +import { relative } from 'path'; +import signale from 'signale'; +import schema from './schema'; +import { getExistFile } from './utils'; +import { IBundleOptions } from './types'; + +function testDefault(obj) { + return obj.default || obj; +} + +export const CONFIG_FILES = [ + '.fatherrc.js', + '.fatherrc.jsx', + '.fatherrc.ts', + '.fatherrc.tsx', + '.umirc.library.js', + '.umirc.library.jsx', + '.umirc.library.ts', + '.umirc.library.tsx', +]; + +export default function({ cwd }): IBundleOptions { + const configFile = getExistFile({ + cwd, + files: CONFIG_FILES, + returnRelative: false, + }); + + if (configFile) { + if (configFile.includes('.umirc.library.')) { + signale.warn(`.umirc.library.js is deprecated, please use .fatherrc.js instead.`); + } + + const userConfig = testDefault(require(configFile)); // eslint-disable-line + const userConfigs = Array.isArray(userConfig) ? userConfig : [userConfig]; + userConfigs.forEach(userConfig => { + const ajv = new AJV({ allErrors: true }); + const isValid = ajv.validate(schema, userConfig); + if (!isValid) { + const errors = ajv.errors.map(({ dataPath, message }, index) => { + return `${index + 1}. ${dataPath}${dataPath ? ' ' : ''}${message}`; + }); + throw new Error( + ` +Invalid options in ${slash(relative(cwd, configFile))} + +${errors.join('\n')} +`.trim(), + ); + } + }); + return userConfig; + } else { + return {}; + } +} diff --git a/packages/father-build/src/importLibToEs.js b/packages/father-build/src/importLibToEs.js new file mode 100644 index 000000000..e7d278a64 --- /dev/null +++ b/packages/father-build/src/importLibToEs.js @@ -0,0 +1,26 @@ +import { join, dirname } from 'path'; +import fs from 'fs'; + +const cwd = process.cwd(); + +function replacePath(path) { + if (path.node.source && /\/lib\//.test(path.node.source.value)) { + const esModule = path.node.source.value.replace('/lib/', '/es/'); + const esPath = dirname(join(cwd, `node_modules/${esModule}`)); + if (fs.existsSync(esPath)) { + console.log(`[es build] replace ${path.node.source.value} with ${esModule}`); + path.node.source.value = esModule; + } + } +} + +function replaceLib() { + return { + visitor: { + ImportDeclaration: replacePath, + ExportNamedDeclaration: replacePath, + }, + }; +} + +export default replaceLib; diff --git a/packages/father-build/src/index.ts b/packages/father-build/src/index.ts new file mode 100644 index 000000000..1bff2b7bf --- /dev/null +++ b/packages/father-build/src/index.ts @@ -0,0 +1,3 @@ + +import build from './build'; +export default build; diff --git a/packages/father-build/src/normalizeBundleOpts.test.ts b/packages/father-build/src/normalizeBundleOpts.test.ts new file mode 100644 index 000000000..54bfdbe23 --- /dev/null +++ b/packages/father-build/src/normalizeBundleOpts.test.ts @@ -0,0 +1,53 @@ +import normalizeBundleOpts from './normalizeBundleOpts'; + +test('normal', () => { + expect( + normalizeBundleOpts('a', { + umd: { name: 'foo' }, + overridesByEntry: { + a: { umd: { name: 'bar' } }, + }, + }), + ).toEqual({ + umd: { name: 'bar' }, + }); +}); + +test('ignore ./ prefix in entry', () => { + expect( + normalizeBundleOpts('./a', { + umd: { name: 'foo' }, + overridesByEntry: { + a: { umd: { name: 'bar' } }, + }, + }), + ).toEqual({ + umd: { name: 'bar' }, + }); +}); + +test('ignore ./ prefix in overridesByEntry', () => { + expect( + normalizeBundleOpts('a', { + umd: { name: 'foo' }, + overridesByEntry: { + './a': { umd: { name: 'bar' } }, + }, + }), + ).toEqual({ + umd: { name: 'bar' }, + }); +}); + +test('deep merge', () => { + expect( + normalizeBundleOpts('a', { + umd: { minFile: false, name: 'foo' }, + overridesByEntry: { + a: { umd: { name: 'bar' } }, + }, + }), + ).toEqual({ + umd: { minFile: false, name: 'bar' }, + }); +}); diff --git a/packages/father-build/src/normalizeBundleOpts.ts b/packages/father-build/src/normalizeBundleOpts.ts new file mode 100644 index 000000000..1767ff9ce --- /dev/null +++ b/packages/father-build/src/normalizeBundleOpts.ts @@ -0,0 +1,24 @@ +import { cloneDeep, merge } from 'lodash'; +import { IBundleOptions } from './types'; + +function stripDotSlashPrefix(path) { + return path.replace(/^\.\//, ''); +} + +export default function(entry: string, opts: IBundleOptions): IBundleOptions { + let clone = cloneDeep(opts); + const stripedEntry = stripDotSlashPrefix(entry); + if (clone.overridesByEntry) { + Object.keys(clone.overridesByEntry).forEach(key => { + const stripedKey = stripDotSlashPrefix(key); + if (stripedKey !== key) { + clone.overridesByEntry[stripedKey] = clone.overridesByEntry[key]; + } + }); + if (clone.overridesByEntry[stripedEntry]) { + clone = merge(clone, clone.overridesByEntry[stripedEntry]); + } + delete clone.overridesByEntry; + } + return clone; +} diff --git a/packages/father-build/src/randomColor.ts b/packages/father-build/src/randomColor.ts new file mode 100644 index 000000000..bf44d2527 --- /dev/null +++ b/packages/father-build/src/randomColor.ts @@ -0,0 +1,34 @@ +import chalk from 'chalk'; + +const colors = [ + 'red', + 'green', + 'yellow', + 'blue', + 'magenta', + 'cyan', + 'gray', + 'redBright', + 'greenBright', + 'yellowBright', + 'blueBright', + 'magentaBright', + 'cyanBright', +]; + +let index = 0; +const cache = {}; + +export default function (pkg) { + if (!cache[pkg]) { + const color = colors[index]; + let str = chalk[color].bold(pkg); + cache[pkg] = str; + if (index === colors.length - 1) { + index = 0; + } else { + index += 1; + } + } + return cache[pkg]; +} diff --git a/packages/father-build/src/registerBabel.ts b/packages/father-build/src/registerBabel.ts new file mode 100644 index 000000000..b2db93e80 --- /dev/null +++ b/packages/father-build/src/registerBabel.ts @@ -0,0 +1,23 @@ +import { join } from 'path'; +import slash from 'slash2'; +import getBabelConfig from './getBabelConfig'; + +interface IRegisterBabelOpts { + cwd: string; + only: string[]; +} + +export default function(opts: IRegisterBabelOpts) { + const { cwd, only } = opts; + const { opts: babelConfig } = getBabelConfig({ + target: 'node', + typescript: true, + }); + require('@babel/register')({ + ...babelConfig, + extensions: ['.es6', '.es', '.jsx', '.js', '.mjs', '.ts', '.tsx'], + only: only.map(file => slash(join(cwd, file))), + babelrc: false, + cache: false, + }); +} diff --git a/packages/father-build/src/rollup.ts b/packages/father-build/src/rollup.ts new file mode 100644 index 000000000..de2315bd6 --- /dev/null +++ b/packages/father-build/src/rollup.ts @@ -0,0 +1,62 @@ +import { ModuleFormat, rollup, watch } from 'rollup'; +import signale from 'signale'; +import getRollupConfig from './getRollupConfig'; +import { IBundleOptions } from './types'; +import normalizeBundleOpts from './normalizeBundleOpts'; + +interface IRollupOpts { + cwd: string; + entry: string | string[]; + type: ModuleFormat; + log: (string) => void; + bundleOpts: IBundleOptions; + watch?: boolean; + importLibToEs?: boolean; +} + +async function build(entry: string, opts: IRollupOpts) { + const { cwd, type, log, bundleOpts, importLibToEs } = opts; + const rollupConfigs = getRollupConfig({ + cwd, + type, + entry, + importLibToEs, + bundleOpts: normalizeBundleOpts(entry, bundleOpts), + }); + + for (const rollupConfig of rollupConfigs) { + if (opts.watch) { + const watcher = watch([ + { + ...rollupConfig, + watch: {}, + }, + ]); + watcher.on('event', event => { + if (event.error) { + signale.error(event.error); + } else if (event.code === 'START') { + log(`[${type}] Rebuild since file changed`); + } + }); + process.once('SIGINT', () => { + watcher.close(); + }); + } else { + const { output, ...input } = rollupConfig; + const bundle = await rollup(input); // eslint-disable-line + await bundle.write(output as any); // eslint-disable-line + } + } +} + +export default async function(opts: IRollupOpts) { + if (Array.isArray(opts.entry)) { + const { entry: entries } = opts; + for (const entry of entries) { + await build(entry, opts); + } + } else { + await build(opts.entry, opts); + } +} diff --git a/packages/father-build/src/schema.test.ts b/packages/father-build/src/schema.test.ts new file mode 100644 index 000000000..75bca1b83 --- /dev/null +++ b/packages/father-build/src/schema.test.ts @@ -0,0 +1,39 @@ +import AJV from 'ajv'; +import schema from './schema'; + +const ajv = new AJV(); + +const successValidates = { + entry: ['a', ['a']], + file: ['a'], + esm: [false, true, { type: 'rollup' }, { type: 'babel' }, { file: 'a' }, { mjs: true }], + cjs: [false, true, { type: 'rollup' }, { type: 'babel' }, { file: 'a' }], + umd: [{ globals: {} }, { file: 'a' }, { name: 'a' }, { minFile: false }, { minFile: true }, { sourcemap: true }], + extraBabelPlugins: [[]], + extraBabelPresets: [[]], + extraPostCSSPlugins: [[]], + lessInRollupMode: [{}], + cssModules: [true, false, {}], + autoprefixer: [{}], + include: ['node_modules', /node_modules/], + namedExports: [{}], + nodeResolveOpts: [{}], + runtimeHelpers: [true, false], + target: ['node', 'browser'], + overridesByEntry: [{}], + doc: [{}], + typescriptOpts: [{}], + pkgs: [[]], +}; + +Object.keys(successValidates).forEach(key => { + test(key, () => { + successValidates[key].forEach(item => { + expect( + ajv.validate(schema, { + [key]: item, + }), + ).toEqual(true); + }); + }); +}); diff --git a/packages/father-build/src/schema.ts b/packages/father-build/src/schema.ts new file mode 100644 index 000000000..692e829d9 --- /dev/null +++ b/packages/father-build/src/schema.ts @@ -0,0 +1,170 @@ +const noEmptyStr = { type: 'string', minLength: 1 }; + +export default { + type: 'object', + additionalProperties: false, + properties: { + entry: { + oneOf: [noEmptyStr, { type: 'array', items: noEmptyStr }], + }, + file: { type: 'string' }, + esm: { + oneOf: [ + noEmptyStr, + { type: 'boolean' }, + { + type: 'object', + additionalProperties: false, + properties: { + type: { + type: 'string', + pattern: '^(rollup|babel)$', + }, + file: noEmptyStr, + mjs: { type: 'boolean' }, + minify: { type: 'boolean' }, + importLibToEs: { + type: 'boolean', + }, + }, + }, + ], + }, + cjs: { + oneOf: [ + noEmptyStr, + { type: 'boolean' }, + { + type: 'object', + additionalProperties: false, + properties: { + type: { + type: 'string', + pattern: '^(rollup|babel)$', + }, + file: noEmptyStr, + minify: { type: 'boolean' }, + lazy: { type: 'boolean' }, + }, + }, + ], + }, + umd: { + oneOf: [ + { type: 'boolean' }, + { + type: 'object', + additionalProperties: false, + properties: { + globals: { type: 'object' }, + file: noEmptyStr, + name: noEmptyStr, + minFile: { type: 'boolean' }, + sourcemap: { + oneOf: [ + { type: 'boolean' }, + { type: 'string', pattern: '^(inline|hidden)$', }, + ] + }, + }, + }, + ], + }, + extraBabelPlugins: { + type: 'array', + }, + extraBabelPresets: { + type: 'array', + }, + extraPostCSSPlugins: { + type: 'array', + }, + extraRollupPlugins: { + type: 'array', + }, + extraExternals: { + type: 'array', + }, + externalsExclude: { + type: 'array', + }, + cssModules: { + oneOf: [{ type: 'boolean' }, { type: 'object' }], + }, + extractCSS: { + type: 'boolean', + }, + injectCSS: { + type: 'boolean', + }, + autoprefixer: { + type: 'object', + }, + include: { + oneOf: [ + { type: 'string' }, + { type: 'object' } + ] + }, + namedExports: { + type: 'object', + }, + runtimeHelpers: { + type: 'boolean', + }, + overridesByEntry: { + type: 'object', + }, + nodeResolveOpts: { + type: 'object', + }, + target: noEmptyStr, + doc: { + type: 'object', + }, + replace: { + type: 'object', + }, + inject: { + type: 'object', + }, + lessInRollupMode: { + type: 'object' + }, + sassInRollupMode: { + type: 'object' + }, + lessInBabelMode: { + oneOf: [ + { type: 'boolean' }, + { type: 'object' }, + ], + }, + browserFiles: { + type: 'array', + }, + nodeFiles: { + type: 'array', + }, + nodeVersion: { + type: 'number', + }, + disableTypeCheck: { + type: 'boolean', + }, + preCommit: { + type: 'object', + additionalProperties: false, + properties: { + eslint: { type: 'boolean' }, + prettier: { type: 'boolean' }, + }, + }, + typescriptOpts: { + type: 'object', + }, + pkgs: { + type: 'array', + }, + }, +}; diff --git a/packages/father-build/src/types.d.ts b/packages/father-build/src/types.d.ts new file mode 100644 index 000000000..ce52d4156 --- /dev/null +++ b/packages/father-build/src/types.d.ts @@ -0,0 +1,99 @@ +export type BundleType = 'rollup' | 'babel'; + +interface IBundleTypeOutput { + type: BundleType; + file?: string; +} + +export interface ICjs extends IBundleTypeOutput { + minify?: boolean; + lazy?: boolean; +} + +interface IEsm extends IBundleTypeOutput { + mjs?: boolean; + minify?: boolean; + importLibToEs?: boolean; +} + +interface IStringObject { + [prop: string]: string; +} + +interface IStringArrayObject { + [prop: string]: string[]; +} + +interface IUmd { + globals?: IStringObject; + name?: string; + minFile?: boolean; + file?: string; + sourcemap?: boolean; +} + +export interface IBundleOptions { + entry?: string | string[]; + file?: string; + esm?: BundleType | IEsm | false; + cjs?: BundleType | ICjs | false; + umd?: IUmd | false; + extraBabelPlugins?: any[]; + extraBabelPresets?: any[]; + extraPostCSSPlugins?: any[]; + extraRollupPlugins?: any[]; + extraExternals?: string[]; + externalsExclude?: string[]; + cssModules?: boolean | Object; + extractCSS?: boolean; + injectCSS?: boolean, + inject?: Object, + autoprefixer?: Object; + include?: string | RegExp; + namedExports?: IStringArrayObject; + runtimeHelpers?: boolean; + target?: 'node' | 'browser'; + overridesByEntry?: { + [entry: string]: any; + }; + replace?: { + [value: string]: any; + }; + browserFiles?: { + [value: string]: any; + }; + nodeFiles?: { + [value: string]: any; + }; + nodeVersion?: number; + disableTypeCheck?: boolean; + preCommit?: { + eslint?: boolean; + prettier?: boolean; + }; + lessInBabelMode?: boolean | { + paths?: any[]; + plugins?: any[]; + }; + typescriptOpts?: { + [value: string]: any; + }; + nodeResolveOpts?: { + [value: string]: any; + }; + lessInRollupMode?: { + [opt: string]: any + }; + sassInRollupMode?: { + [opt: string]: any + }; + pkgs?: string[]; +} + +export interface IOpts { + cwd: string; + watch?: boolean; + buildArgs?: IBundleOptions; + rootConfig?: IBundleOptions; + rootPath?: string; +} diff --git a/packages/father-build/src/utils.ts b/packages/father-build/src/utils.ts new file mode 100644 index 000000000..c00bcb91c --- /dev/null +++ b/packages/father-build/src/utils.ts @@ -0,0 +1,11 @@ +import { existsSync } from 'fs'; +import { join } from 'path'; + +export function getExistFile({ cwd, files, returnRelative }) { + for (const file of files) { + const absFilePath = join(cwd, file); + if (existsSync(absFilePath)) { + return returnRelative ? file : absFilePath; + } + } +} diff --git a/packages/father-build/template/tsconfig.json b/packages/father-build/template/tsconfig.json new file mode 100644 index 000000000..9a8969019 --- /dev/null +++ b/packages/father-build/template/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "allowSyntheticDefaultImports": true, + "declaration": true, + "module": "esnext", + "target": "esnext", + "moduleResolution": "node", + "jsx": "react" + }, + "include": ["./src", "./typings/"], + "typings": "./typings/index.d.ts", + "exclude": [ + "node_modules", + "build", + "scripts", + "acceptance-tests", + "webpack", + "jest", + "src/setupTests.ts", + "tslint:latest", + "tslint-config-prettier" + ] +} diff --git a/packages/plugin-collections/package.json b/packages/plugin-collections/package.json new file mode 100644 index 000000000..3296687ab --- /dev/null +++ b/packages/plugin-collections/package.json @@ -0,0 +1,11 @@ +{ + "name": "@nocobase/plugin-collections", + "version": "0.3.0-alpha.0", + "main": "lib/index.js", + "license": "MIT", + "dependencies": { + "@nocobase/api": "^0.3.0-alpha.0", + "@nocobase/database": "^0.3.0-alpha.0", + "@nocobase/resourcer": "^0.3.0-alpha.0" + } +} diff --git a/packages/plugin-collections/src/index.ts b/packages/plugin-collections/src/index.ts new file mode 100644 index 000000000..99aae14f8 --- /dev/null +++ b/packages/plugin-collections/src/index.ts @@ -0,0 +1,55 @@ +import path from 'path'; +import Database, { ModelCtor } from '@nocobase/database'; +import Resourcer from '@nocobase/resourcer'; +import CollectionModel from './models/collection'; + +export default async function (options = {}) { + const database: Database = this.database; + const resourcer: Resourcer = this.resourcer; + + const tables = database.import({ + directory: path.resolve(__dirname, 'tables'), + }); + + await database.sync(); + + const Collection = database.getModel('collections') as ModelCtor; + + for (const [key, table] of tables) { + const options = table.getOptions(); + const collection = await Collection.create({ + name: options.name, + title: options.title, + }); + await collection.updateAssociations({ + fields: options.fields.map(field => { + return { + type: field.type, + name: field.name, + options: field, + }; + }), + }); + } + + const collections = await Collection.findAll(); + await Promise.all(collections.map(async (collection) => { + return await collection.modelInit(); + })); + + let first = false; + + resourcer.use(async (ctx, next) => { + if (first) { + return next(); + } + first = true; + const [Scope, User] = database.getModels(['scopes', 'users']); + console.log('aaaa', {Scope, User}); + await next(); + }); + + resourcer.import({ + directory: path.resolve(__dirname, 'resources'), + }); +} diff --git a/packages/plugin-collections/src/models/collection.ts b/packages/plugin-collections/src/models/collection.ts new file mode 100644 index 000000000..a28bd1b0c --- /dev/null +++ b/packages/plugin-collections/src/models/collection.ts @@ -0,0 +1,23 @@ +import { Model } from '@nocobase/database'; + +export class CollectionModel extends Model { + async modelInit() { + if (['collections', 'fields'].includes(this.get('name'))) { + return; + } + const Field = this.database.getModel('fields'); + const fields = await Field.findAll(); + this.database.table({ + name: this.get('name'), + fields: fields.map(field => { + return { + name: field.get('name'), + type: field.get('type'), + }; + }), + }); + console.log('modelInit', this.get('name')); + } +} + +export default CollectionModel; diff --git a/packages/plugin-collections/src/models/field.ts b/packages/plugin-collections/src/models/field.ts new file mode 100644 index 000000000..6a2f04991 --- /dev/null +++ b/packages/plugin-collections/src/models/field.ts @@ -0,0 +1,7 @@ +import { Model } from '@nocobase/database'; + +export class FieldModel extends Model { + +} + +export default FieldModel; diff --git a/packages/plugin-collections/src/resources/collections.ts b/packages/plugin-collections/src/resources/collections.ts new file mode 100644 index 000000000..a85ce0af6 --- /dev/null +++ b/packages/plugin-collections/src/resources/collections.ts @@ -0,0 +1,25 @@ +import { ResourceOptions } from '@nocobase/resourcer'; + +export default { + name: 'collections', + actions: { + list: { + fields: { + appends: ['fields'], + } + }, + get: { + fields: { + appends: ['fields'], + } + }, + // get: { + // handler: async (ctx, next) => { + // ctx.body = { + // get: {} + // } + // await next(); + // } + // }, + }, +} as ResourceOptions; diff --git a/packages/plugin-collections/src/tables/collections.ts b/packages/plugin-collections/src/tables/collections.ts new file mode 100644 index 000000000..be29ae029 --- /dev/null +++ b/packages/plugin-collections/src/tables/collections.ts @@ -0,0 +1,55 @@ +import { TableOptions } from '@nocobase/database'; +import CollectionModel from '../models/collection'; + +export default { + name: 'collections', + title: '数据表配置', + model: CollectionModel, + fields: [ + { + type: 'string', + name: 'title', + showInTable: true, + required: true, + component: { + type: 'string', + label: '名称', + }, + }, + { + type: 'string', + name: 'name', + unique: true, + primaryKey: true, + autoIncrement: false, + showInTable: true, + required: true, + component: { + type: 'string', + label: '标识', + 'x-rules': [ + { + format: 'slug', + message: '只允许英文数字和下划线', + }, + ], + }, + }, + { + type: 'string', + name: 'description', + component: { + type: 'textarea', + label: '描述', + }, + }, + { + type: 'json', + name: 'options', + }, + { + name: 'fields', + type: 'hasMany', + }, + ], +} as TableOptions; diff --git a/packages/plugin-collections/src/tables/fields.ts b/packages/plugin-collections/src/tables/fields.ts new file mode 100644 index 000000000..27316134f --- /dev/null +++ b/packages/plugin-collections/src/tables/fields.ts @@ -0,0 +1,24 @@ +import { TableOptions } from '@nocobase/database'; + +export default { + name: 'fields', + title: '字段配置', + fields: [ + { + type: 'string', + name: 'name', + }, + { + type: 'string', + name: 'type', + }, + { + type: 'json', + name: 'options', + }, + { + type: 'belongsTo', + name: 'collection', + }, + ], +} as TableOptions; diff --git a/packages/plugin-file-manager/package.json b/packages/plugin-file-manager/package.json new file mode 100644 index 000000000..e9f22b897 --- /dev/null +++ b/packages/plugin-file-manager/package.json @@ -0,0 +1,6 @@ +{ + "name": "@nocobase/plugin-file-manager", + "version": "0.3.0-alpha.0", + "main": "lib/index.js", + "license": "MIT" +} diff --git a/packages/plugin-file-manager/src/index.ts b/packages/plugin-file-manager/src/index.ts new file mode 100644 index 000000000..e69de29bb diff --git a/packages/plugin-pages/package.json b/packages/plugin-pages/package.json new file mode 100644 index 000000000..6524cdcd6 --- /dev/null +++ b/packages/plugin-pages/package.json @@ -0,0 +1,6 @@ +{ + "name": "@nocobase/plugin-pages", + "version": "0.3.0-alpha.0", + "main": "lib/index.js", + "license": "MIT" +} diff --git a/packages/plugin-pages/src/index.ts b/packages/plugin-pages/src/index.ts new file mode 100644 index 000000000..e69de29bb diff --git a/packages/plugin-permissions/package.json b/packages/plugin-permissions/package.json new file mode 100644 index 000000000..5cab95768 --- /dev/null +++ b/packages/plugin-permissions/package.json @@ -0,0 +1,6 @@ +{ + "name": "@nocobase/plugin-permissions", + "version": "0.3.0-alpha.0", + "main": "lib/index.js", + "license": "MIT" +} diff --git a/packages/plugin-permissions/src/index.ts b/packages/plugin-permissions/src/index.ts new file mode 100644 index 000000000..e69de29bb diff --git a/packages/plugin-users/package.json b/packages/plugin-users/package.json new file mode 100644 index 000000000..2181df619 --- /dev/null +++ b/packages/plugin-users/package.json @@ -0,0 +1,6 @@ +{ + "name": "@nocobase/plugin-users", + "version": "0.3.0-alpha.0", + "main": "lib/index.js", + "license": "MIT" +} diff --git a/packages/plugin-users/src/index.ts b/packages/plugin-users/src/index.ts new file mode 100644 index 000000000..e69de29bb diff --git a/packages/resourcer/package.json b/packages/resourcer/package.json new file mode 100644 index 000000000..f5ac67119 --- /dev/null +++ b/packages/resourcer/package.json @@ -0,0 +1,24 @@ +{ + "name": "@nocobase/resourcer", + "version": "0.3.0-alpha.0", + "description": "", + "main": "./lib/index.js", + "types": "./lib/index.d.ts", + "scripts": { + }, + "license": "MIT", + "dependencies": { + "koa-compose": "^4.1.0", + "lodash": "^4.17.19", + "path-to-regexp": "^6.1.0", + "qs": "^6.9.4" + }, + "devDependencies": { + "typescript": "^3.9.6" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/nocobase/nocobase.git", + "directory": "packages/resourcer" + } +} diff --git a/packages/resourcer/src/__tests__/actions/demo0.js b/packages/resourcer/src/__tests__/actions/demo0.js new file mode 100644 index 000000000..cdc06eafa --- /dev/null +++ b/packages/resourcer/src/__tests__/actions/demo0.js @@ -0,0 +1,5 @@ +module.exports = async function (ctx, next) { + ctx.arr.push(7); + await next(); + ctx.arr.push(8); +} \ No newline at end of file diff --git a/packages/resourcer/src/__tests__/actions/demo1.ts b/packages/resourcer/src/__tests__/actions/demo1.ts new file mode 100644 index 000000000..200c2db56 --- /dev/null +++ b/packages/resourcer/src/__tests__/actions/demo1.ts @@ -0,0 +1,5 @@ +export default async function (ctx, next) { + ctx.arr.push(9); + await next(); + ctx.arr.push(10); +} \ No newline at end of file diff --git a/packages/resourcer/src/__tests__/koa.test.ts b/packages/resourcer/src/__tests__/koa.test.ts new file mode 100644 index 000000000..adac2e7f7 --- /dev/null +++ b/packages/resourcer/src/__tests__/koa.test.ts @@ -0,0 +1,699 @@ +import Koa from 'koa'; +import Router from '@koa/router'; +import request from 'supertest'; +import http from 'http'; +import Resourcer from '../resourcer'; +import qs from 'qs'; +import bodyParser from 'koa-bodyparser'; + +describe('koa middleware', () => { + it('shound work', async () => { + const app = new Koa(); + + const resourcer = new Resourcer(); + + resourcer.define({ + name: 'test', + actions: { + async list(ctx, next) { + ctx.body = ctx.body || {}; + ctx.body.arr = ctx.body.arr || []; + ctx.body.arr.push(5); + await next(); + ctx.body.arr.push(6); + }, + }, + }); + + app.use(resourcer.middleware({ + prefix: '/api', + })); + + app.use(async (ctx, next) => { + if (ctx.path === '/api/test') { + ctx.body = ctx.body || {}; + ctx.body.arr = ctx.body.arr || []; + ctx.body.arr.push(3); + await next(); + ctx.body.arr.push(4); + } + }); + + const response = await request(http.createServer(app.callback())).get('/api/test'); + expect(response.body.arr).toEqual([5,3,4,6]); + }); + + it('shound work', async () => { + const app = new Koa(); + + const resourcer = new Resourcer(); + + resourcer.registerHandlers({ + async index(ctx, next) { + ctx.body = ctx.body || {}; + ctx.body.arr = ctx.body.arr || []; + ctx.body.arr.push(5); + await next(); + ctx.body.arr.push(6); + }, + }); + + resourcer.define({ + name: 'test', + }); + + app.use(resourcer.middleware({ + prefix: '/api', + accessors: { + list: 'index', + }, + })); + + app.use(async (ctx, next) => { + if (ctx.path === '/api/test') { + ctx.body = ctx.body || {}; + ctx.body.arr = ctx.body.arr || []; + ctx.body.arr.push(3); + await next(); + ctx.body.arr.push(4); + } + }); + + const response = await request(http.createServer(app.callback())).get('/api/test'); + expect(response.body.arr).toEqual([5,3,4,6]); + }); + + it('shound be 404', async () => { + const app = new Koa(); + const resourcer = new Resourcer(); + app.use(resourcer.middleware()); + const response = await request(http.createServer(app.callback())).get('/test'); + expect(response.status).toBe(404); + }); + + it('shound work', async () => { + const app = new Koa(); + + const resourcer = new Resourcer(); + + resourcer.define({ + name: 'test', + actions: { + async index(ctx, next) { + ctx.body = ctx.body || {}; + ctx.body.arr = ctx.body.arr || []; + ctx.body.arr.push(5); + await next(); + ctx.body.arr.push(6); + }, + }, + }); + + app.use(resourcer.middleware({ + prefix: '/api', + accessors: { + list: 'index', + }, + })); + + app.use(async (ctx, next) => { + if (ctx.path === '/api/test') { + ctx.body = ctx.body || {}; + ctx.body.arr = ctx.body.arr || []; + ctx.body.arr.push(3); + await next(); + ctx.body.arr.push(4); + } + }); + + const response = await request(http.createServer(app.callback())).get('/api/test'); + expect(response.body.arr).toEqual([5,3,4,6]); + }); + + it('shound work', async () => { + const app = new Koa(); + const resourcer = new Resourcer(); + + resourcer.define({ + name: 'tables.fields', + actions: { + async list(ctx, next) { + ctx.body = ctx.body || {}; + ctx.body.arr = ctx.body.arr || []; + ctx.body.arr.push(3); + await next(); + ctx.body.arr.push(4); + }, + } + }); + + app.use(resourcer.middleware()); + + const response = await request(http.createServer(app.callback())).get('/tables/demos/fields'); + expect(response.body.arr).toEqual([3,4]); + }); + + it('shound work', async () => { + const app = new Koa(); + const resourcer = new Resourcer(); + + resourcer.define({ + name: 'tables#fields', + actions: { + async list(ctx, next) { + ctx.body = ctx.body || {}; + ctx.body.arr = ctx.body.arr || []; + ctx.body.arr.push(3); + await next(); + ctx.body.arr.push(4); + }, + } + }); + + app.use(resourcer.middleware({ + nameRule: ({resourceName, associatedName}) => associatedName ? `${associatedName}#${resourceName}` : resourceName, + })); + + const response = await request(http.createServer(app.callback())).get('/tables/demos/fields'); + expect(response.body.arr).toEqual([3,4]); + }); + + describe('action options', () => { + let app: Koa; + let resourcer: Resourcer; + beforeAll(() => { + app = new Koa(); + resourcer = new Resourcer(); + const handler = async (ctx, next) => { + ctx.body = ctx.action.params; + await next(); + }; + resourcer.registerHandlers({ + list: handler, + create: handler, + update: handler, + get: handler, + destroy: handler, + set: handler, + add: handler, + remove: handler, + }); + app.use(bodyParser()); + app.use(resourcer.middleware()); + }); + it('options1', async () => { + resourcer.define({ + name: 'tests', + actions: { + list: { + filter: { + col1: 'val1', + col2: 'val2', + }, + fields: ['id'], + sort: ['-id'], + }, + }, + }); + const response = await request(http.createServer(app.callback())) + .get('/tests') + .query({ + filter: { + col2: '&val2', + col3: 'val3', + }, + other: 'other1', + sort: '-id', + }); + expect(response.body).toEqual({ + sort: '-id', + filter: { col1: 'val1', col2: 'val2', col3: 'val3' }, + fields: { only: [ 'id' ], appends: [] }, + other: 'other1', + actionName: 'list', + resourceName: 'tests' + }); + }); + it('options2', async () => { + resourcer.define({ + name: 'tests', + actions: { + create: { + defaultValues: { + col1: 'val1', + }, + }, + }, + }); + const response = await request(http.createServer(app.callback())) + .post('/tests') + .send({'aa': 'aa'}); + expect(response.body).toEqual({ + actionName: 'create', + resourceName: 'tests', + values: { col1: 'val1', aa: 'aa' } + }); + }); + it('options3', async () => { + resourcer.define({ + name: 'tests', + actions: { + create: { + defaultValues: { + col1: 'val1', + }, + }, + }, + }); + const response = await request(http.createServer(app.callback())) + .post('/resourcer/tests:create') + .send({ + values: {'aa': 'aa'} + }); + expect(response.body).toEqual({ + actionName: 'create', + resourceName: 'tests', + values: { col1: 'val1', aa: 'aa' } + }); + }); + it('options4', async () => { + resourcer.define({ + name: 'tests', + actions: { + update: { + defaultValues: { + col1: 'val1', + }, + }, + }, + }); + const response = await request(http.createServer(app.callback())) + .post('/resourcer/tests:update') + .send({ + resourceKey: 1, + values: {'aa': 'aa'} + }); + expect(response.body).toEqual({ + resourceKey: 1, + actionName: 'update', + resourceName: 'tests', + values: { col1: 'val1', aa: 'aa' } + }); + }); + describe('hasOne', () => { + beforeAll(() => { + resourcer.define({ + type: 'hasOne', + name: 'users.settings', + }); + }); + it('get', async () => { + const response = await request(http.createServer(app.callback())) + .get('/users/1/settings'); + expect(response.body).toEqual({ + associatedName: 'users', + associatedKey: '1', + resourceName: 'settings', + actionName: 'get' + }); + }); + it('update', async () => { + const response = await request(http.createServer(app.callback())) + .post('/users/1/settings'); + expect(response.body).toEqual({ + associatedName: 'users', + associatedKey: '1', + resourceName: 'settings', + actionName: 'update' + }); + }); + it('destroy', async () => { + const response = await request(http.createServer(app.callback())) + .delete('/users/1/settings'); + expect(response.body).toEqual({ + associatedName: 'users', + associatedKey: '1', + resourceName: 'settings', + actionName: 'destroy' + }); + }); + }); + describe('hasMany', () => { + beforeAll(() => { + resourcer.define({ + type: 'hasMany', + name: 'users.posts', + }); + }); + it('list', async () => { + const response = await request(http.createServer(app.callback())) + .get('/users/1/posts'); + expect(response.body).toEqual({ + associatedName: 'users', + associatedKey: '1', + resourceName: 'posts', + actionName: 'list' + }); + }); + it('get', async () => { + const response = await request(http.createServer(app.callback())) + .get('/users/1/posts/1'); + expect(response.body).toEqual({ + associatedName: 'users', + associatedKey: '1', + resourceName: 'posts', + resourceKey: '1', + actionName: 'get' + }); + }); + it('create', async () => { + const response = await request(http.createServer(app.callback())) + .post('/users/1/posts'); + expect(response.body).toEqual({ + associatedName: 'users', + associatedKey: '1', + resourceName: 'posts', + actionName: 'create' + }); + }); + it('update', async () => { + const response = await request(http.createServer(app.callback())) + .put('/users/1/posts/1'); + expect(response.body).toEqual({ + associatedName: 'users', + associatedKey: '1', + resourceName: 'posts', + resourceKey: '1', + actionName: 'update' + }); + }); + it('destroy', async () => { + const response = await request(http.createServer(app.callback())) + .delete('/users/1/posts/1'); + expect(response.body).toEqual({ + associatedName: 'users', + associatedKey: '1', + resourceName: 'posts', + resourceKey: '1', + actionName: 'destroy' + }); + }); + }); + describe('belongsTo', () => { + beforeAll(() => { + resourcer.define({ + type: 'belongsTo', + name: 'posts.user', + }); + }); + it('get', async () => { + const response = await request(http.createServer(app.callback())) + .get('/posts/1/user'); + expect(response.body).toEqual({ + associatedName: 'posts', + associatedKey: '1', + resourceName: 'user', + actionName: 'get' + }); + }); + it('set', async () => { + const response = await request(http.createServer(app.callback())) + .post('/posts/1/user/1'); + expect(response.body).toEqual({ + associatedName: 'posts', + associatedKey: '1', + resourceName: 'user', + resourceKey: '1', + actionName: 'set' + }); + }); + it('remove', async () => { + const response = await request(http.createServer(app.callback())) + .delete('/posts/1/user'); + expect(response.body).toEqual({ + associatedName: 'posts', + associatedKey: '1', + resourceName: 'user', + actionName: 'remove' + }); + }); + }); + describe('belongsToMany', () => { + beforeAll(() => { + resourcer.define({ + type: 'belongsToMany', + name: 'posts.tags', + }); + }); + it('list', async () => { + const response = await request(http.createServer(app.callback())) + .get('/posts/1/tags'); + expect(response.body).toEqual({ + associatedName: 'posts', + associatedKey: '1', + resourceName: 'tags', + actionName: 'list' + }); + }); + it('get', async () => { + const response = await request(http.createServer(app.callback())) + .get('/posts/1/tags/1'); + expect(response.body).toEqual({ + associatedName: 'posts', + associatedKey: '1', + resourceName: 'tags', + resourceKey: '1', + actionName: 'get' + }); + }); + it('set', async () => { + const response = await request(http.createServer(app.callback())) + .post('/posts/1/tags'); + expect(response.body).toEqual({ + associatedName: 'posts', + associatedKey: '1', + resourceName: 'tags', + actionName: 'set' + }); + }); + it('add', async () => { + const response = await request(http.createServer(app.callback())) + .post('/posts/1/tags/1'); + expect(response.body).toEqual({ + associatedName: 'posts', + associatedKey: '1', + resourceName: 'tags', + resourceKey: '1', + actionName: 'add' + }); + }); + it('update', async () => { + const response = await request(http.createServer(app.callback())) + .put('/posts/1/tags/1'); + expect(response.body).toEqual({ + associatedName: 'posts', + associatedKey: '1', + resourceName: 'tags', + resourceKey: '1', + actionName: 'update' + }); + }); + it('remove', async () => { + const response = await request(http.createServer(app.callback())) + .delete('/posts/1/tags/1'); + expect(response.body).toEqual({ + associatedName: 'posts', + associatedKey: '1', + resourceName: 'tags', + resourceKey: '1', + actionName: 'remove' + }); + }); + }); + it('fields1', async () => { + resourcer.define({ + name: 'test1', + actions: { + list: {}, + }, + }); + const response = await request(http.createServer(app.callback())) + .get('/test1') + .query({ + fields: ['id', 'col1'], + }); + expect(response.body).toEqual({ + actionName: 'list', + resourceName: 'test1', + fields: { only: [ 'id', 'col1' ], appends: [] } + }); + }); + it('fields2', async () => { + resourcer.define({ + name: 'test1', + actions: { + list: { + fields: ['id'], + }, + }, + }); + const response = await request(http.createServer(app.callback())) + .get('/test1') + .query({ + fields: ['id', 'col1'], + }); + expect(response.body).toEqual({ + actionName: 'list', + resourceName: 'test1', + fields: { only: [ 'id' ], appends: [] } + }); + }); + it('fields3', async () => { + resourcer.define({ + name: 'test1', + actions: { + list: { + fields: { + except: ['password'], + }, + }, + }, + }); + const response = await request(http.createServer(app.callback())) + .get('/test1') + .query({ + fields: ['id', 'col1', 'password'], + }); + expect(response.body).toEqual({ + actionName: 'list', + resourceName: 'test1', + fields: { only: [ 'id', 'col1' ], appends: [] } + }); + }); + it('fields4', async () => { + resourcer.define({ + name: 'test1', + actions: { + list: { + fields: { + except: ['password'], + appends: ['col2'], + }, + }, + }, + }); + const response = await request(http.createServer(app.callback())) + .get('/test1') + .query({ + fields: ['id', 'col1', 'password', 'col2'], + }); + expect(response.body).toEqual({ + actionName: 'list', + resourceName: 'test1', + fields: { only: [ 'id', 'col1', 'col2' ], appends: [] } + }); + }); + it('fields5', async () => { + resourcer.define({ + name: 'test1', + actions: { + list: { + fields: { + except: ['password'], + appends: ['col2'], + }, + }, + }, + }); + const response = await request(http.createServer(app.callback())) + .get('/test1').query({ + fields: { + appends: ['relation1'], + } + }); + expect(response.body).toEqual({ + actionName: 'list', + resourceName: 'test1', + fields: { except: [ 'password' ], appends: [ 'relation1', 'col2' ] } + }); + }); + it('fields6', async () => { + resourcer.define({ + name: 'test1', + list: {}, + }); + const response = await request(http.createServer(app.callback())) + .get('/test1').query({ + 'fields[appends]': 'rel1,rel2', + }); + expect(response.body).toEqual({ + actionName: 'list', + resourceName: 'test1', + fields: { appends: [ 'rel1', 'rel2' ] } + }); + }); + it('fields7', async () => { + resourcer.define({ + name: 'users.posts', + list: {}, + }); + const response = await request(http.createServer(app.callback())) + .get('/users/name/posts').query({ + 'fields[appends]': 'rel1,rel2', + }); + expect(response.body).toEqual({ + associatedName: 'users', + associatedKey: 'name', + resourceName: 'posts', + actionName: 'list', + fields: { appends: [ 'rel1', 'rel2' ] } + }); + }); + it('fields8', async () => { + resourcer.define({ + name: 'users.posts', + actions: { + list: { + async middleware(ctx, next) { + ctx.action.setParam('filter.user_name', ctx.action.params.associatedKey); + await next(); + }, + }, + } + }); + const response = await request(http.createServer(app.callback())) + .get('/users/name/posts'); + expect(response.body).toEqual({ + associatedName: 'users', + associatedKey: 'name', + resourceName: 'posts', + actionName: 'list', + filter: { user_name: 'name' }, + }); + }); + it('fields9', async () => { + resourcer.define({ + name: 'users.posts', + actions: { + list: { + async middleware(ctx, next) { + ctx.action.setParam('fields.only[]', ctx.action.params.associatedKey); + await next(); + }, + }, + } + }); + const response = await request(http.createServer(app.callback())) + .get('/users/name/posts'); + expect(response.body).toEqual({ + associatedName: 'users', + associatedKey: 'name', + resourceName: 'posts', + actionName: 'list', + fields: { + only: ['name'], + }, + }); + }); + }); +}); diff --git a/packages/resourcer/src/__tests__/middlewares/demo0.js b/packages/resourcer/src/__tests__/middlewares/demo0.js new file mode 100644 index 000000000..4cc4d4654 --- /dev/null +++ b/packages/resourcer/src/__tests__/middlewares/demo0.js @@ -0,0 +1,5 @@ +module.exports = async function (ctx, next) { + ctx.arr.push(1); + await next(); + ctx.arr.push(2); +} \ No newline at end of file diff --git a/packages/resourcer/src/__tests__/middlewares/demo1.ts b/packages/resourcer/src/__tests__/middlewares/demo1.ts new file mode 100644 index 000000000..b6ef8f4ba --- /dev/null +++ b/packages/resourcer/src/__tests__/middlewares/demo1.ts @@ -0,0 +1,5 @@ +export default async function (ctx, next) { + ctx.arr.push(2); + await next(); + ctx.arr.push(3); +} \ No newline at end of file diff --git a/packages/resourcer/src/__tests__/resourcer.test.ts b/packages/resourcer/src/__tests__/resourcer.test.ts new file mode 100644 index 000000000..667cacdb1 --- /dev/null +++ b/packages/resourcer/src/__tests__/resourcer.test.ts @@ -0,0 +1,549 @@ +import Resourcer, { ResourcerContext, Action } from '..'; +import Resource from '../resource'; +import path from 'path'; +import { resourceUsage } from 'process'; + +describe('resourcer', () => { + + it('action', async () => { + const resourcer = new Resourcer(); + + resourcer.define({ + name: 'test', + actions: { + async list(ctx, next) { + ctx.arr.push(1); + await next(); + ctx.arr.push(2); + }, + }, + }); + + let context: ResourcerContext = { + arr: [], + }; + + await resourcer.execute({ + resource: 'test', + action: 'list', + }, context); + + expect(context.arr).toStrictEqual([1,2]); + expect(context.resourcer).toBeInstanceOf(Resourcer); + expect(context.action).toBeInstanceOf(Action); + expect(context.action.getResource()).toBeInstanceOf(Resource); + expect(context.action.getName()).toBe('list'); + expect(context.action.getResource().getName()).toBe('test'); + }); + + it('action', async () => { + const resourcer = new Resourcer(); + + resourcer.define({ + name: 'test', + actions: { + async list(ctx, next) { + ctx.arr.push(1); + await next(); + ctx.arr.push(2); + }, + async test(ctx, next) { + ctx.arr.push(11); + await next(); + ctx.arr.push(22); + }, + }, + }); + + let context = { + arr: [], + }; + + await resourcer.execute({ + resource: 'test', + action: 'list', + }, context); + + expect(context.arr).toStrictEqual([1,2]); + + context = { + arr: [], + }; + + await resourcer.execute({ + resource: 'test', + action: 'test', + }, context); + + expect(context.arr).toStrictEqual([11,22]); + }); + + it('registerHandlers()', async () => { + const resourcer = new Resourcer(); + + resourcer.registerHandlers({ + async list(ctx, next) { + ctx.arr.push(1); + await next(); + ctx.arr.push(2); + }, + }); + + resourcer.define({ + name: 'test', + actions: { + async test(ctx, next) { + ctx.arr.push(11); + await next(); + ctx.arr.push(22); + }, + }, + }); + + let context = { + arr: [], + }; + + await resourcer.execute({ + resource: 'test', + action: 'list', + }, context); + + expect(context.arr).toStrictEqual([1,2]); + + context = { + arr: [], + }; + + await resourcer.execute({ + resource: 'test', + action: 'test', + }, context); + + expect(context.arr).toStrictEqual([11,22]); + }); + + it('only', async () => { + const resourcer = new Resourcer(); + + resourcer.registerHandlers({ + async list(ctx, next) { + ctx.arr.push(1); + await next(); + ctx.arr.push(2); + }, + async test(ctx, next) { + ctx.arr.push('test1'); + await next(); + ctx.arr.push('test2'); + }, + }); + + resourcer.define({ + name: 'test', + only: ['list'], + }); + + let context = { + arr: [], + }; + + await resourcer.execute({ + resource: 'test', + action: 'list', + }, context); + + expect(context.arr).toStrictEqual([1,2]); + + context = { + arr: [], + }; + + await expect(async () => { + await resourcer.execute({ + resource: 'test', + action: 'test', + }, context); + }).rejects.toThrow('test action is not allowed'); + }); + + it('except', async () => { + const resourcer = new Resourcer(); + + resourcer.registerHandlers({ + async list(ctx, next) { + ctx.arr.push(1); + await next(); + ctx.arr.push(2); + }, + async test(ctx, next) { + ctx.arr.push('test1'); + await next(); + ctx.arr.push('test2'); + }, + }); + + resourcer.define({ + name: 'test', + except: ['test'], + }); + + let context = { + arr: [], + }; + + await resourcer.execute({ + resource: 'test', + action: 'list', + }, context); + + expect(context.arr).toStrictEqual([1,2]); + + context = { + arr: [], + }; + + await expect(async () => { + await resourcer.execute({ + resource: 'test', + action: 'test', + }, context); + }).rejects.toThrow('test action is not allowed'); + }); + + it('middlewares', async () => { + const resourcer = new Resourcer(); + + resourcer.define({ + name: 'test', + middlewares: [ + async (ctx, next) => { + ctx.arr.push(1); + await next(); + ctx.arr.push(6); + }, + async (ctx, next) => { + ctx.arr.push(2); + await next(); + ctx.arr.push(5); + }, + ], + actions: { + async list(ctx, next) { + ctx.arr.push(3); + await next(); + ctx.arr.push(4); + }, + }, + }); + + let context = { + arr: [], + }; + + await resourcer.execute({ + resource: 'test', + action: 'list', + }, context); + + expect(context.arr).toStrictEqual([1,2,3,4,5,6]); + }); + + it('middlewares#global', async () => { + const resourcer = new Resourcer(); + + resourcer.use(async (ctx, next) => { + ctx.arr.push(7); + await next(); + ctx.arr.push(8); + }); + + resourcer.define({ + name: 'test', + middlewares: [ + async (ctx, next) => { + ctx.arr.push(1); + await next(); + ctx.arr.push(6); + }, + async (ctx, next) => { + ctx.arr.push(2); + await next(); + ctx.arr.push(5); + }, + ], + actions: { + async list(ctx, next) { + ctx.arr.push(3); + await next(); + ctx.arr.push(4); + }, + }, + }); + + let context = { + arr: [], + }; + + await resourcer.execute({ + resource: 'test', + action: 'list', + }, context); + + expect(context.arr).toStrictEqual([7,1,2,3,4,5,6,8]); + }); + + it('middlewares#only', async () => { + const resourcer = new Resourcer(); + + resourcer.define({ + name: 'test', + middlewares: [ + { + only: ['list'], + async handler(ctx, next) { + ctx.arr.push(1); + await next(); + ctx.arr.push(6); + }, + }, + { + only: ['create'], + async handler(ctx, next) { + ctx.arr.push(2); + await next(); + ctx.arr.push(5); + }, + }, + ], + actions: { + async list(ctx, next) { + ctx.arr.push(3); + await next(); + ctx.arr.push(4); + }, + async create(ctx, next) { + ctx.arr.push(7); + await next(); + ctx.arr.push(8); + }, + }, + }); + + let context = { + arr: [], + }; + + await resourcer.execute({ + resource: 'test', + action: 'list', + }, context); + + expect(context.arr).toStrictEqual([1,3,4,6]); + + context = { + arr: [], + }; + + await resourcer.execute({ + resource: 'test', + action: 'create', + }, context); + + expect(context.arr).toStrictEqual([2,7,8,5]); + }); + + it('middlewares#except', async () => { + const resourcer = new Resourcer(); + + resourcer.define({ + name: 'test', + middlewares: [ + { + except: ['create'], + async handler(ctx, next) { + ctx.arr.push(1); + await next(); + ctx.arr.push(6); + }, + }, + { + except: ['list'], + async handler(ctx, next) { + ctx.arr.push(2); + await next(); + ctx.arr.push(5); + }, + }, + ], + actions: { + async list(ctx, next) { + ctx.arr.push(3); + await next(); + ctx.arr.push(4); + }, + async create(ctx, next) { + ctx.arr.push(7); + await next(); + ctx.arr.push(8); + }, + }, + }); + + let context = { + arr: [], + }; + + await resourcer.execute({ + resource: 'test', + action: 'list', + }, context); + + expect(context.arr).toStrictEqual([1,3,4,6]); + + context = { + arr: [], + }; + + await resourcer.execute({ + resource: 'test', + action: 'create', + }, context); + + expect(context.arr).toStrictEqual([2,7,8,5]); + }); + + it('shound work', async () => { + const resourcer = new Resourcer(); + resourcer.import({ + directory: path.resolve(__dirname, 'resources'), + }); + let context = { + arr: [], + }; + await resourcer.execute({ + resource: 'demo', + action: 'list', + }, context); + expect(context.arr).toEqual([ 1, 2 ]); + }); + + it('shound work', async () => { + const resourcer = new Resourcer(); + resourcer.define({ + name: 'test', + middlewares: [ + async (ctx, next) => { + ctx.arr.push(5); + await next(); + ctx.arr.push(6); + }, + async (ctx, next) => { + ctx.arr.push(7); + await next(); + ctx.arr.push(8); + }, + ], + actions: { + list: { + middlewares: [ + async (ctx, next) => { + ctx.arr.push(1); + await next(); + ctx.arr.push(2); + }, + async (ctx, next) => { + ctx.arr.push(9); + await next(); + ctx.arr.push(10); + }, + ], + async handler(ctx, next) { + ctx.arr.push(3); + await next(); + ctx.arr.push(4); + }, + } + }, + }); + let context = { + arr: [], + }; + await resourcer.execute({ + resource: 'test', + action: 'list', + }, context); + expect(context.arr).toEqual([ 5, 7, 1, 9, 3, 4, 10, 2, 8, 6 ]); + }); + + it('require module', async () => { + const resourcer = new Resourcer(); + + resourcer.define({ + name: 'test', + middleware: require('./middlewares/demo0'), + actions: { + list: require('./actions/demo0'), + }, + }); + + let context = { + arr: [], + }; + + await resourcer.execute({ + resource: 'test', + action: 'list', + }, context); + + expect(context.arr).toEqual([ 1, 7, 8, 2 ]); + }); + + it('require module', async () => { + const resourcer = new Resourcer(); + + resourcer.define({ + name: 'test', + middleware: require('./middlewares/demo1'), + actions: { + list: require('./actions/demo1'), + }, + }); + + let context = { + arr: [], + }; + + await resourcer.execute({ + resource: 'test', + action: 'list', + }, context); + + expect(context.arr).toEqual([ 2, 9, 10, 3 ]); + }); + + it('require module', async () => { + const resourcer = new Resourcer(); + + resourcer.define({ + name: 'test', + middleware: require('./middlewares/demo1'), + actions: { + list: { + handler: require('./actions/demo1'), + }, + }, + }); + + let context = { + arr: [], + }; + + await resourcer.execute({ + resource: 'test', + action: 'list', + }, context); + + expect(context.arr).toEqual([ 2, 9, 10, 3 ]); + }); +}); diff --git a/packages/resourcer/src/__tests__/resources/demo.ts b/packages/resourcer/src/__tests__/resources/demo.ts new file mode 100644 index 000000000..fa1534220 --- /dev/null +++ b/packages/resourcer/src/__tests__/resources/demo.ts @@ -0,0 +1,10 @@ +export default { + name: 'demo', + actions: { + async list(ctx, next) { + ctx.arr.push(1); + await next(); + ctx.arr.push(2); + }, + }, +}; \ No newline at end of file diff --git a/packages/resourcer/src/__tests__/utils.test.ts b/packages/resourcer/src/__tests__/utils.test.ts new file mode 100644 index 000000000..fd73d94c0 --- /dev/null +++ b/packages/resourcer/src/__tests__/utils.test.ts @@ -0,0 +1,195 @@ +import { parseRequest } from '..'; + +describe('utils', () => { + describe('parseRequest', () => { + it('index action', () => { + const params = parseRequest({ + path: '/posts', + method: 'GET', + }); + expect(params).toEqual({ resourceName: 'posts', actionName: 'list' }); + }); + + it('store action', () => { + const params = parseRequest({ + path: '/posts', + method: 'POST', + }); + expect(params).toEqual({ resourceName: 'posts', actionName: 'create' }); + }); + + it('get action', () => { + const params = parseRequest({ + path: '/posts/1', + method: 'GET', + }); + expect(params).toEqual({ resourceName: 'posts', resourceKey: '1', actionName: 'get' }); + }); + + it('update action', () => { + const params = parseRequest({ + path: '/posts/1', + method: 'PUT', + }); + expect(params).toEqual({ resourceName: 'posts', resourceKey: '1', actionName: 'update' }); + }); + + it('update action', () => { + const params = parseRequest({ + path: '/posts/1', + method: 'PATCH', + }); + expect(params).toEqual({ resourceName: 'posts', resourceKey: '1', actionName: 'update' }); + }); + + it('delete action', () => { + const params = parseRequest({ + path: '/posts/1', + method: 'delete', + }); + expect(params).toEqual({ resourceName: 'posts', resourceKey: '1', actionName: 'destroy' }); + }); + + it('delete action', () => { + const params = parseRequest({ + path: '/posts/1,2,3,4,5,6', + method: 'delete', + }); + expect(params).toEqual({ resourceName: 'posts', resourceKey: '1,2,3,4,5,6', actionName: 'destroy' }); + }); + + it('index action', () => { + const params = parseRequest({ + path: '/posts/1/comments', + method: 'GET', + }); + expect(params).toEqual({ resourceName: 'comments', associatedName: 'posts', associatedKey: '1', actionName: 'list' }); + }); + + it('store action', () => { + const params = parseRequest({ + path: '/posts/1/comments', + method: 'POST', + }); + expect(params).toEqual({ resourceName: 'comments', associatedName: 'posts', associatedKey: '1', actionName: 'create' }); + }); + + it('get action', () => { + const params = parseRequest({ + path: '/posts/1/comments/1', + method: 'GET', + }); + expect(params).toEqual({ resourceName: 'comments', resourceKey: '1', associatedName: 'posts', associatedKey: '1', actionName: 'get' }); + }); + + it('update action', () => { + const params = parseRequest({ + path: '/posts/1/comments/1', + method: 'PUT', + }); + expect(params).toEqual({ resourceName: 'comments', resourceKey: '1', associatedName: 'posts', associatedKey: '1', actionName: 'update' }); + }); + + it('update action', () => { + const params = parseRequest({ + path: '/posts/1/comments/1', + method: 'PATCH', + }); + expect(params).toEqual({ resourceName: 'comments', resourceKey: '1', associatedName: 'posts', associatedKey: '1', actionName: 'update' }); + }); + + it('get action', () => { + const params = parseRequest({ + path: '/posts/1/comments/1', + method: 'delete', + }); + expect(params).toEqual({ resourceName: 'comments', resourceKey: '1', associatedName: 'posts', associatedKey: '1', actionName: 'destroy' }); + }); + + it('export action', () => { + const params = parseRequest({ + path: '/posts:export', + method: 'GET', + }); + expect(params).toEqual({ resourceName: 'posts', actionName: 'export' }); + }); + + it('export action', () => { + const params = parseRequest({ + path: '/posts:export', + method: 'POST', + }); + expect(params).toEqual({ resourceName: 'posts', actionName: 'export' }); + }); + + it('export action', () => { + const params = parseRequest({ + path: '/posts:export/1', + method: 'POST', + }); + expect(params).toEqual({ resourceName: 'posts', resourceKey: '1', actionName: 'export' }); + }); + + it('attach action', () => { + const params = parseRequest({ + path: '/posts/1/tags:attach/2', + method: 'POST', + }); + expect(params).toEqual({ + resourceName: 'tags', + resourceKey: '2', + associatedKey: '1', + associatedName: 'posts', + actionName: 'attach', + }); + }); + + it('prefix options', () => { + const params = parseRequest({ + path: '/api/posts', + method: 'GET', + }, { + prefix: '/api' + }); + expect(params).toEqual({ resourceName: 'posts', actionName: 'list' }); + }); + + it('prefix options', () => { + const params = parseRequest({ + path: '/posts', + method: 'GET', + }, { + prefix: '/api' + }); + expect(params).toBeFalsy(); + }); + + it('actions options', () => { + const params = parseRequest({ + path: '/posts', + method: 'GET', + }, { + accessors: { + list: 'query' + } + }); + expect(params).toEqual({ resourceName: 'posts', actionName: 'query' }); + }); + + it('actions options', () => { + const params = parseRequest({ + path: '/posts:list', + method: 'GET', + }); + expect(params).toEqual({ resourceName: 'posts', actionName: 'list' }); + }); + + it('actions options', () => { + const params = parseRequest({ + path: '/user.posts:list', + method: 'GET', + }); + expect(params).toEqual({ associatedName: 'user', resourceName: 'posts', actionName: 'list' }); + }); + }); +}); \ No newline at end of file diff --git a/packages/resourcer/src/action.ts b/packages/resourcer/src/action.ts new file mode 100644 index 000000000..f4e43b564 --- /dev/null +++ b/packages/resourcer/src/action.ts @@ -0,0 +1,324 @@ +import _, { isArray } from 'lodash'; +import compose from 'koa-compose'; +import Resource from './resource'; +import { requireModule, mergeFields } from './utils'; +import { HandlerType } from './resourcer'; +import Middleware, { MiddlewareType } from './middleware'; + +export type ActionType = string | HandlerType | ActionOptions; + +export type DefaultActionType = 'list' | 'create' | 'get' | 'update' | 'destroy' | 'set' | 'add' | 'remove'; + +export type ActionName = DefaultActionType | Omit; + +export interface ActionContext { + action?: Action; + [key: string]: any; +} + +export type FieldsOptions = string[] | { + only?: string[]; + appends?: string[]; +} | { + except?: string[]; + appends?: string[]; +}; + +export type FieldsOptionsFn = (ctx: ActionContext) => FieldsOptions | Promise; + +/** + * 过滤参数 + * + * TODO:细节待定 + */ +export interface FilterOptions { + [key: string]: any; +} + +export type FilterOptionsFn = (ctx: ActionContext) => FilterOptions | Promise; + +export type ParamsCallback = (ctx: ActionContext) => ActionParams | Promise; + +export interface ActionOptions { + /** + * 默认数据 + */ + defaultValues?: any; + /** + * 字段 + * + * 示例一: + * ['col1', 'col2', 'relation.col1']; + * + * 示例二: + * { + * only: ['col1'], + * } + * + * 示例三: + * { + * except: ['col1'], + * } + */ + fields?: FieldsOptions; + /** + * 过滤 + */ + filter?: FilterOptions; + /** + * 排序 + */ + sort?: string | string[]; + /** + * 当前页码 + */ + page?: number; + /** + * 每页显示数量 + */ + perPage?: number; + /** + * 中间件 + */ + middleware?: MiddlewareType; + /** + * 中间件 + * + * 与 middleware 用法相同 + */ + middlewares?: MiddlewareType; + /** + * 当前 Action 待执行的方法 + * + * 支持 Function 和 require 调用 + */ + handler?: HandlerType; + /** + * 其他扩展配置 + */ + [key: string]: any; +} + +/** + * action params 与 action options 略有不同 + * - options 的参数更灵活,主要用于开发配置 + * - params 是开发配置参数 + 客户端参数的结合体 + */ +export interface ActionParams { + /** + * 输出哪些字段 + * + * 与 ActionOptions 的不同,这里的 fields 是 object,提供 only,except,appends 三种情况 + */ + fields?: { + only?: string[]; + except?: string[]; + appends?: string[]; + }; + /** + * 过滤 + */ + filter?: FilterOptions; + /** + * 排序 + * + * 与 ActionOptions 的不同,这里的 sort 只有一种 array 类型 + */ + sort?: string[]; + /** + * 当前页码 + */ + page?: number; + /** + * 每页显示数量 + */ + perPage?: number; + /** + * 数据,默认为 options.defaultValues + request.body + */ + values?: any; + /** + * 当前资源的主体,对应的表名或 Model 名称 + */ + resourceName?: string; + /** + * 资源标识符 + */ + resourceKey?: string; + /** + * 资源的从属关系 + */ + associatedName?: string; + /** + * 从属关系的标识符 + */ + associatedKey?: string; + /** + * 从属关系的当前实例 + */ + associated ?: any; + /** + * 资源提供哪些行为或方法 + */ + actionName?: string; + /** + * 其他扩展配置 + */ + [key: string]: any; +} + +export class Action { + + protected handler: any; + + protected resource: Resource; + + protected name: ActionName; + + protected options: ActionOptions; + + protected parameters: ActionParams = {}; + + protected context: ActionContext = {}; + + public readonly middlewares: Array = []; + + constructor(options: ActionOptions) { + options = requireModule(options); + if (typeof options === 'function') { + options = { handler: options }; + } + const { middleware, middlewares = [], handler } = options; + this.middlewares = Middleware.toInstanceArray(middleware || middlewares); + this.handler = handler; + this.options = options; + } + + get params(): ActionParams { + return this.parameters; + } + + clone() { + const options = _.cloneDeep(this.options); + delete options.middleware; + delete options.middlewares; + const action = new Action(options); + action.setName(this.name); + action.setResource(this.resource); + action.middlewares.push(...this.middlewares); + return action; + } + + setContext(context: any) { + this.context = context; + } + + setParam(key: string, value: any) { + if (/\[\]$/.test(key)) { + key = key.substr(0, key.length-2); + let values = _.get(this.parameters, key); + if (_.isArray(values)) { + values.push(value); + } else { + values = []; + values.push(value); + } + value = values; + } + _.set(this.parameters, key, value); + } + + async mergeParams(params: ActionParams) { + const { filter, fields, values, ...restPrams } = params; + let { filter: optionsFilter, fields: optionsFields } = this.options; + const options = _.omit(this.options, [ + 'defaultValues', 'filter', 'fields', 'handler', 'middlewares', 'middleware', + ]); + const data: ActionParams = { + ...options, + ...restPrams, + }; + if (!_.isEmpty(this.options.defaultValues) || !_.isEmpty(values)) { + data.values = _.merge(_.cloneDeep(this.options.defaultValues), values); + } + if (data.per_page) { + data.perPage = data.per_page; + } + // if (typeof optionsFilter === 'function') { + // this.parameters = _.cloneDeep(data); + // optionsFilter = await optionsFilter(this.context); + // } + if (!_.isEmpty(optionsFilter) || !_.isEmpty(filter)) { + data.filter = _.merge(filter, _.cloneDeep(optionsFilter)); + } + // if (typeof optionsFields === 'function') { + // this.parameters = _.cloneDeep(data); + // optionsFields = await optionsFields(this.context); + // } + if (!_.isEmpty(optionsFields) || !_.isEmpty(fields)) { + data.fields = mergeFields(optionsFields, fields); + } + this.parameters = _.cloneDeep(data); + } + + setResource(resource: Resource) { + this.resource = resource; + return this; + } + + getResource() { + return this.resource; + } + + getOptions(): ActionOptions { + return this.options; + } + + setName(name: ActionName) { + this.name = name; + return this; + } + + getName() { + return this.name; + } + + getMiddlewareHandlers() { + return this.middlewares + .filter(middleware => middleware.canAccess(this.name)) + .map(middleware => middleware.getHandler()); + } + + getHandler() { + const handler = requireModule(this.handler || this.resource.resourcer.getRegisteredHandler(this.name)); + if (typeof handler !== 'function') { + throw new Error('Handler must be a function!'); + } + return handler; + } + + getHandlers() { + return [...this.resource.resourcer.getMiddlewares(),...this.getMiddlewareHandlers(), this.getHandler()].filter(Boolean); + } + + async execute(context: any, next?: any) { + return await compose(this.getHandlers())(context, next); + } + + static toInstanceMap(actions: object, resource?: Resource) { + return new Map(Object.entries(actions).map(([key, options]) => { + let action: Action; + if (options instanceof Action) { + action = options; + } else { + action = new Action(options); + } + action.setName(key); + action.setResource(resource); + resource && action.middlewares.unshift(...resource.middlewares); + return [key, action]; + })); + } +} + +export default Action; \ No newline at end of file diff --git a/packages/resourcer/src/index.ts b/packages/resourcer/src/index.ts new file mode 100644 index 000000000..04a82c09f --- /dev/null +++ b/packages/resourcer/src/index.ts @@ -0,0 +1,9 @@ +import Resourcer from './resourcer'; + +export * from './utils'; +export * from './middleware'; +export * from './action'; +export * from './resource'; +export * from './resourcer'; + +export default Resourcer; \ No newline at end of file diff --git a/packages/resourcer/src/middleware.ts b/packages/resourcer/src/middleware.ts new file mode 100644 index 000000000..8419460f9 --- /dev/null +++ b/packages/resourcer/src/middleware.ts @@ -0,0 +1,78 @@ +import { ActionName } from './action'; +import { requireModule } from './utils'; +import { HandlerType } from './resourcer'; + +export type MiddlewareType = string | string[] | HandlerType | HandlerType[] + | MiddlewareOptions | MiddlewareOptions[]; + +export interface MiddlewareOptions { + + /** + * actions 白名单,默认有 list、get、create、update、delete + */ + only?: Array; + + /** + * actions 黑名单,默认有 list、get、create、update、delete + */ + except?: Array; + + handler?: HandlerType | Function; + + [key: string]: any; +} + +export class Middleware { + + protected options: MiddlewareOptions; + + constructor(options: MiddlewareOptions | Function) { + options = requireModule(options); + if (typeof options === 'function') { + this.options = { handler: options }; + } else { + this.options = options; + } + } + + getHandler() { + const handler = requireModule(this.options.handler); + if (typeof handler !== 'function') { + throw new Error('Handler must be a function!'); + } + return handler; + } + + canAccess(name: ActionName) { + const { only = [], except = [] } = this.options; + if (only.length > 0) { + return only.includes(name); + } + if (except.length > 0) { + return !except.includes(name); + } + return true; + } + + static toInstanceArray(middlewares: any): Middleware[] { + if (!middlewares) { + return []; + } + if (!Array.isArray(middlewares)) { + middlewares = [middlewares]; + } + return middlewares.map(middleware => { + if (middleware instanceof Middleware) { + return middleware; + } + if (typeof middleware === 'object') { + return new Middleware(middleware); + } + if (typeof middleware === 'function') { + return new Middleware({handler: middleware}); + } + }); + } +} + +export default Middleware; \ No newline at end of file diff --git a/packages/resourcer/src/resource.ts b/packages/resourcer/src/resource.ts new file mode 100644 index 000000000..4fdb4a0c6 --- /dev/null +++ b/packages/resourcer/src/resource.ts @@ -0,0 +1,105 @@ +import _ from 'lodash'; +import Resourcer from './resourcer'; +import Middleware, { MiddlewareType } from './middleware'; +import Action, { ActionName, ActionType } from './action'; + +export type ResourceType = 'single' | 'hasOne' | 'hasMany' | 'belongsTo' | 'belongsToMany'; + +export interface ResourceOptions { + + /** + * 资源名称 + */ + name: string; + + /** + * 资源类型,默认为 single + */ + type?: ResourceType; + + /** + * 资源的行为 + */ + actions?: { + [key: string]: ActionType; + }, + + /** + * actions 白名单,默认有 list、get、create、update、delete + */ + only?: Array; + + /** + * actions 黑名单,默认有 list、get、create、update、delete + */ + except?: Array; + + /** + * 中间件 + */ + middleware?: MiddlewareType; + + /** + * 中间件 + */ + middlewares?: MiddlewareType; + + /** + * 额外的一些参数 + */ + [key: string]: any; +} + +export class Resource { + + public readonly resourcer: Resourcer; + + public readonly middlewares: Middleware[]; + + public readonly actions = new Map(); + + public readonly options: ResourceOptions; + + public readonly except: Array; + + constructor(options: ResourceOptions, resourcer: Resourcer) { + const { middleware, middlewares, actions = {} } = options; + this.options = options; + this.resourcer = resourcer; + this.middlewares = Middleware.toInstanceArray(middleware || middlewares); + let excludes = []; + for (const [name, handler] of resourcer.getRegisteredHandlers()) { + if (!actions[name as string]) { + actions[name as string] = handler; + } + } + const { only = [], except = [] } = options; + if (except.length > 0) { + excludes = except; + } else if (only.length > 0) { + excludes = Object.keys(actions).filter(name => !only.includes(name)); + } + this.except = excludes; + this.actions = Action.toInstanceMap(_.omit(actions, excludes), this); + } + + getName() { + return this.options.name; + } + + getExcept() { + return this.except; + } + + getAction(action: ActionName) { + if (this.except.includes(action)) { + throw new Error(`${action} action is not allowed`); + } + if (!this.actions.has(action)) { + throw new Error(`${action} action does not exist`); + } + return this.actions.get(action); + } +} + +export default Resource; \ No newline at end of file diff --git a/packages/resourcer/src/resourcer.ts b/packages/resourcer/src/resourcer.ts new file mode 100644 index 000000000..914850f4c --- /dev/null +++ b/packages/resourcer/src/resourcer.ts @@ -0,0 +1,293 @@ +import qs from 'qs'; +import glob from 'glob'; +import compose from 'koa-compose'; +import Action, { ActionName } from './action'; +import Resource, { ResourceOptions } from './resource'; +import { parseRequest, getNameByParams, ParaseParams, requireModule } from './utils'; +import { pathToRegexp } from 'path-to-regexp'; + +export interface ResourcerContext { + resourcer?: Resourcer; + action?: Action; + params?: ParaseParams; + [key: string]: any; +} + +export interface KoaMiddlewareOptions { + + /** + * 前缀 + */ + prefix?: string; + + /** + * 自定义 resource name 的获取规则 + * + * 默认规则 relatedTable ? relatedTable.table : table + */ + nameRule?: (params: ParaseParams) => string; + + /** + * 上下文中的 key - ctx[paramsKey] + * + * 可以单独配置 paramsKey,默认为 params + */ + paramsKey?: string; + + /** + * 自定义 action name + * + * 默认为 + * + * - list 查看列表 + * - create 新增数据 + * - get 查看数据详情 + * - update 更新数据 + * - delete 删除数据 + */ + accessors?: { + + /** + * 查看列表 + */ + list?: string; + + /** + * 新增数据 + */ + create?: string; + + /** + * 查看数据详情 + */ + get?: string; + + /** + * 更新数据 + */ + update?: string; + + /** + * 删除数据 + */ + delete?: string; + }; +} + +export interface ExecuteOptions { + + /** + * 资源名称 + */ + resource: string; + + /** + * 自定义 action name + * + * 默认 + * - list 查看列表 + * - create 新增数据 + * - get 查看数据详情 + * - update 更新数据 + * - delete 删除数据 + */ + action: ActionName; +} + +export type HandlerType = (ctx: ResourcerContext, next: () => Promise) => any; + +export interface Handlers { + [key: string]: HandlerType; +} + +export interface ImportOptions { + + /** + * 指定配置所在路径 + */ + directory: string; + + /** + * 文件后缀,默认值 ['js', 'ts', 'json'] + */ + extensions?: string[]; +} + +export class Resourcer { + + protected resources = new Map(); + + /** + * 全局定义的 action handlers + */ + protected handlers = new Map(); + + protected paramsKey = 'params'; + + protected middlewares = []; + + /** + * 载入指定目录下的 resource 配置(配置的文件驱动) + * + * TODO: 配置的文件驱动现在会全部初始化,大数据时可能存在性能瓶颈,后续可以加入动态加载 + * + * @param {object} [options] + * @param {string} [options.directory] 指定配置所在路径 + * @param {array} [options.extensions = ['js', 'ts', 'json']] 文件后缀 + */ + public import(options: ImportOptions): Map { + const { extensions = ['js', 'ts', 'json'], directory } = options; + const patten = `${directory}/*.{${extensions.join(',')}}`; + const files = glob.sync(patten); + const resources = new Map(); + files.forEach((file: string) => { + if (file.endsWith('.d.ts')) { + return; + } + const options = requireModule(file); + const table = this.define(typeof options === 'function' ? options(this) : options); + resources.set(table.getName(), table); + }); + return resources; + } + + /** + * resource 配置 + * + * @param name + * @param options + */ + define(options: ResourceOptions) { + const { name } = options; + const resource = new Resource(options, this); + this.resources.set(name, resource); + return resource; + } + + /** + * 注册全局的 action handlers + * + * @param handlers + */ + registerHandlers(handlers: Handlers) { + this.handlers = new Map(Object.entries(handlers)); + } + + getRegisteredHandler(name: ActionName) { + return this.handlers.get(name); + } + + getRegisteredHandlers() { + return this.handlers; + } + + getResource(name: string): Resource { + if (!this.resources.has(name)) { + throw new Error(`${name} resource does not exist`); + } + return this.resources.get(name); + } + + getAction(name: string, action: ActionName): Action { + return this.getResource(name).getAction(action); + } + + getParamsKey() { + return this.paramsKey; + } + + getMiddlewares() { + return this.middlewares; + } + + use(middlewares: HandlerType | HandlerType[]) { + if (typeof middlewares === 'function') { + this.middlewares.push(middlewares); + } else if (Array.isArray(middlewares)) { + this.middlewares.push(...middlewares); + } + } + + middleware(options: KoaMiddlewareOptions = {}) { + const { prefix, accessors, paramsKey = 'params', nameRule = getNameByParams } = options; + return async (ctx: ResourcerContext, next: () => Promise) => { + ctx.resourcer = this; + let params = parseRequest({ + path: ctx.request.path, + method: ctx.request.method, + }, { + prefix, + accessors, + }); + if (!params) { + return next(); + } + try { + const resource = this.getResource(nameRule(params)); + // 为关系资源时,暂时需要再执行一遍 parseRequest + if (resource.options.type !== 'single') { + params = parseRequest({ + path: ctx.request.path, + method: ctx.request.method, + type: resource.options.type, + }, { + prefix, + accessors, + }); + if (!params) { + return next(); + } + } + // action 需要 clone 之后再赋给 ctx + ctx.action = this.getAction(nameRule(params), params.actionName).clone(); + ctx.action.setContext(ctx); + // 自带 query 处理的不太给力,需要用 qs 转一下 + const query = qs.parse(qs.stringify(ctx.query)); + // filter 支持 json string + if (typeof query.filter === 'string') { + query.filter = JSON.parse(query.filter); + } + // 兼容 ctx.params 的处理,之后的版本里会去掉 + ctx[paramsKey] = { + table: params.resourceName, + tableKey: params.resourceKey, + relatedTable: params.associatedName, + relatedKey: params.resourceKey, + action: params.actionName, + }; + if (pathToRegexp('/resourcer/{:associatedName.}?:resourceName{\\::actionName}').test(ctx.request.path)) { + await ctx.action.mergeParams({ + ...query, + ...params, + ...ctx.request.body, + }); + } else { + await ctx.action.mergeParams({ + ...query, + ...params, + values: ctx.request.body, + }); + } + return compose(ctx.action.getHandlers())(ctx, next); + } catch (error) { + return next(); + } + } + } + + /** + * 实验性 API + * + * @param options + * @param context + * @param next + */ + async execute(options: ExecuteOptions, context: ResourcerContext = {}, next?: any) { + const { resource, action } = options; + context.resourcer = this; + context.action = this.getAction(resource, action); + return await context.action.execute(context, next); + } +} + +export default Resourcer; \ No newline at end of file diff --git a/packages/resourcer/src/utils.ts b/packages/resourcer/src/utils.ts new file mode 100644 index 000000000..178e55ebe --- /dev/null +++ b/packages/resourcer/src/utils.ts @@ -0,0 +1,256 @@ +import _ from 'lodash'; +import { pathToRegexp } from 'path-to-regexp'; +import { ResourceType } from './resource'; + +export interface ParseRequest { + path: string; + method: string; + // 资源类型 + type?: ResourceType; +} + +export interface ParseOptions { + prefix?: string; + accessors?: { + list?: string; + create?: string; + get?: string; + update?: string; + delete?: string; + set?: string; + add?: string; + }; +} + +export interface ParaseParams { + actionName?: string; + resourceName?: string; + resourceKey?: string; + associatedName?: string; + associatedKey?: string; + // table: string; + // tableKey?: string | number; + // relatedTable?: string; + // relatedKey?: string | number; + // action?: ActionName, +} + +export function getNameByParams(params: ParaseParams): string { + const { resourceName, associatedName } = params; + return associatedName ? `${associatedName}.${resourceName}` : resourceName; +} + +export function parseRequest(request: ParseRequest, options: ParseOptions = {}): ParaseParams | false { + + const accessors = { + // 常规 actions + list: 'list', + create: 'create', + get: 'get', + update: 'update', + delete: 'destroy', + // associate 操作 + add: 'add', + set: 'set', + remove: 'remove', + ...(options.accessors || {}), + }; + const keys = []; + const regexp = pathToRegexp('/resourcer/{:associatedName.}?:resourceName{\\::actionName}', keys); + const matches = regexp.exec(request.path); + if (matches) { + const params = {}; + keys.forEach((obj, index) => { + if (matches[index+1] === undefined) { + return; + } + params[obj.name] = matches[index+1]; + }); + return params; + } + const defaults = { + single: { + '/:resourceName': { + get: accessors.list, + post: accessors.create, + }, + '/:resourceName/:resourceKey': { + get: accessors.get, + put: accessors.update, + patch: accessors.update, + delete: accessors.delete, + }, + '/:associatedName/:associatedKey/:resourceName': { + get: accessors.list, + post: accessors.create, + }, + '/:associatedName/:associatedKey/:resourceName/:resourceKey': { + get: accessors.get, + post: accessors.create, + put: accessors.update, + patch: accessors.update, + delete: accessors.delete, + }, + }, + hasOne: { + '/:associatedName/:associatedKey/:resourceName': { + get: accessors.get, + post: accessors.update, + put: accessors.update, + patch: accessors.update, + delete: accessors.delete, + }, + }, + hasMany: { + '/:associatedName/:associatedKey/:resourceName': { + get: accessors.list, + post: accessors.create, + }, + '/:associatedName/:associatedKey/:resourceName/:resourceKey': { + get: accessors.get, + post: accessors.create, + put: accessors.update, + patch: accessors.update, + delete: accessors.delete, + }, + }, + belongsTo: { + '/:associatedName/:associatedKey/:resourceName': { + get: accessors.get, + delete: accessors.remove, + }, + '/:associatedName/:associatedKey/:resourceName/:resourceKey': { + post: accessors.set, + }, + }, + belongsToMany: { + '/:associatedName/:associatedKey/:resourceName': { + get: accessors.list, + post: accessors.set, + }, + '/:associatedName/:associatedKey/:resourceName/:resourceKey': { + get: accessors.get, + post: accessors.add, + put: accessors.update, // Many to Many 的 update 是针对 through + patch: accessors.update, // Many to Many 的 update 是针对 through + delete: accessors.remove, + }, + }, + }; + + const params: ParaseParams = {}; + + let prefix = (options.prefix||'').trim(); + + if (prefix && !prefix.startsWith('/')) { + prefix = `/${prefix}`; + } + + const { type = 'single' } = request; + + for (const path in defaults[type]) { + const keys = []; + const regexp = pathToRegexp(`${prefix}${path}`, keys, {}); + const matches = regexp.exec(request.path); + if (!matches) { + continue; + } + keys.forEach((obj, index) => { + if (matches[index+1] === undefined) { + return; + } + params[obj.name] = matches[index+1]; + }); + params.actionName = _.get(defaults, [type, path, request.method.toLowerCase()]); + } + + if (Object.keys(params).length === 0) { + return false; + } + + if (params.resourceName) { + const [resourceName, actionName] = params.resourceName.split(':'); + if (actionName) { + params.resourceName = resourceName; + params.actionName = actionName; + } + } + + return params; +} + +export function requireModule(module: any) { + if (typeof module === 'string') { + module = require(module); + } + if (typeof module !== 'object') { + return module; + } + return module.__esModule ? module.default : module; +} + +export function paraseFields(fields: any) { + if (!fields) { + return {} + } + if (typeof fields === 'string') { + fields = fields.split(',').map(field => field.trim()); + } + if (Array.isArray(fields)) { + return { + only: fields, + appends: [], + } + } + if (fields.only && typeof fields.only === 'string') { + fields.only = fields.only.split(',').map(field => field.trim()); + } + if (fields.except && typeof fields.except === 'string') { + fields.except = fields.except.split(',').map(field => field.trim()); + } + if (fields.appends && typeof fields.appends === 'string') { + fields.appends = fields.appends.split(',').map(field => field.trim()); + } + return fields; +} + +export function mergeFields(defaults: any, inputs: any) { + let fields: any = {}; + defaults = paraseFields(defaults); + inputs = paraseFields(inputs); + if (inputs.only) { + // 前端提供 only,后端提供 only + if (defaults.only) { + fields.only = defaults.only.filter(field => inputs.only.includes(field)) + } + // 前端提供 only,后端提供 except,输出 only 排除 except + else if (defaults.except) { + fields.only = inputs.only.filter(field => !defaults.except.includes(field)) + } + // 前端提供 only,后端没有提供 only 或 except + else { + fields.only = inputs.only; + } + } else if (inputs.except) { + // 前端提供 except,后端提供 only,只输出 only 里排除 except 的字段 + if (defaults.only) { + fields.only = defaults.only.filter(field => !inputs.except.includes(field)) + } + // 前端提供 except,后端提供 except 或不提供,合并 except + else { + fields.except = _.uniq([...inputs.except, ...(defaults.except||[])]); + } + } + // 前端没提供 only 或 except + else { + fields = defaults; + } + // 如果前端提供了 appends + if (!_.isEmpty(inputs.appends)) { + fields.appends = _.uniq([...inputs.appends, ...(defaults.appends||[])]); + } + if (!fields.appends) { + fields.appends = []; + } + return fields; +} diff --git a/packages/ui/package.json b/packages/ui/package.json new file mode 100644 index 000000000..75de3a8b0 --- /dev/null +++ b/packages/ui/package.json @@ -0,0 +1,6 @@ +{ + "name": "@nocobase/ui", + "version": "0.3.0-alpha.0", + "main": "lib/index.js", + "license": "MIT" +} diff --git a/packages/ui/src/details/index.tsx b/packages/ui/src/details/index.tsx new file mode 100644 index 000000000..e69de29bb diff --git a/packages/ui/src/form/index.tsx b/packages/ui/src/form/index.tsx new file mode 100644 index 000000000..e69de29bb diff --git a/packages/ui/src/index.ts b/packages/ui/src/index.ts new file mode 100644 index 000000000..e69de29bb diff --git a/packages/ui/src/spin/index.tsx b/packages/ui/src/spin/index.tsx new file mode 100644 index 000000000..e69de29bb diff --git a/packages/ui/src/table/index.tsx b/packages/ui/src/table/index.tsx new file mode 100644 index 000000000..e69de29bb diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 000000000..d550df238 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "lib": [ "es5", "es6" ], + "module": "commonjs", + "target": "es6", + "allowJs": true, + "declaration": true, + "resolveJsonModule": true, + "esModuleInterop": true, + "sourceMap": true, + "baseUrl": "./", + "paths": { + } + }, + "exclude": [ + "dotenv.js", + "jest.config.js", + "node_modules" + ] +} \ No newline at end of file