diff --git a/.env.template b/.env.template deleted file mode 100644 index 4c8ede67baf33..0000000000000 --- a/.env.template +++ /dev/null @@ -1,7 +0,0 @@ -CHANGELOG_URL= -ENABLE_NEW_SETTING_UNSTABLE_API= -ENABLE_CAPTCHA= -CAPTCHA_SITE_KEY= -ENABLE_ENHANCE_SHARE_MODE= -ALLOW_LOCAL_WORKSPACE= -DEBUG_JOTAI= \ No newline at end of file diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index d1599299fa3cc..8a29d11c6403b 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -283,8 +283,7 @@ jobs: electron-install: false full-cache: true - name: Build Electron renderer - # always skip cache because its fast, and cache configuration is always changing - run: yarn build + run: yarn affine @affine/electron bundle env: DISTRIBUTION: desktop - name: zip web diff --git a/.gitignore b/.gitignore index a2a3f8492a044..cf88160ead31d 100644 --- a/.gitignore +++ b/.gitignore @@ -81,3 +81,9 @@ apps/web/next-routes.conf packages/frontend/templates/edgeless packages/frontend/core/public/static/templates + +# new dist folder +tsbuild/ +# script +af +affine diff --git a/.prettierignore b/.prettierignore index 6aa0948e1fdde..59b17bb746f0c 100644 --- a/.prettierignore +++ b/.prettierignore @@ -25,4 +25,5 @@ packages/frontend/templates/onboarding packages/backend/native/index.d.ts packages/frontend/native/index.d.ts packages/frontend/native/index.js -compose.yaml \ No newline at end of file +compose.yaml +*.gen.ts \ No newline at end of file diff --git a/eslint.config.mjs b/eslint.config.mjs index 04b636602900c..764923081b672 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -31,6 +31,7 @@ export default tseslint.config( '.yarn/**', '**/*.d.ts', '.github/**/*', + '**/*.gen.ts', 'packages/frontend/component/.storybook/**/*', 'packages/frontend/i18n/src/i18n-generated.ts', 'packages/frontend/i18n/src/i18n-completenesses.json', @@ -216,7 +217,7 @@ export default tseslint.config( }, }, { - files: ['packages/**/*.{ts,tsx}', 'tools/cli/**/*.{ts,tsx}'], + files: ['packages/**/*.{ts,tsx}', 'tools/**/*.ts'], rules: { '@typescript-eslint/no-floating-promises': [ 'error', diff --git a/oxlint.json b/oxlint.json index 45aa4926f8627..12cf04135cf14 100644 --- a/oxlint.json +++ b/oxlint.json @@ -5,7 +5,7 @@ "correctness": "error", "perf": "error" }, - "ignorePatterns": ["tools/cli/src/webpack/error-handler.js"], + "ignorePatterns": ["tools/scripts/src/webpack/error-handler.js"], "rules": { "import/named": "allow", "no-await-in-loop": "allow", @@ -167,7 +167,8 @@ "files": [ "*.{spec,test,e2e,stories}.{ts,tsx}", "tests/**/*.ts", - "packages/backend/server/tests/**/*.ts" + "packages/backend/server/tests/**/*.ts", + "tools/**.*" ], "rules": { "typescript/no-non-null-assertion": "off", diff --git a/package.json b/package.json index ba95f87de66c0..18fbb496fb8d6 100644 --- a/package.json +++ b/package.json @@ -17,12 +17,10 @@ "node": "<21.0.0" }, "scripts": { - "dev": "yarn workspace @affine/cli dev", - "build": "yarn workspace @affine/cli bundle", - "dev:electron": "yarn workspace @affine/electron dev", - "build:electron": "yarn nx build @affine/electron", - "build:server-native": "yarn nx run-many -t build -p @affine/server-native", - "start:web-static": "yarn workspace @affine/web static-server", + "affine": "yarn workspace @affine-tools/scripts affine", + "af": "yarn workspace @affine-tools/scripts af", + "dev": "yarn affine dev", + "build": "yarn affine build", "lint:eslint": "cross-env NODE_OPTIONS=\"--max-old-space-size=8192\" eslint --report-unused-disable-directives-severity=off . --cache", "lint:eslint:fix": "yarn lint:eslint --fix", "lint:prettier": "prettier --ignore-unknown --cache --check .", @@ -33,9 +31,8 @@ "test": "vitest --run", "test:ui": "vitest --ui", "test:coverage": "vitest run --coverage", - "typecheck": "tsc -b tsconfig.json", - "postinstall": "node ./scripts/check-version.mjs && yarn workspace @affine/i18n i18n-codegen gen && yarn husky install", - "prepare": "husky" + "typecheck": "tsc -b tsconfig.project.json --verbose", + "postinstall": "yarn affine codegen && yarn husky" }, "lint-staged": { "*": "prettier --write --ignore-unknown --cache", @@ -51,7 +48,7 @@ ] }, "devDependencies": { - "@affine/cli": "workspace:*", + "@affine-tools/scripts": "workspace:*", "@capacitor/cli": "^6.2.0", "@eslint/js": "^9.16.0", "@faker-js/faker": "^9.3.0", diff --git a/packages/frontend/admin/package.json b/packages/frontend/admin/package.json index 9f969a735ef71..fa8bd3a5b810a 100644 --- a/packages/frontend/admin/package.json +++ b/packages/frontend/admin/package.json @@ -62,7 +62,8 @@ "tailwindcss-animate": "^1.0.7" }, "scripts": { - "build": "cross-env DISTRIBUTION=admin yarn workspace @affine/cli bundle", + "build": "affine bundle", + "dev": "affine bundle --dev", "update-shadcn": "shadcn-ui add -p src/components/ui" }, "exports": { diff --git a/packages/frontend/admin/tailwind.config.js b/packages/frontend/admin/tailwind.config.js index ee3b0653cb636..f433890cff0f8 100644 --- a/packages/frontend/admin/tailwind.config.js +++ b/packages/frontend/admin/tailwind.config.js @@ -1,7 +1,8 @@ /** @type {import('tailwindcss').Config} */ module.exports = { darkMode: ['class'], - content: ['./src/**/*.{ts,tsx}'], + // TODO(@forehalo): we are not running webpack in admin dir + content: ['./packages/frontend/admin/src/**/*.{ts,tsx}'], prefix: '', theme: { container: { diff --git a/packages/frontend/apps/android/package.json b/packages/frontend/apps/android/package.json index d8e8832fd6635..97220a334ab7b 100644 --- a/packages/frontend/apps/android/package.json +++ b/packages/frontend/apps/android/package.json @@ -5,9 +5,8 @@ "private": true, "browser": "src/index.tsx", "scripts": { - "build": "cross-env DISTRIBUTION=android yarn workspace @affine/cli bundle", - "dev": "yarn workspace @affine/cli dev", - "static-server": "cross-env DISTRIBUTION=android yarn workspace @affine/cli dev --static" + "build": "affine bundle", + "dev": "affine bundle --dev" }, "dependencies": { "@affine/component": "workspace:*", diff --git a/packages/frontend/apps/electron/package.json b/packages/frontend/apps/electron/package.json index a20be5b11a5d8..acef6bbf29a0c 100644 --- a/packages/frontend/apps/electron/package.json +++ b/packages/frontend/apps/electron/package.json @@ -2,28 +2,23 @@ "name": "@affine/electron", "private": true, "version": "0.18.0", - "author": "toeverything", - "repository": { - "url": "https://github.com/toeverything/AFFiNE", - "type": "git" - }, - "description": "AFFiNE Desktop App", - "homepage": "https://github.com/toeverything/AFFiNE", + "main": "./dist/main.js", "scripts": { "start": "electron .", - "dev": "DEV_SERVER_URL=http://localhost:8080 node --loader ts-node/esm/transpile-only ./scripts/dev.ts", - "dev:prod": "yarn node --loader ts-node/esm/transpile-only scripts/dev.ts", - "build": "NODE_ENV=production node --loader ts-node/esm/transpile-only scripts/build-layers.ts", - "build:dev": "NODE_ENV=development node --loader ts-node/esm/transpile-only scripts/build-layers.ts", - "generate-assets": "node --loader ts-node/esm/transpile-only scripts/generate-assets.ts", - "package": "cross-env NODE_OPTIONS=\"--loader ts-node/esm/transpile-only\" electron-forge package", - "make": "cross-env NODE_OPTIONS=\"--loader ts-node/esm/transpile-only\" electron-forge make", - "make-squirrel": "node --loader ts-node/esm/transpile-only scripts/make-squirrel.ts", - "make-nsis": "node --loader ts-node/esm/transpile-only scripts/make-nsis.ts" + "dev": "cross-env DEV_SERVER_URL=http://localhost:8080 node ./scripts/dev.ts", + "dev:prod": "node ./scripts/dev.ts", + "build": "cross-env NODE_ENV=production node ./scripts/build-layers.ts", + "build:dev": "node ./scripts/build-layers.ts", + "bundle": "affine bundle", + "generate-assets": "node ./scripts/generate-assets.ts", + "package": "electron-forge package", + "make": "electron-forge make", + "make-squirrel": "node ./scripts/make-squirrel.ts", + "make-nsis": "node ./scripts/make-nsis.ts" }, - "main": "./dist/main.js", "devDependencies": { "@affine-test/kit": "workspace:*", + "@affine-tools/utils": "workspace:*", "@affine/component": "workspace:*", "@affine/core": "workspace:*", "@affine/electron-api": "workspace:*", diff --git a/packages/frontend/apps/electron/scripts/common.ts b/packages/frontend/apps/electron/scripts/common.ts index ddc10995088f0..5775953a16c13 100644 --- a/packages/frontend/apps/electron/scripts/common.ts +++ b/packages/frontend/apps/electron/scripts/common.ts @@ -1,8 +1,8 @@ import { resolve } from 'node:path'; import { fileURLToPath } from 'node:url'; -// eslint-disable-next-line @typescript-eslint/no-restricted-imports -import { getBuildConfig } from '@affine/cli/src/webpack/runtime-config'; +import { getBuildConfig } from '@affine-tools/utils/build-config'; +import { Workspace } from '@affine-tools/utils/workspace'; import { sentryEsbuildPlugin } from '@sentry/esbuild-plugin'; import type { BuildOptions, Plugin } from 'esbuild'; @@ -24,12 +24,10 @@ export const config = (): BuildOptions => { 'process.env.NODE_ENV': process.env.NODE_ENV, REPLACE_ME_BUILD_ENV: process.env.BUILD_TYPE ?? 'stable', ...Object.entries( - getBuildConfig({ - channel: (process.env.BUILD_TYPE as any) ?? 'canary', - distribution: 'desktop', + getBuildConfig(new Workspace().getPackage('@affine/electron'), { mode: process.env.NODE_ENV === 'production' ? 'production' : 'development', - static: false, + channel: (process.env.BUILD_TYPE as any) ?? 'canary', }) ).reduce( (def, [key, val]) => { diff --git a/packages/frontend/apps/electron/tsconfig.json b/packages/frontend/apps/electron/tsconfig.json index df1043bd00f99..2b455fb9eaf05 100644 --- a/packages/frontend/apps/electron/tsconfig.json +++ b/packages/frontend/apps/electron/tsconfig.json @@ -33,6 +33,9 @@ }, { "path": "../../../../tests/kit" + }, + { + "path": "../../../../tools/utils" } ], "ts-node": { diff --git a/packages/frontend/apps/electron/tsconfig.node.json b/packages/frontend/apps/electron/tsconfig.node.json index 2beac5166efc1..2fcb612fd0800 100644 --- a/packages/frontend/apps/electron/tsconfig.node.json +++ b/packages/frontend/apps/electron/tsconfig.node.json @@ -5,7 +5,7 @@ "target": "ESNext", "module": "ESNext", "resolveJsonModule": true, - "moduleResolution": "Node", + "moduleResolution": "bundler", "allowSyntheticDefaultImports": true, "noEmit": false, "outDir": "./lib/scripts", @@ -17,5 +17,10 @@ "ts-node": { "esm": true, "experimentalSpecifierResolution": "node" - } + }, + "references": [ + { + "path": "../../../../tools/utils" + } + ] } diff --git a/packages/frontend/apps/electron/webpack.config.ts b/packages/frontend/apps/electron/webpack.config.ts new file mode 100644 index 0000000000000..6c2c6e2fa1b06 --- /dev/null +++ b/packages/frontend/apps/electron/webpack.config.ts @@ -0,0 +1,7 @@ +// TODO(@forehalo): all packages would become 'module' +module.exports.config = { + entry: { + app: './renderer/index.tsx', + shell: './renderer/shell/index.tsx', + }, +}; diff --git a/packages/frontend/apps/ios/package.json b/packages/frontend/apps/ios/package.json index c3aa1b6c1f38a..78a99fec7c5e5 100644 --- a/packages/frontend/apps/ios/package.json +++ b/packages/frontend/apps/ios/package.json @@ -5,11 +5,10 @@ "private": true, "browser": "src/index.tsx", "scripts": { - "build": "cross-env DISTRIBUTION=ios yarn workspace @affine/cli bundle", - "dev": "yarn workspace @affine/cli dev", + "build": "affine bundle", + "dev": "affine bundle --dev", "sync": "yarn cap sync", - "sync:dev": "CAP_SERVER_URL=http://localhost:8080 yarn cap sync", - "static-server": "cross-env DISTRIBUTION=ios yarn workspace @affine/cli dev --static" + "sync:dev": "CAP_SERVER_URL=http://localhost:8080 yarn cap sync" }, "dependencies": { "@affine/component": "workspace:*", diff --git a/packages/frontend/apps/mobile/package.json b/packages/frontend/apps/mobile/package.json index ae1d786897b6a..f2d859da27ff3 100644 --- a/packages/frontend/apps/mobile/package.json +++ b/packages/frontend/apps/mobile/package.json @@ -5,9 +5,8 @@ "private": true, "browser": "src/index.tsx", "scripts": { - "build": "cross-env DISTRIBUTION=mobile yarn workspace @affine/cli bundle", - "dev": "yarn workspace @affine/cli dev", - "static-server": "cross-env DISTRIBUTION=mobile yarn workspace @affine/cli dev --static" + "build": "affine bundle", + "dev": "affine bundle --dev" }, "dependencies": { "@affine/component": "workspace:*", diff --git a/packages/frontend/apps/web/package.json b/packages/frontend/apps/web/package.json index b3a995920f089..d60092b257f6e 100644 --- a/packages/frontend/apps/web/package.json +++ b/packages/frontend/apps/web/package.json @@ -5,9 +5,8 @@ "private": true, "browser": "src/index.tsx", "scripts": { - "build": "cross-env DISTRIBUTION=web yarn workspace @affine/cli bundle", - "dev": "yarn workspace @affine/cli dev", - "static-server": "yarn workspace @affine/cli dev --static" + "build": "affine bundle", + "dev": "affine bundle --dev" }, "dependencies": { "@affine/component": "workspace:*", diff --git a/packages/frontend/component/.storybook/main.ts b/packages/frontend/component/.storybook/main.ts index ac46638d1d16a..9da8b74fc958e 100644 --- a/packages/frontend/component/.storybook/main.ts +++ b/packages/frontend/component/.storybook/main.ts @@ -3,7 +3,7 @@ import { StorybookConfig } from '@storybook/react-vite'; import { vanillaExtractPlugin } from '@vanilla-extract/vite-plugin'; import swc from 'unplugin-swc'; import { mergeConfig } from 'vite'; -import { getBuildConfig } from '@affine/cli/src/webpack/runtime-config'; +import { getBuildConfig } from '@affine-tools/scripts/src/webpack/runtime-config'; export default { stories: ['../src/ui/**/*.@(mdx|stories.@(js|jsx|ts|tsx))'], diff --git a/packages/frontend/component/package.json b/packages/frontend/component/package.json index bb20658c7bcee..9577f34adee44 100644 --- a/packages/frontend/component/package.json +++ b/packages/frontend/component/package.json @@ -20,7 +20,7 @@ "react-dom": "^19.0.0" }, "dependencies": { - "@affine/cli": "workspace:*", + "@affine-tools/scripts": "workspace:*", "@affine/debug": "workspace:*", "@affine/electron-api": "workspace:*", "@affine/graphql": "workspace:*", diff --git a/packages/frontend/core/public/robots.txt b/packages/frontend/core/public/robots.txt index 7b537c62f904c..66864824ccdb4 100644 --- a/packages/frontend/core/public/robots.txt +++ b/packages/frontend/core/public/robots.txt @@ -2,6 +2,7 @@ User-agent: * Allow: / +Allow: /api/workspaces/*/blobs/* Disallow: /oauth/* Disallow: /workspace/* Disallow: /public-workspace/* diff --git a/packages/frontend/core/tsconfig.node.json b/packages/frontend/core/tsconfig.node.json index acbcfbefb94c4..22e466d88025e 100644 --- a/packages/frontend/core/tsconfig.node.json +++ b/packages/frontend/core/tsconfig.node.json @@ -13,9 +13,6 @@ }, "include": [".webpack/*.ts"], "references": [ - { - "path": "../../../tools/cli" - }, { "path": "../../common/env" } diff --git a/packages/frontend/graphql/package.json b/packages/frontend/graphql/package.json index 1aad8f3c8f8ee..706ceb2418375 100644 --- a/packages/frontend/graphql/package.json +++ b/packages/frontend/graphql/package.json @@ -21,7 +21,7 @@ "vitest": "2.1.8" }, "scripts": { - "postinstall": "gql-gen --errors-only" + "build": "gql-gen --errors-only" }, "dependencies": { "@affine/env": "workspace:*", diff --git a/packages/frontend/templates/package.json b/packages/frontend/templates/package.json index 6646cb336f90a..698643e1259e2 100644 --- a/packages/frontend/templates/package.json +++ b/packages/frontend/templates/package.json @@ -4,7 +4,8 @@ "sideEffect": false, "version": "0.18.0", "scripts": { - "postinstall": "node ./build-edgeless.mjs && node ./build-stickers.mjs" + "build": "node ./build-edgeless.mjs && node ./build-stickers.mjs", + "postinstall": "yarn build" }, "type": "module", "exports": { diff --git a/scripts/setup/global.ts b/scripts/setup/global.ts index cedc216dd425e..cc268a8ccb7e8 100644 --- a/scripts/setup/global.ts +++ b/scripts/setup/global.ts @@ -1,12 +1,14 @@ -import { getBuildConfig } from '@affine/cli/src/webpack/runtime-config'; import { setupGlobal } from '@affine/env/global'; +import { getBuildConfig } from '@affine-tools/utils/build-config'; +import { Workspace } from '@affine-tools/utils/workspace'; -globalThis.BUILD_CONFIG = getBuildConfig({ - distribution: 'web', - mode: 'development', - channel: 'canary', - static: false, -}); +globalThis.BUILD_CONFIG = getBuildConfig( + new Workspace().getPackage('@affine/web'), + { + mode: 'development', + channel: 'canary', + } +); if (typeof window !== 'undefined') { window.location.search = '?prefixUrl=http://127.0.0.1:3010/'; diff --git a/tests/affine-cloud-copilot/playwright.config.ts b/tests/affine-cloud-copilot/playwright.config.ts index 4b6736354bba5..faff4e14eb90f 100644 --- a/tests/affine-cloud-copilot/playwright.config.ts +++ b/tests/affine-cloud-copilot/playwright.config.ts @@ -26,9 +26,8 @@ const config: PlaywrightTestConfig = { retries: 3, reporter: process.env.CI ? 'github' : 'list', webServer: [ - // Intentionally not building the web, reminds you to run it by yourself. { - command: 'yarn -T run start:web-static', + command: 'yarn run -T affine dev -p @affine/web', port: 8080, timeout: 120 * 1000, reuseExistingServer: !process.env.CI, @@ -37,7 +36,7 @@ const config: PlaywrightTestConfig = { }, }, { - command: 'yarn workspace @affine/server start', + command: 'yarn run -T affine dev -p @affine/server', port: 3010, timeout: 120 * 1000, reuseExistingServer: !process.env.CI, diff --git a/tests/affine-cloud/package.json b/tests/affine-cloud/package.json index 67995a5496ada..c7d0a614373fe 100644 --- a/tests/affine-cloud/package.json +++ b/tests/affine-cloud/package.json @@ -6,6 +6,7 @@ }, "devDependencies": { "@affine-test/kit": "workspace:*", + "@affine-tools/scripts": "workspace:*", "@playwright/test": "=1.49.1" }, "version": "0.18.0" diff --git a/tests/affine-cloud/playwright.config.ts b/tests/affine-cloud/playwright.config.ts index 68e8d852e5e3f..59d59729b62c6 100644 --- a/tests/affine-cloud/playwright.config.ts +++ b/tests/affine-cloud/playwright.config.ts @@ -26,9 +26,12 @@ const config: PlaywrightTestConfig = { retries: process.env.COPILOT ? 1 : 3, reporter: process.env.CI ? 'github' : 'list', webServer: [ - // Intentionally not building the web, reminds you to run it by yourself. { - command: 'yarn -T run start:web-static', + // TODO(@forehalo): + // in ci, all the target will be built, + // we could download the builds from archives + // and then run the web with simple http serve, it's will be faster + command: 'yarn run -T affine dev -p @affine/web', port: 8080, timeout: 120 * 1000, reuseExistingServer: !process.env.CI, @@ -37,12 +40,10 @@ const config: PlaywrightTestConfig = { }, }, { - command: 'yarn workspace @affine/server start', + command: 'yarn run -T affine dev -p @affine/server', port: 3010, timeout: 120 * 1000, reuseExistingServer: !process.env.CI, - stdout: 'pipe', - stderr: 'pipe', env: { DATABASE_URL: process.env.DATABASE_URL ?? diff --git a/tests/affine-desktop-cloud/playwright.config.ts b/tests/affine-desktop-cloud/playwright.config.ts index 3432035187049..7765e1bc628b6 100644 --- a/tests/affine-desktop-cloud/playwright.config.ts +++ b/tests/affine-desktop-cloud/playwright.config.ts @@ -23,7 +23,7 @@ const config: PlaywrightTestConfig = { webServer: [ // Intentionally not building the web, reminds you to run it by yourself. { - command: 'yarn -T run start:web-static', + command: 'yarn run -T affine dev -p @affine/web', port: 8080, timeout: 120 * 1000, reuseExistingServer: !process.env.CI, @@ -34,7 +34,7 @@ const config: PlaywrightTestConfig = { }, }, { - command: 'yarn workspace @affine/server start', + command: 'yarn run -T affine dev -p @affine/server', port: 3010, timeout: 120 * 1000, reuseExistingServer: !process.env.CI, diff --git a/tests/affine-desktop/playwright.config.ts b/tests/affine-desktop/playwright.config.ts index f6cd39740d55d..ffa4003023c48 100644 --- a/tests/affine-desktop/playwright.config.ts +++ b/tests/affine-desktop/playwright.config.ts @@ -41,7 +41,7 @@ if (process.env.DEV_SERVER_URL) { ); config.webServer = [ { - command: 'yarn run start:web-static', + command: 'yarn run -T affine dev -p @affine/web', port: 8080, timeout: 120 * 1000, reuseExistingServer: !process.env.CI, diff --git a/tests/affine-local/playwright.config.ts b/tests/affine-local/playwright.config.ts index 8337cb53b111a..7f68d6a403089 100644 --- a/tests/affine-local/playwright.config.ts +++ b/tests/affine-local/playwright.config.ts @@ -46,7 +46,7 @@ const config: PlaywrightTestConfig = { webServer: [ // Intentionally not building the web, reminds you to run it by yourself. { - command: 'yarn run start:web-static', + command: 'yarn run -T affine dev -p @affine/web', port: 8080, timeout: 120 * 1000, reuseExistingServer: !process.env.CI, diff --git a/tests/affine-mobile/playwright.config.ts b/tests/affine-mobile/playwright.config.ts index 6804256482284..76b381bc588f9 100644 --- a/tests/affine-mobile/playwright.config.ts +++ b/tests/affine-mobile/playwright.config.ts @@ -49,7 +49,7 @@ const config: PlaywrightTestConfig = { webServer: [ // Intentionally not building the web, reminds you to run it by yourself. { - command: 'yarn workspace @affine/mobile static-server', + command: 'yarn run -T affine dev -p @affine/mobile', port: 8080, timeout: 120 * 1000, reuseExistingServer: !process.env.CI, diff --git a/tools/cli/src/bin/build.ts b/tools/cli/src/bin/build.ts deleted file mode 100644 index 64113974a61dd..0000000000000 --- a/tools/cli/src/bin/build.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { spawn } from 'node:child_process'; - -import webpack from 'webpack'; - -import { getCwdFromDistribution } from '../config/cwd.cjs'; -import type { BuildFlags } from '../config/index.js'; -import { createWebpackConfig } from '../webpack/webpack.config.js'; - -// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -const buildType = process.env.BUILD_TYPE_OVERRIDE || process.env.BUILD_TYPE; - -if (process.env.BUILD_TYPE_OVERRIDE) { - process.env.BUILD_TYPE = process.env.BUILD_TYPE_OVERRIDE; -} - -const getChannel = () => { - switch (buildType) { - case 'canary': - case 'beta': - case 'stable': - case 'internal': - return buildType; - case '': - throw new Error('BUILD_TYPE is not set'); - default: { - throw new Error( - `BUILD_TYPE must be one of canary, beta, stable, internal, received [${buildType}]` - ); - } - } -}; - -let entry: BuildFlags['entry']; - -const { DISTRIBUTION = 'web' } = process.env; - -const cwd = getCwdFromDistribution(DISTRIBUTION); - -if (DISTRIBUTION === 'desktop') { - entry = { app: './index.tsx', shell: './shell/index.tsx' }; -} - -const flags = { - distribution: DISTRIBUTION as BuildFlags['distribution'], - mode: 'production', - channel: getChannel(), - coverage: process.env.COVERAGE === 'true', - entry, - static: false, -} satisfies BuildFlags; - -spawn('yarn', ['workspace', '@affine/i18n', 'build'], { - stdio: 'inherit', -}); - -// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -webpack(createWebpackConfig(cwd!, flags), (err, stats) => { - if (err) { - console.error(err); - process.exit(1); - } - if (stats?.hasErrors()) { - console.error(stats.toString('errors-only')); - process.exit(1); - } -}); diff --git a/tools/cli/src/bin/dev.ts b/tools/cli/src/bin/dev.ts deleted file mode 100644 index 8962d797e263b..0000000000000 --- a/tools/cli/src/bin/dev.ts +++ /dev/null @@ -1,146 +0,0 @@ -import { spawn } from 'node:child_process'; -import { existsSync } from 'node:fs'; -import { join } from 'node:path'; - -import * as p from '@clack/prompts'; -import { config } from 'dotenv'; -import webpack from 'webpack'; -import WebpackDevServer from 'webpack-dev-server'; - -import { getCwdFromDistribution, projectRoot } from '../config/cwd.cjs'; -import type { BuildFlags } from '../config/index.js'; -import { createWebpackConfig } from '../webpack/webpack.config.js'; - -const flags: BuildFlags = { - distribution: - (process.env.DISTRIBUTION as BuildFlags['distribution']) ?? 'web', - mode: 'development', - static: false, - channel: 'canary', - coverage: process.env.COVERAGE === 'true', -}; - -const files = ['.env', '.env.local']; - -for (const file of files) { - if (existsSync(join(projectRoot, file))) { - config({ - path: join(projectRoot, file), - }); - console.log(`${file} loaded`); - break; - } -} - -const buildFlags = process.argv.includes('--static') - ? { ...flags, static: true } - : ((await p.group( - { - distribution: () => - p.select({ - message: 'Distribution', - options: [ - { - value: 'web', - }, - { - value: 'desktop', - }, - { - value: 'admin', - }, - { - value: 'mobile', - }, - { - value: 'ios', - }, - ], - initialValue: 'web', - }), - mode: () => - p.select({ - message: 'Mode', - options: [ - { - value: 'development', - }, - { - value: 'production', - }, - ], - initialValue: 'development', - }), - channel: () => - p.select({ - message: 'Channel', - options: [ - { - value: 'canary', - }, - { - value: 'beta', - }, - { - value: 'stable', - }, - ], - initialValue: 'canary', - }), - coverage: () => - p.confirm({ - message: 'Enable coverage', - initialValue: process.env.COVERAGE === 'true', - }), - }, - { - onCancel: () => { - p.cancel('Operation cancelled.'); - process.exit(0); - }, - } - )) as BuildFlags); - -flags.distribution = buildFlags.distribution; -flags.mode = buildFlags.mode; -flags.channel = buildFlags.channel; -flags.coverage = buildFlags.coverage; -flags.static = buildFlags.static; -flags.entry = undefined; - -const cwd = getCwdFromDistribution(flags.distribution); - -process.env.DISTRIBUTION = flags.distribution; - -if (flags.distribution === 'desktop') { - flags.entry = { - app: join(cwd, 'index.tsx'), - shell: join(cwd, 'shell/index.tsx'), - }; -} - -console.info(flags); - -if (!flags.static) { - spawn('yarn', ['workspace', '@affine/i18n', 'dev'], { - stdio: 'inherit', - shell: true, - }); -} - -try { - // @ts-expect-error no types - await import('@affine/templates/build-edgeless'); - const config = createWebpackConfig(cwd, flags); - if (flags.static) { - config.watch = false; - } - const compiler = webpack(config); - // Start webpack - const devServer = new WebpackDevServer(config.devServer, compiler); - - await devServer.start(); -} catch (error) { - console.error('Error during build:', error); - process.exit(1); -} diff --git a/tools/cli/src/config/cwd.cjs b/tools/cli/src/config/cwd.cjs deleted file mode 100644 index b01b774455bcb..0000000000000 --- a/tools/cli/src/config/cwd.cjs +++ /dev/null @@ -1,38 +0,0 @@ -// @ts-check - -const { join } = require('node:path'); - -const projectRoot = join(__dirname, '../../../..'); - -module.exports.projectRoot = projectRoot; - -/** - * - * @param {string | undefined} distribution - * @returns string - */ -module.exports.getCwdFromDistribution = function getCwdFromDistribution( - distribution -) { - switch (distribution) { - case 'web': - case undefined: - case null: - return join(projectRoot, 'packages/frontend/apps/web'); - case 'desktop': - return join(projectRoot, 'packages/frontend/apps/electron/renderer'); - case 'admin': - return join(projectRoot, 'packages/frontend/admin'); - case 'mobile': - return join(projectRoot, 'packages/frontend/apps/mobile'); - case 'ios': - return join(projectRoot, 'packages/frontend/apps/ios'); - case 'android': - return join(projectRoot, 'packages/frontend/apps/android'); - default: { - throw new Error( - 'DISTRIBUTION must be one of web, desktop, admin, mobile' - ); - } - } -}; diff --git a/tools/cli/src/config/index.ts b/tools/cli/src/config/index.ts deleted file mode 100644 index f4452c5da070a..0000000000000 --- a/tools/cli/src/config/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -export type BuildFlags = { - distribution: 'web' | 'desktop' | 'admin' | 'mobile' | 'ios' | 'android'; - mode: 'development' | 'production'; - channel: 'stable' | 'beta' | 'canary' | 'internal'; - static: boolean; - coverage?: boolean; - localBlockSuite?: string; - entry?: string | { [key: string]: string }; -}; diff --git a/tools/cli/src/util/infra.ts b/tools/cli/src/util/infra.ts deleted file mode 100644 index 4e88c9ae8d816..0000000000000 --- a/tools/cli/src/util/infra.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { spawn } from 'node:child_process'; -import { resolve } from 'node:path'; - -import { build } from 'vite'; - -import { projectRoot } from '../config/cwd.cjs'; - -const infraFilePath = resolve( - projectRoot, - 'packages', - 'infra', - 'vite.config.ts' -); -export const buildInfra = async () => { - await build({ - configFile: infraFilePath, - }); -}; - -export const watchInfra = async () => { - spawn('vite', ['build', '--watch'], { - cwd: resolve(projectRoot, 'packages/common/infra'), - shell: true, - stdio: 'inherit', - }); -}; diff --git a/tools/cli/src/webpack/postcss.config.cjs b/tools/cli/src/webpack/postcss.config.cjs deleted file mode 100644 index 9bc817f28a73f..0000000000000 --- a/tools/cli/src/webpack/postcss.config.cjs +++ /dev/null @@ -1,47 +0,0 @@ -const { join } = require('node:path'); - -const cssnano = require('cssnano'); -const tailwindcss = require('tailwindcss'); -const autoprefixer = require('autoprefixer'); - -const { getCwdFromDistribution } = require('../config/cwd.cjs'); - -const projectCwd = getCwdFromDistribution(process.env.DISTRIBUTION); - -const twConfig = (function () { - try { - const config = require(`${projectCwd}/tailwind.config.js`); - const { content } = config; - if (Array.isArray(content)) { - config.content = content.map(c => - c.startsWith(projectCwd) ? c : join(projectCwd, c) - ); - } - return config; - } catch { - return null; - } -})(); - -module.exports = function (context) { - const plugins = [ - cssnano({ - preset: [ - 'default', - { - convertValues: false, - }, - ], - }), - ]; - - if (twConfig) { - plugins.push(tailwindcss(twConfig), autoprefixer()); - } - - return { - from: context.from, - plugins, - to: context.to, - }; -}; diff --git a/tools/cli/src/webpack/webpack.config.ts b/tools/cli/src/webpack/webpack.config.ts deleted file mode 100644 index 290ce1fc386cc..0000000000000 --- a/tools/cli/src/webpack/webpack.config.ts +++ /dev/null @@ -1,183 +0,0 @@ -import { execSync } from 'node:child_process'; -import { readFileSync } from 'node:fs'; -import { join, resolve } from 'node:path'; - -import type { BuildFlags } from '@affine/cli/config'; -import { Repository } from '@napi-rs/simple-git'; -import HTMLPlugin from 'html-webpack-plugin'; -import { once } from 'lodash-es'; -import type { Compiler } from 'webpack'; -import webpack from 'webpack'; -import { merge } from 'webpack-merge'; - -import { - createConfiguration, - getPublicPath, - rootPath, - workspaceRoot, -} from './config.js'; -import { getBuildConfig } from './runtime-config.js'; - -const DESCRIPTION = `There can be more than Notion and Miro. AFFiNE is a next-gen knowledge base that brings planning, sorting and creating all together.`; - -const gitShortHash = once(() => { - const { GITHUB_SHA } = process.env; - if (GITHUB_SHA) { - return GITHUB_SHA.substring(0, 9); - } - const repo = new Repository(workspaceRoot); - const shortSha = repo.head().target()?.substring(0, 9); - if (shortSha) { - return shortSha; - } - const sha = execSync(`git rev-parse --short HEAD`, { - encoding: 'utf-8', - }).trim(); - return sha; -}); - -export function createWebpackConfig(cwd: string, flags: BuildFlags) { - console.log('build flags', flags); - const runtimeConfig = getBuildConfig(flags); - console.log('BUILD_CONFIG', runtimeConfig); - const config = createConfiguration(cwd, flags, runtimeConfig); - const entry = - typeof flags.entry === 'string' || !flags.entry - ? { - app: flags.entry ?? resolve(cwd, 'src/index.tsx'), - } - : flags.entry; - - const publicPath = getPublicPath(flags); - const cdnOrigin = publicPath.startsWith('/') - ? undefined - : new URL(publicPath).origin; - - const globalErrorHandler = [ - 'js/global-error-handler.js', - readFileSync( - join(workspaceRoot, 'tools/cli/src/webpack/error-handler.js'), - 'utf-8' - ), - ]; - - const templateParams = { - GIT_SHORT_SHA: gitShortHash(), - DESCRIPTION, - PRECONNECT: cdnOrigin - ? `` - : '', - VIEWPORT_FIT: - flags.distribution === 'mobile' || - flags.distribution === 'ios' || - flags.distribution === 'android' - ? 'cover' - : 'auto', - }; - - const createHTMLPlugins = (entryName: string) => { - const htmlPluginOptions = { - template: join(rootPath, 'webpack', 'template.html'), - inject: 'body', - filename: 'index.html', - minify: false, - templateParameters: templateParams, - chunks: [entryName], - } satisfies HTMLPlugin.Options; - - if (entryName === 'app') { - return [ - { - apply(compiler: Compiler) { - compiler.hooks.compilation.tap( - 'assets-manifest-plugin', - compilation => { - HTMLPlugin.getHooks(compilation).beforeAssetTagGeneration.tap( - 'assets-manifest-plugin', - arg => { - if ( - flags.distribution !== 'desktop' && - !compilation.getAsset(globalErrorHandler[0]) - ) { - compilation.emitAsset( - globalErrorHandler[0], - new webpack.sources.RawSource(globalErrorHandler[1]) - ); - arg.assets.js.unshift( - arg.assets.publicPath + globalErrorHandler[0] - ); - } - - if (!compilation.getAsset('assets-manifest.json')) { - compilation.emitAsset( - globalErrorHandler[0], - new webpack.sources.RawSource(globalErrorHandler[1]) - ); - compilation.emitAsset( - `assets-manifest.json`, - new webpack.sources.RawSource( - JSON.stringify( - { - ...arg.assets, - js: arg.assets.js.map(file => - file.substring(arg.assets.publicPath.length) - ), - css: arg.assets.css.map(file => - file.substring(arg.assets.publicPath.length) - ), - gitHash: templateParams.GIT_SHORT_SHA, - description: templateParams.DESCRIPTION, - }, - null, - 2 - ) - ), - { - immutable: false, - } - ); - } - - return arg; - } - ); - } - ); - }, - }, - new HTMLPlugin({ - ...htmlPluginOptions, - publicPath, - meta: { - 'env:publicPath': publicPath, - }, - }), - // selfhost html - new HTMLPlugin({ - ...htmlPluginOptions, - meta: { - 'env:isSelfHosted': 'true', - 'env:publicPath': '/', - }, - filename: 'selfhost.html', - templateParameters: { - ...htmlPluginOptions.templateParameters, - PRECONNECT: '', - }, - }), - ]; - } else { - return [ - new HTMLPlugin({ - ...htmlPluginOptions, - filename: `${entryName}.html`, - }), - ]; - } - }; - - return merge(config, { - entry, - plugins: Object.keys(entry).map(createHTMLPlugins).flat(), - }); -} diff --git a/tools/cli/tsconfig.json b/tools/cli/tsconfig.json deleted file mode 100644 index 796d7ab24f54a..0000000000000 --- a/tools/cli/tsconfig.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "extends": "../../tsconfig.json", - "compilerOptions": { - "composite": true, - "allowJs": true, - "module": "ESNext", - "moduleResolution": "Node", - "outDir": "lib" - }, - "include": ["src", "package.json"], - "references": [{ "path": "../../packages/common/env" }] -} diff --git a/tools/commitlint/.commitlintrc.json b/tools/commitlint/.commitlintrc.json index e3ef1467c514a..8be259393347f 100644 --- a/tools/commitlint/.commitlintrc.json +++ b/tools/commitlint/.commitlintrc.json @@ -14,19 +14,17 @@ "ios", "android", "docs", - "storybook", "component", - "workspace", "env", "graphql", - "cli", "hooks", "i18n", "native", "templates", "debug", "nbstore", - "infra" + "infra", + "tools" ] ] } diff --git a/tools/scripts/README.md b/tools/scripts/README.md new file mode 100644 index 0000000000000..f5fd4153ec166 --- /dev/null +++ b/tools/scripts/README.md @@ -0,0 +1,74 @@ +# AFFiNE Monorepo scripts + +## Start + +```bash +yarn affine -h +``` + +### Run build command defined in package.json + +```bash +yarn affine i18n build +# or +yarn build -p i18n +``` + +### Run dev command defined in package.json + +```bash +yarn affine web dev +# or +yarn dev -p i18n +``` + +### Clean + +```bash +yarn affine clean --ts --dist --rust +# clean node_modules +yarn affine clean --node-modules +``` + +## Tricks + +### Short your key presses + +```bash +# af is also available for running the scripts +yarn af web build +``` + +#### by custom shell script + +> personally, I use 'af', and only demoed in macos. + +create file `af` in the root of AFFiNE project with the following content + +```bash +#!/usr/bin/env sh +./tools/scripts/bin/runner.js affine.ts $@ +``` + +and give it executable permission + +```bash +chmod a+x ./af + +# now you can run scripts with simply +./af web build +``` + +if you want to go further, but for vscode(or other forks) only, add the following to your `.vscode/settings.json` + +```json +"terminal.integrated.env.osx": { + "PATH": ".:$PATH" +} +``` + +restart all the integrated terminals and now you get: + +```bash +af web build +``` diff --git a/tools/scripts/bin/runner.js b/tools/scripts/bin/runner.js new file mode 100755 index 0000000000000..8f10896225fea --- /dev/null +++ b/tools/scripts/bin/runner.js @@ -0,0 +1,68 @@ +#!/usr/bin/env node +import { spawn } from 'node:child_process'; +import { existsSync } from 'node:fs'; +import { join } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const scriptsFolder = join(fileURLToPath(import.meta.url), '..', '..'); +const scriptsSrcFolder = join(scriptsFolder, 'src'); +const projectRoot = join(scriptsFolder, '..', '..'); +const loader = join(scriptsFolder, 'loader.js'); + +const [node, _self, file, ...options] = process.argv; + +if (!file) { + console.error(`Please provide a file to run, e.g. 'run src/index.{js/ts}'`); + process.exit(1); +} + +const fileLocationCandidates = new Set([ + process.cwd(), + scriptsSrcFolder, + projectRoot, +]); +const lookups = []; + +/** + * @type {string | undefined} + */ +let scriptLocation; +for (const location of fileLocationCandidates) { + const fileCandidates = [file, `${file}.js`, `${file}.ts`]; + for (const candidate of fileCandidates) { + const candidateLocation = join(location, candidate); + if (existsSync(candidateLocation)) { + scriptLocation = candidateLocation; + break; + } + lookups.push(candidateLocation); + } +} + +if (!scriptLocation) { + console.error( + `File ${file} not found, please make sure the first parameter passed to 'run' script is a valid js or ts file.` + ); + console.error(`Searched locations: `); + lookups.forEach(location => { + console.error(` - ${location}`); + }); + process.exit(1); +} + +const nodeOptions = []; + +if ( + scriptLocation.endsWith('.ts') || + scriptLocation.startsWith(scriptsFolder) +) { + nodeOptions.unshift(`--import=${loader}`); +} + +nodeOptions.unshift('--experimental-specifier-resolution=node'); + +spawn(node, [...nodeOptions, scriptLocation, ...options], { + stdio: 'inherit', +}).on('exit', code => { + process.exit(code); +}); diff --git a/tools/scripts/loader.js b/tools/scripts/loader.js new file mode 100644 index 0000000000000..57272fa69dbd0 --- /dev/null +++ b/tools/scripts/loader.js @@ -0,0 +1,4 @@ +import { register } from 'node:module'; +import { pathToFileURL } from 'node:url'; + +register('ts-node/esm/transpile-only.mjs', pathToFileURL('./')); diff --git a/tools/cli/package.json b/tools/scripts/package.json similarity index 69% rename from tools/cli/package.json rename to tools/scripts/package.json index ee11989048b11..9f3196db5e47d 100644 --- a/tools/cli/package.json +++ b/tools/scripts/package.json @@ -1,27 +1,34 @@ { - "name": "@affine/cli", + "name": "@affine-tools/scripts", + "version": "0.0.1", "type": "module", "private": true, + "bin": { + "r": "./bin/runner.js" + }, + "exports": { + "./loader": "./loader.js" + }, + "scripts": { + "affine": "r ./src/affine.ts" + }, "devDependencies": { - "@affine/env": "workspace:*", - "@affine/templates": "workspace:*", + "@affine-tools/utils": "workspace:*", "@aws-sdk/client-s3": "^3.709.0", - "@blocksuite/affine": "0.19.3", - "@clack/core": "^0.3.5", - "@clack/prompts": "^0.8.2", - "@magic-works/i18n-codegen": "^0.6.1", "@napi-rs/simple-git": "^0.1.19", "@perfsee/webpack": "^1.13.0", "@pmmmwh/react-refresh-webpack-plugin": "^0.5.15", "@sentry/webpack-plugin": "^2.22.7", + "@types/lodash-es": "^4.17.12", "@types/mime-types": "^2.1.4", + "@types/node": "^20.17.10", "@types/webpack-env": "^1.18.5", "@vanilla-extract/webpack-plugin": "^2.3.15", "autoprefixer": "^10.4.20", + "clipanion": "^3.2.1", "copy-webpack-plugin": "^12.0.2", "css-loader": "^7.1.2", "cssnano": "^7.0.6", - "dotenv": "^16.4.7", "html-webpack-plugin": "^5.6.3", "lodash-es": "^4.17.21", "mime-types": "^2.1.35", @@ -35,14 +42,10 @@ "tailwindcss": "^3.4.16", "terser-webpack-plugin": "^5.3.10", "ts-node": "^10.9.2", - "vite": "^6.0.3", + "typanion": "^3.14.0", + "typescript": "^5.5.4", "webpack": "^5.97.1", "webpack-dev-server": "^5.2.0", "webpack-merge": "^6.0.1" - }, - "scripts": { - "bundle": "node --loader ts-node/esm/transpile-only.mjs ./src/bin/build.ts", - "dev": "node --loader ts-node/esm/transpile-only.mjs ./src/bin/dev.ts" - }, - "version": "0.18.0" + } } diff --git a/tools/scripts/src/affine.ts b/tools/scripts/src/affine.ts new file mode 100644 index 0000000000000..d69acfaabe6eb --- /dev/null +++ b/tools/scripts/src/affine.ts @@ -0,0 +1,32 @@ +import { Workspace } from '@affine-tools/utils/workspace'; +import { Cli } from 'clipanion'; + +import { BuildCommand } from './build'; +import { BundleCommand } from './bundle'; +import { CleanCommand } from './clean'; +import { CodegenCommand } from './codegen'; +import type { CliContext } from './context'; +import { DevCommand } from './dev'; +import { RunCommand } from './run'; + +const cli = new Cli({ + binaryName: 'affine', + binaryVersion: '0.0.0', + binaryLabel: 'AFFiNE Monorepo Tools', + enableColors: true, + enableCapture: true, +}); + +cli.register(RunCommand); +cli.register(CodegenCommand); +cli.register(CleanCommand); +cli.register(BuildCommand); +cli.register(DevCommand); +cli.register(BundleCommand); + +await cli.runExit(process.argv.slice(2), { + workspace: new Workspace(), + stdin: process.stdin, + stdout: process.stdout, + stderr: process.stderr, +}); diff --git a/tools/scripts/src/build.ts b/tools/scripts/src/build.ts new file mode 100644 index 0000000000000..94337b55d3ad3 --- /dev/null +++ b/tools/scripts/src/build.ts @@ -0,0 +1,15 @@ +import { PackageCommand } from './command'; + +export class BuildCommand extends PackageCommand { + static override paths = [['build'], ['b']]; + + async execute() { + const args = ['affine build', this.package]; + + if (this.deps) { + args.push('--deps'); + } + + await this.cli.run(args); + } +} diff --git a/tools/scripts/src/bundle.ts b/tools/scripts/src/bundle.ts new file mode 100644 index 0000000000000..933000c03f957 --- /dev/null +++ b/tools/scripts/src/bundle.ts @@ -0,0 +1,93 @@ +import webpack, { type Compiler, type Configuration } from 'webpack'; +import WebpackDevServer from 'webpack-dev-server'; +import { merge } from 'webpack-merge'; + +import { Option, PackageCommand } from './command'; +import { createWebpackConfig } from './webpack'; + +function getChannel() { + const channel = process.env.BUILD_TYPE ?? 'canary'; + switch (channel) { + case 'canary': + case 'beta': + case 'stable': + case 'internal': + return channel; + default: { + throw new Error( + `BUILD_TYPE must be one of canary, beta, stable, internal, received [${channel}]` + ); + } + } +} + +export class BundleCommand extends PackageCommand { + static override paths = [['bundle'], ['webpack'], ['pack'], ['bun']]; + + // bundle is not able to run with deps + override deps = false; + + dev = Option.Boolean('--dev,-d', false, { + description: 'Run in Development mode', + }); + + async execute() { + this.logger.info(`Packing package ${this.package}...`); + + const config = await this.getConfig(); + + const compiler = webpack(config); + + if (this.dev) { + await this.start(compiler, config.devServer); + } else { + await this.build(compiler); + } + } + + async getConfig() { + let config = createWebpackConfig(this.workspace.getPackage(this.package), { + mode: this.dev ? 'development' : 'production', + channel: getChannel(), + }); + + let configOverride: Configuration | undefined; + const overrideConfigPath = this.workspace + .getPackage(this.package) + .join('webpack.config.ts'); + + if (overrideConfigPath.isFile()) { + const override = await import(overrideConfigPath.value); + configOverride = override.config ?? override.default; + } + + if (configOverride) { + config = merge(config, configOverride); + } + + return config; + } + + async start(compiler: Compiler, config: Configuration['devServer']) { + const devServer = new WebpackDevServer(config, compiler); + + await devServer.start(); + } + + async build(compiler: Compiler) { + compiler.run((error, stats) => { + if (error) { + console.error(error); + process.exit(1); + } + if (stats) { + if (stats.hasErrors()) { + console.error(stats.toString('errors-only')); + process.exit(1); + } else { + console.log(stats.toString('minimal')); + } + } + }); + } +} diff --git a/tools/scripts/src/clean.ts b/tools/scripts/src/clean.ts new file mode 100644 index 0000000000000..b963907032cfb --- /dev/null +++ b/tools/scripts/src/clean.ts @@ -0,0 +1,84 @@ +import { rmSync } from 'node:fs'; + +import { exec } from '@affine-tools/utils/process'; + +import { Command, Option } from './command'; + +export class CleanCommand extends Command { + static override paths = [['clean']]; + + cleanTsBuild = Option.Boolean('--ts', false); + cleanDist = Option.Boolean('--dist', false); + cleanRustTarget = Option.Boolean('--rust', false); + cleanNodeModules = Option.Boolean('--node-modules', false); + all = Option.Boolean('--all,-a', false); + + async execute() { + this.logger.info('Cleaning Workspace...'); + if (this.all || this.cleanNodeModules) { + this.doCleanNodeModules(); + } + + if (this.all || this.cleanDist) { + this.doCleanDist(); + } + + if (this.all || this.cleanRustTarget) { + this.doCleanRust(); + } + + if (this.all || this.cleanTsBuild) { + this.doCleanTs(); + } + } + + doCleanNodeModules() { + this.logger.info('Cleaning node_modules...'); + + const rootNodeModules = this.workspace.join('node_modules'); + if (rootNodeModules.isDirectory()) { + this.logger.info(`Cleaning ${rootNodeModules}`); + rmSync(rootNodeModules.value, { recursive: true }); + } + + this.workspace.forEach(pkg => { + const nodeModules = pkg.nodeModulesPath; + if (nodeModules.isDirectory()) { + this.logger.info(`Cleaning ${nodeModules}`); + rmSync(nodeModules.value, { recursive: true }); + } + }); + + this.logger.info('node_modules cleaned'); + } + + doCleanTs() { + this.logger.info('Cleaning ts build outputs...'); + + this.workspace.forEach(pkg => { + if (pkg.tsbuildPath.isDirectory()) { + this.logger.info(`Cleaning ${pkg.tsbuildPath}`); + rmSync(pkg.tsbuildPath.value, { recursive: true }); + } + }); + + this.logger.info('ts build outputs cleaned'); + } + + doCleanDist() { + this.logger.info('Cleaning dist...'); + + this.workspace.forEach(pkg => { + if (pkg.distPath.isDirectory()) { + this.logger.info(`Cleaning ${pkg.distPath}`); + rmSync(pkg.distPath.value, { recursive: true }); + } + }); + + this.logger.info('dist cleaned'); + } + + doCleanRust() { + exec('', 'cargo clean'); + } +} diff --git a/tools/scripts/src/codegen.ts b/tools/scripts/src/codegen.ts new file mode 100755 index 0000000000000..35a7946d3fe80 --- /dev/null +++ b/tools/scripts/src/codegen.ts @@ -0,0 +1,35 @@ +import { writeFileSync } from 'node:fs'; + +import type { Path } from '@affine-tools/utils/path'; + +import { Command } from './command'; + +export class CodegenCommand extends Command { + static override paths = [['codegen'], ['gg'], ['g']]; + + async execute() { + this.logger.info('Generating Workspace configs'); + this.generateWorkspaceFiles(); + this.logger.info('Workspace configs generated'); + } + + generateWorkspaceFiles() { + const filesToGenerate: [Path, () => string][] = [ + [ + this.workspace.join('tsconfig.project.json'), + this.workspace.genProjectTsConfig, + ], + [ + this.workspace + .getPackage('@affine-tools/utils') + .join('src/workspace.gen.ts'), + this.workspace.genWorkspaceInfo, + ], + ]; + + for (const [path, content] of filesToGenerate) { + this.logger.info(`Output: ${path}`); + writeFileSync(path.value, content.apply(this.workspace)); + } + } +} diff --git a/tools/scripts/src/command.ts b/tools/scripts/src/command.ts new file mode 100644 index 0000000000000..ab5b54da14c06 --- /dev/null +++ b/tools/scripts/src/command.ts @@ -0,0 +1,71 @@ +import { AliasToPackage } from '@affine-tools/utils/distribution'; +import { Logger } from '@affine-tools/utils/logger'; +import { type PackageName, Workspace } from '@affine-tools/utils/workspace'; +import { Command as BaseCommand, Option } from 'clipanion'; +import * as t from 'typanion'; + +import type { CliContext } from './context'; + +export abstract class Command extends BaseCommand { + get logger() { + // @ts-expect-error hack: Get the command name + return new Logger(this.constructor.paths[0][0]); + } + + get workspace() { + return this.context.workspace; + } +} + +export abstract class PackageCommand extends Command { + protected availablePackageNameArgs = ( + Workspace.PackageNames as string[] + ).concat(Array.from(AliasToPackage.keys())); + protected packageNameValidator = t.isOneOf( + this.availablePackageNameArgs.map(k => t.isLiteral(k)) + ); + + protected packageNameOrAlias = Option.String('--package,-p', { + required: true, + validator: this.packageNameValidator, + description: 'The package name or alias to be run with', + }); + + get package(): PackageName { + return ( + AliasToPackage.get(this.packageNameOrAlias as any) ?? + (this.packageNameOrAlias as PackageName) + ); + } + + deps = Option.Boolean('--deps', false, { + description: + 'Execute the same command in workspace dependencies, if defined.', + }); +} + +export abstract class PackagesCommand extends Command { + protected availablePackageNameArgs = ( + Workspace.PackageNames as string[] + ).concat(Array.from(AliasToPackage.keys())); + protected packageNameValidator = t.isOneOf( + this.availablePackageNameArgs.map(k => t.isLiteral(k)) + ); + + protected packageNamesOrAliases = Option.Array('--package,-p', { + required: true, + validator: t.isArray(this.packageNameValidator), + }); + get packages() { + return this.packageNamesOrAliases.map( + name => AliasToPackage.get(name as any) ?? name + ); + } + + deps = Option.Boolean('--deps', false, { + description: + 'Execute the same command in workspace dependencies, if defined.', + }); +} + +export { Option }; diff --git a/tools/scripts/src/context.ts b/tools/scripts/src/context.ts new file mode 100644 index 0000000000000..bc97d15a67328 --- /dev/null +++ b/tools/scripts/src/context.ts @@ -0,0 +1,6 @@ +import type { Workspace } from '@affine-tools/utils/workspace'; +import type { BaseContext } from 'clipanion'; + +export interface CliContext extends BaseContext { + workspace: Workspace; +} diff --git a/tools/scripts/src/dev.ts b/tools/scripts/src/dev.ts new file mode 100644 index 0000000000000..efa3c3028c937 --- /dev/null +++ b/tools/scripts/src/dev.ts @@ -0,0 +1,15 @@ +import { PackageCommand } from './command'; + +export class DevCommand extends PackageCommand { + static override paths = [['dev'], ['d']]; + + async execute() { + const args = [this.package, 'dev']; + + if (this.deps) { + args.push('--deps'); + } + + await this.cli.run(args); + } +} diff --git a/tools/scripts/src/run.ts b/tools/scripts/src/run.ts new file mode 100644 index 0000000000000..dc747d8794c7e --- /dev/null +++ b/tools/scripts/src/run.ts @@ -0,0 +1,111 @@ +import { Path } from '@affine-tools/utils/path'; +import { execAsync } from '@affine-tools/utils/process'; +import type { PackageName } from '@affine-tools/utils/workspace'; + +import { Option, PackageCommand } from './command'; + +interface RunScriptOptions { + ignoreMissingScript?: boolean; + includeDependencies?: boolean; + waitDependencies?: boolean; +} + +const currentDir = Path.dir(import.meta.url); + +export class RunCommand extends PackageCommand { + static override paths = [[], ['run'], ['r']]; + + static override usage = PackageCommand.Usage({ + description: 'AFFiNE Monorepo scripts', + details: ` + \`affine web