From 751e234bd750faf3f88047f1a13249cfb7316a60 Mon Sep 17 00:00:00 2001 From: Andrey Sobolev Date: Thu, 12 Dec 2024 21:50:57 +0700 Subject: [PATCH] Revert "Init scripts with unified import format (#7242)" This reverts commit 57ee462ca6347bdd2faad1724467c6b65c9a6e84. Signed-off-by: Andrey Sobolev --- .github/workflows/main.yml | 9 - .vscode/launch.json | 2 +- .vscode/settings.json | 2 +- common/config/rush/pnpm-lock.yaml | 118 +---------- dev/.env | 2 +- dev/docker-compose.yaml | 3 + dev/import-tool/README.md | 8 +- .../Project Alpha/files/screenshot.png | Bin 39710 -> 0 bytes .../files/screenshot/drawing1.json | 55 ----- dev/import-tool/package.json | 31 ++- .../{docs => src}/clickup/README.md | 0 .../import-tool}/src/clickup/clickup.ts | 23 +- dev/import-tool/{docs => src}/huly/README.md | 0 .../huly/example-workspace/Documentation.yaml | 1 - .../Documentation/Getting Started.md | 0 .../Documentation/User Guide.md | 0 .../Documentation/User Guide/Installation.md | 0 .../Documentation/files/architecture.png | Bin .../huly/example-workspace/Project Alpha.yaml | 1 - .../Project Alpha/1.Project Setup.md | 2 - .../1.Project Setup/2.Configure CI.md | 0 .../Project Alpha/4.Update Docs.md | 0 .../Project Alpha/files/config.yaml | 0 .../import-tool}/src/huly/unified.ts | 188 +++++------------ .../import-tool}/src/importer/builder.ts | 33 --- .../import-tool}/src/importer/dowloader.ts | 0 .../import-tool}/src/importer/importer.ts | 197 ++++-------------- .../import-tool}/src/importer/preprocessor.ts | 0 .../import-tool/src/importer/uploader.ts | 54 ++--- dev/import-tool/src/index.ts | 30 +-- .../{docs => src}/notion/README.md | 0 .../import-tool}/src/notion/notion.ts | 8 +- dev/tool/src/index.ts | 7 +- packages/importer/.eslintrc.js | 7 - packages/importer/.npmignore | 4 - packages/importer/jest.config.js | 7 - packages/importer/package.json | 69 ------ packages/importer/src/importer/logger.ts | 18 -- .../importer/src/importer/storageUploader.ts | 59 ------ packages/importer/src/importer/uploader.ts | 33 --- packages/importer/src/index.ts | 23 -- packages/importer/tsconfig.json | 10 - pods/workspace/Dockerfile | 3 - rush.json | 5 - server/account-service/package.json | 1 + server/account-service/src/index.ts | 6 + server/tool/package.json | 1 - server/tool/src/index.ts | 20 +- server/tool/src/initializer.ts | 26 +-- server/tool/src/plugin.ts | 2 +- server/workspace-service/src/index.ts | 9 +- 51 files changed, 194 insertions(+), 883 deletions(-) delete mode 100644 dev/import-tool/docs/huly/example-workspace/Project Alpha/files/screenshot.png delete mode 100644 dev/import-tool/docs/huly/example-workspace/Project Alpha/files/screenshot/drawing1.json rename dev/import-tool/{docs => src}/clickup/README.md (100%) rename {packages/importer => dev/import-tool}/src/clickup/clickup.ts (93%) rename dev/import-tool/{docs => src}/huly/README.md (100%) rename dev/import-tool/{docs => src}/huly/example-workspace/Documentation.yaml (94%) rename dev/import-tool/{docs => src}/huly/example-workspace/Documentation/Getting Started.md (100%) rename dev/import-tool/{docs => src}/huly/example-workspace/Documentation/User Guide.md (100%) rename dev/import-tool/{docs => src}/huly/example-workspace/Documentation/User Guide/Installation.md (100%) rename dev/import-tool/{docs => src}/huly/example-workspace/Documentation/files/architecture.png (100%) rename dev/import-tool/{docs => src}/huly/example-workspace/Project Alpha.yaml (95%) rename dev/import-tool/{docs => src}/huly/example-workspace/Project Alpha/1.Project Setup.md (94%) rename dev/import-tool/{docs => src}/huly/example-workspace/Project Alpha/1.Project Setup/2.Configure CI.md (100%) rename dev/import-tool/{docs => src}/huly/example-workspace/Project Alpha/4.Update Docs.md (100%) rename dev/import-tool/{docs => src}/huly/example-workspace/Project Alpha/files/config.yaml (100%) rename {packages/importer => dev/import-tool}/src/huly/unified.ts (76%) rename {packages/importer => dev/import-tool}/src/importer/builder.ts (94%) rename {packages/importer => dev/import-tool}/src/importer/dowloader.ts (100%) rename {packages/importer => dev/import-tool}/src/importer/importer.ts (73%) rename {packages/importer => dev/import-tool}/src/importer/preprocessor.ts (100%) rename packages/importer/src/importer/frontUploader.ts => dev/import-tool/src/importer/uploader.ts (53%) rename dev/import-tool/{docs => src}/notion/README.md (100%) rename {packages/importer => dev/import-tool}/src/notion/notion.ts (98%) delete mode 100644 packages/importer/.eslintrc.js delete mode 100644 packages/importer/.npmignore delete mode 100644 packages/importer/jest.config.js delete mode 100644 packages/importer/package.json delete mode 100644 packages/importer/src/importer/logger.ts delete mode 100644 packages/importer/src/importer/storageUploader.ts delete mode 100644 packages/importer/src/importer/uploader.ts delete mode 100644 packages/importer/src/index.ts delete mode 100644 packages/importer/tsconfig.json diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 3333d250d3..f051325abe 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -42,7 +42,6 @@ env: tools PublishTempFolder: publish_artifacts MODEL_VERSION_MODE: ${{ startsWith(github.ref, 'refs/tags/s') && 'tagTime' || 'file' }} - INIT_SCRIPTS_BRANCH: 'unified-init-scripts' # A workflow run is made up of one or more jobs that can run sequentially or in parallel jobs: @@ -494,14 +493,6 @@ jobs: with: fetch-depth: 0 filter: tree:0 - - - name: Checkout init repository - run: | - wget https://github.com/hcengineering/init/archive/refs/heads/${{env.INIT_SCRIPTS_BRANCH}}.zip - unzip ${{env.INIT_SCRIPTS_BRANCH}}.zip -d pods/workspace - mv pods/workspace/init-${{env.INIT_SCRIPTS_BRANCH}} pods/workspace/init - rm -rf ${{env.INIT_SCRIPTS_BRANCH}}.zip - - uses: actions/setup-node@v4 with: node-version-file: '.nvmrc' diff --git a/.vscode/launch.json b/.vscode/launch.json index 6823fd564f..646aab7971 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -624,7 +624,7 @@ "name": "Debug Huly import", "type": "node", "request": "launch", - "args": ["src/__start.ts", "import", "/home/anna/huly/platform/dev/import-tool/docs/huly/example-workspace", "-u", "user1", "-pw", "1234", "-ws", "ws1"], + "args": ["src/__start.ts", "import", "/home/anna/xored/huly/platform/dev/import-tool/src/huly/example-workspace", "-u", "user1", "-pw", "1234", "-ws", "ws12"], "env": { "FRONT_URL": "http://localhost:8087" }, diff --git a/.vscode/settings.json b/.vscode/settings.json index bb197af43f..a7c6b18b4e 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -8,7 +8,7 @@ "editor.defaultFormatter": "dbaeumer.vscode-eslint" }, "[json]": { - "editor.defaultFormatter": "esbenp.prettier-vscode" + "editor.defaultFormatter": "dbaeumer.vscode-eslint" }, "[markdown]": { "editor.defaultFormatter": "esbenp.prettier-vscode" diff --git a/common/config/rush/pnpm-lock.yaml b/common/config/rush/pnpm-lock.yaml index 8126b9d196..83ee914bb6 100644 --- a/common/config/rush/pnpm-lock.yaml +++ b/common/config/rush/pnpm-lock.yaml @@ -296,9 +296,6 @@ dependencies: '@rush-temp/import-tool': specifier: file:./projects/import-tool.tgz version: file:projects/import-tool.tgz - '@rush-temp/importer': - specifier: file:./projects/importer.tgz - version: file:projects/importer.tgz(esbuild@0.20.1)(ts-node@10.9.2) '@rush-temp/inventory': specifier: file:./projects/inventory.tgz version: file:projects/inventory.tgz(@types/node@20.11.19)(esbuild@0.20.1)(ts-node@10.9.2) @@ -1643,9 +1640,6 @@ dependencies: htmlparser2: specifier: ^9.0.0 version: 9.1.0 - image-size: - specifier: ^1.1.1 - version: 1.1.1 intl-messageformat: specifier: ^9.7.1 version: 9.13.0 @@ -5248,10 +5242,6 @@ packages: resolution: {integrity: sha512-dyHY+sMF0ihPus3O27ODd4+agdHMEmuRdyiZJ2CCWjPV5UFmn17ZbElvk6WOGVE4rdCJKZQCrPV2BcikOMLUGQ==} dev: false - /@sec-ant/readable-stream@0.4.1: - resolution: {integrity: sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==} - dev: false - /@selderee/plugin-htmlparser2@0.11.0: resolution: {integrity: sha512-P33hHGdldxGabLFjPPpaTxVolMrzrcegejx+0GxjrIb9Zv48D8yAIA/QTDR2dFl7Uz7urX8aX6+5bCZslr+gWQ==} dependencies: @@ -12486,16 +12476,6 @@ packages: token-types: 4.2.1 dev: false - /file-type@19.6.0: - resolution: {integrity: sha512-VZR5I7k5wkD0HgFnMsq5hOsSc710MJMu5Nc5QYsbe38NN5iPV/XTObYLc/cpttRTf6lX538+5uO1ZQRhYibiZQ==} - engines: {node: '>=18'} - dependencies: - get-stream: 9.0.1 - strtok3: 9.1.1 - token-types: 6.0.0 - uint8array-extras: 1.4.0 - dev: false - /filelist@1.0.4: resolution: {integrity: sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==} dependencies: @@ -12917,14 +12897,6 @@ packages: engines: {node: '>=10'} dev: false - /get-stream@9.0.1: - resolution: {integrity: sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==} - engines: {node: '>=18'} - dependencies: - '@sec-ant/readable-stream': 0.4.1 - is-stream: 4.0.1 - dev: false - /get-symbol-description@1.0.2: resolution: {integrity: sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==} engines: {node: '>= 0.4'} @@ -13682,14 +13654,6 @@ packages: '@types/node': 16.9.1 dev: false - /image-size@1.1.1: - resolution: {integrity: sha512-541xKlUw6jr/6gGuk92F+mYM5zaFAc5ahphvkqvNe2bQ6gVBkd6bfrmVJ2t4KDAfikAYZyIqTnktX3i6/aQDrQ==} - engines: {node: '>=16.x'} - hasBin: true - dependencies: - queue: 6.0.2 - dev: false - /immediate@3.0.6: resolution: {integrity: sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==} dev: false @@ -14106,11 +14070,6 @@ packages: engines: {node: '>=8'} dev: false - /is-stream@4.0.1: - resolution: {integrity: sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==} - engines: {node: '>=18'} - dev: false - /is-string@1.0.7: resolution: {integrity: sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==} engines: {node: '>= 0.4'} @@ -17276,11 +17235,6 @@ packages: engines: {node: '>=8'} dev: false - /peek-readable@5.3.1: - resolution: {integrity: sha512-GVlENSDW6KHaXcd9zkZltB7tCLosKB/4Hg0fqBJkAoBgYG2Tn1xtMgXtSUuMU9AK/gCm/tTdT8mgAeF4YNeeqw==} - engines: {node: '>=14.16'} - dev: false - /pend@1.2.0: resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==} dev: false @@ -18152,12 +18106,6 @@ packages: requiresBuild: true dev: false - /queue@6.0.2: - resolution: {integrity: sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==} - dependencies: - inherits: 2.0.4 - dev: false - /quick-format-unescaped@4.0.4: resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==} dev: false @@ -19592,14 +19540,6 @@ packages: peek-readable: 4.1.0 dev: false - /strtok3@9.1.1: - resolution: {integrity: sha512-FhwotcEqjr241ZbjFzjlIYg6c5/L/s4yBGWSMvJ9UoExiSqL+FnFA/CaeZx17WGaZMS/4SOZp8wH18jSS4R4lw==} - engines: {node: '>=16'} - dependencies: - '@tokenizer/token': 0.3.0 - peek-readable: 5.3.1 - dev: false - /style-loader@3.3.4(webpack@5.97.1): resolution: {integrity: sha512-0WqXzrsMTyb8yjZJHDqwmnwRJvhALK9LfRtRc6B4UTWe8AijYLZYZ9thuJTZc2VfQWINADW/j+LiJnfy2RoC1w==} engines: {node: '>= 12.13.0'} @@ -20152,14 +20092,6 @@ packages: ieee754: 1.2.1 dev: false - /token-types@6.0.0: - resolution: {integrity: sha512-lbDrTLVsHhOMljPscd0yitpozq7Ga2M5Cvez5AjGg8GASBjtt6iERCAJ93yommPmz62fb45oFIXHEZ3u9bfJEA==} - engines: {node: '>=14.16'} - dependencies: - '@tokenizer/token': 0.3.0 - ieee754: 1.2.1 - dev: false - /toposort@2.0.2: resolution: {integrity: sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg==} dev: false @@ -20687,11 +20619,6 @@ packages: resolution: {integrity: sha512-IevTus0SbGwQzYh3+fRsAMTVVPOoIVufzacXcHPmdlle1jUpq7BRL+mw3dgeLanvGZdwwbWhRV6XrcFNdBmjWA==} dev: false - /uint8array-extras@1.4.0: - resolution: {integrity: sha512-ZPtzy0hu4cZjv3z5NW9gfKnNLjoz4y6uv4HlelAjDK7sY/xOkKZv9xK/WQpcsBB3jEybChz9DPC2U/+cusjJVQ==} - engines: {node: '>=18'} - dev: false - /unbox-primitive@1.0.2: resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==} dependencies: @@ -21880,7 +21807,7 @@ packages: dev: false file:projects/account-service.tgz: - resolution: {integrity: sha512-Kxz4jt8+j+W4r7hgcpPq5D1wVL5G4AmpEWF6yxyUTR6+vxQERCTILfJo6wRbecPBvEPcFvA7Snogo8exY2YJQg==, tarball: file:projects/account-service.tgz} + resolution: {integrity: sha512-Cyt9smf3yBf80n9zd8D77piZd9wnJIYLjzSJzSfcZLA+w4WPacwDMZEU9hN3Pf9aAIpeaaVpuZqXSvnOoBvv4A==, tarball: file:projects/account-service.tgz} name: '@rush-temp/account-service' version: 0.0.0 dependencies: @@ -24791,7 +24718,7 @@ packages: dev: false file:projects/import-tool.tgz: - resolution: {integrity: sha512-Pkbjr/0XN1X7G8Gi6vwh0DjhRr8f0un2HSW2ef6ZMsIv2MyH8BgE2erUpXmzHETxSKsyJllAm6T1DmfSa26XIg==, tarball: file:projects/import-tool.tgz} + resolution: {integrity: sha512-0Q1/hHZxEdYFPr2qqfovlVJRA8JyvWsKtY3ubHrffnaAMbbN6BNWt6Jf+GzwyGeIwImLI2Oud2x/WqOFb/USdg==, tarball: file:projects/import-tool.tgz} name: '@rush-temp/import-tool' version: 0.0.0 dependencies: @@ -24838,45 +24765,6 @@ packages: - supports-color dev: false - file:projects/importer.tgz(esbuild@0.20.1)(ts-node@10.9.2): - resolution: {integrity: sha512-H8zT+HFFD2VV3Fo3ALiiDtd0VtXQZpWEfm10a5udm4D07Gv4DgxJOLl8uF4wNId//6UNKOp6/z517p8D/8velg==, tarball: file:projects/importer.tgz} - id: file:projects/importer.tgz - name: '@rush-temp/importer' - version: 0.0.0 - dependencies: - '@types/csvtojson': 2.0.0 - '@types/jest': 29.5.12 - '@types/js-yaml': 4.0.9 - '@types/mime-types': 2.1.4 - '@types/node': 20.11.19 - '@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0)(eslint@8.56.0)(typescript@5.6.2) - '@typescript-eslint/parser': 6.21.0(eslint@8.56.0)(typescript@5.6.2) - commander: 8.3.0 - csvtojson: 2.0.10 - eslint: 8.56.0 - eslint-config-standard-with-typescript: 40.0.0(@typescript-eslint/eslint-plugin@6.21.0)(eslint-plugin-import@2.29.1)(eslint-plugin-n@15.7.0)(eslint-plugin-promise@6.1.1)(eslint@8.56.0)(typescript@5.6.2) - eslint-plugin-import: 2.29.1(eslint@8.56.0) - eslint-plugin-n: 15.7.0(eslint@8.56.0) - eslint-plugin-promise: 6.1.1(eslint@8.56.0) - file-type: 19.6.0 - image-size: 1.1.1 - jest: 29.7.0(@types/node@20.11.19)(ts-node@10.9.2) - js-yaml: 4.1.0 - mime-types: 2.1.35 - prettier: 3.2.5 - ts-jest: 29.1.2(esbuild@0.20.1)(jest@29.7.0)(typescript@5.6.2) - typescript: 5.6.2 - transitivePeerDependencies: - - '@babel/core' - - '@jest/types' - - babel-jest - - babel-plugin-macros - - esbuild - - node-notifier - - supports-color - - ts-node - dev: false - file:projects/inventory-assets.tgz(esbuild@0.20.1)(ts-node@10.9.2): resolution: {integrity: sha512-Uayr70kuiNfwBgNoFcu1rkWhdHhnbg7aEDcbozbhn5Eyel/B6he2uUYxPZc2gl1VUiEA8KBGszSAEXMs0YER0A==, tarball: file:projects/inventory-assets.tgz} id: file:projects/inventory-assets.tgz @@ -31278,7 +31166,7 @@ packages: dev: false file:projects/server-tool.tgz(@types/node@20.11.19)(esbuild@0.20.1)(ts-node@10.9.2): - resolution: {integrity: sha512-FwqM3BepKqMKvnizEtaWgyv6YTuhwVDaJ4yPF+DEDLqHmxdeGC5u2Akj+sIdP0qtmSRQy9ts5NMwJw2SVNIetQ==, tarball: file:projects/server-tool.tgz} + resolution: {integrity: sha512-Pd5QWUOAPkgU3vgG4J4yXrQJuskV46yXxzATYcWPzY+4LQRJ4jshHlld/rI2EGJKJNr5fy6trH1p8LJ+xvLdeA==, tarball: file:projects/server-tool.tgz} id: file:projects/server-tool.tgz name: '@rush-temp/server-tool' version: 0.0.0 diff --git a/dev/.env b/dev/.env index 7f2e5141e7..6650314f86 100644 --- a/dev/.env +++ b/dev/.env @@ -1,3 +1,3 @@ STORAGE_CONFIG="minio|minio?accessKey=minioadmin&secretKey=minioadmin" MONGO_URL=mongodb://mongodb:27017?compressors=snappy -DB_URL_PG=postgresql://postgres:example@postgres:5432 +DB_URL_PG=postgresql://postgres:example@postgres:5432 \ No newline at end of file diff --git a/dev/docker-compose.yaml b/dev/docker-compose.yaml index bed9eceb30..01adc293e9 100644 --- a/dev/docker-compose.yaml +++ b/dev/docker-compose.yaml @@ -138,6 +138,7 @@ services: - ACCOUNTS_URL=http://host.docker.internal:3000 - BRANDING_PATH=/var/cfg/branding.json # - PARALLEL=2 + - INIT_SCRIPT_URL=https://raw.githubusercontent.com/hcengineering/init/main/script.yaml - INIT_WORKSPACE=test restart: unless-stopped workspacepg: @@ -164,6 +165,7 @@ services: - ACCOUNTS_URL=http://host.docker.internal:3000 - BRANDING_PATH=/var/cfg/branding.json # - PARALLEL=2 + - INIT_SCRIPT_URL=https://raw.githubusercontent.com/hcengineering/init/main/script.yaml # - INIT_WORKSPACE=onboarding restart: unless-stopped workspace_cockroach: @@ -189,6 +191,7 @@ services: - ACCOUNTS_URL=http://host.docker.internal:3000 - BRANDING_PATH=/var/cfg/branding.json # - PARALLEL=2 + - INIT_SCRIPT_URL=https://raw.githubusercontent.com/hcengineering/init/main/script.yaml # - INIT_WORKSPACE=onboarding restart: unless-stopped collaborator: diff --git a/dev/import-tool/README.md b/dev/import-tool/README.md index 59de5713b5..4e1c0d56b8 100644 --- a/dev/import-tool/README.md +++ b/dev/import-tool/README.md @@ -5,9 +5,9 @@ Tool for importing data into Huly workspace. ## Recommended Import Method ### Unified Format Import -The recommended way to import data into Huly is using our [Unified Import Format](./docs/huly/README.md). This format provides a straightforward way to migrate data from any system by converting it into an intermediate, human-readable structure. +The recommended way to import data into Huly is using our [Unified Import Format](./src/huly/README.md). This format provides a straightforward way to migrate data from any system by converting it into an intermediate, human-readable structure. -See the [complete guide](./docs/huly/README.md) and [example workspace](./docs/huly/example-workspace) to get started. +See the [complete guide](./src/huly/README.md) and [example workspace](./src/huly/example-workspace) to get started. ### Why Use Unified Format? - Simple, human-readable format using YAML and Markdown @@ -19,7 +19,7 @@ See the [complete guide](./docs/huly/README.md) and [example workspace](./docs/h We also support direct import from some platforms: -1. **Notion**: see [Import from Notion Guide](./docs/notion/README.md) -2. **ClickUp**: see [Import from ClickUp Guide](./docs/clickup/README.md) +1. **Notion**: see [Import from Notion Guide](./src/notion/README.md) +2. **ClickUp**: see [Import from ClickUp Guide](./src/clickup/README.md) These direct imports are suitable for simple migrations, but for complex cases or systems not listed above, please use the Unified Format. \ No newline at end of file diff --git a/dev/import-tool/docs/huly/example-workspace/Project Alpha/files/screenshot.png b/dev/import-tool/docs/huly/example-workspace/Project Alpha/files/screenshot.png deleted file mode 100644 index d727c76daef0ad966703df85686414441dbeb67b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 39710 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%0At-fNf1Ku}>_1&Gj;m?&OmPh`mH_Kl%VOaUq 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 diff --git a/dev/import-tool/docs/huly/example-workspace/Project Alpha/files/screenshot/drawing1.json b/dev/import-tool/docs/huly/example-workspace/Project Alpha/files/screenshot/drawing1.json deleted file mode 100644 index 4848657f09..0000000000 --- a/dev/import-tool/docs/huly/example-workspace/Project Alpha/files/screenshot/drawing1.json +++ /dev/null @@ -1,55 +0,0 @@ -{ - "class": "drawing:class:Drawing", - "content": [ - { - "lineWidth": 4, - "erasing": false, - "penColor": "#0000ff", - "points": [ - { "x": 213, "y": 181.5 }, - { "x": 203, "y": 181.5 }, - { "x": 193, "y": 181.5 }, - { "x": 168, "y": 181.5 }, - { "x": 136, "y": 181.5 }, - { "x": 108, "y": 181.5 }, - { "x": 84, "y": 188.5 }, - { "x": 59, "y": 196.5 }, - { "x": 42, "y": 205.5 }, - { "x": 35, "y": 220.5 }, - { "x": 32, "y": 227.5 }, - { "x": 31, "y": 233.5 }, - { "x": 31, "y": 241.5 }, - { "x": 31, "y": 248.5 }, - { "x": 34, "y": 257.5 }, - { "x": 41, "y": 269.5 }, - { "x": 53, "y": 280.5 }, - { "x": 69, "y": 290.5 }, - { "x": 84, "y": 297.5 }, - { "x": 112, "y": 305.5 }, - { "x": 190, "y": 319.5 }, - { "x": 250, "y": 323.5 }, - { "x": 314, "y": 323.5 }, - { "x": 372, "y": 317.5 }, - { "x": 410, "y": 303.5 }, - { "x": 424, "y": 292.5 }, - { "x": 427, "y": 279.5 }, - { "x": 427, "y": 265.5 }, - { "x": 425, "y": 250.5 }, - { "x": 413, "y": 225.5 }, - { "x": 407, "y": 214.5 }, - { "x": 398, "y": 203.5 }, - { "x": 380, "y": 189.5 }, - { "x": 361, "y": 181.5 }, - { "x": 317, "y": 168.5 }, - { "x": 261, "y": 154.5 }, - { "x": 207, "y": 148.5 }, - { "x": 168, "y": 148.5 }, - { "x": 153, "y": 148.5 }, - { "x": 147, "y": 151.5 }, - { "x": 143, "y": 157.5 }, - { "x": 142, "y": 165.5 }, - { "x": 142, "y": 165.5 } - ] - } - ] -} diff --git a/dev/import-tool/package.json b/dev/import-tool/package.json index a2db246214..29af15302d 100644 --- a/dev/import-tool/package.json +++ b/dev/import-tool/package.json @@ -34,26 +34,43 @@ "cross-env": "~7.0.3", "@hcengineering/platform-rig": "^0.6.0", "@typescript-eslint/eslint-plugin": "^6.11.0", + "eslint-plugin-import": "^2.26.0", + "eslint-plugin-promise": "^6.1.1", + "eslint-plugin-n": "^15.4.0", "eslint": "^8.54.0", "ts-node": "^10.8.0", "esbuild": "^0.20.0", + "@types/mime-types": "~2.1.1", "@types/node": "~20.11.16", "@typescript-eslint/parser": "^6.11.0", + "eslint-config-standard-with-typescript": "^40.0.0", + "prettier": "^3.1.0", "typescript": "^5.3.3", "jest": "^29.7.0", "ts-jest": "^29.1.1", "@types/jest": "^29.5.5", - "eslint-config-standard-with-typescript": "^40.0.0", - "eslint-plugin-import": "^2.26.0", - "eslint-plugin-n": "^15.4.0", - "eslint-plugin-promise": "^6.1.1", - "prettier": "^3.1.0" + "@types/csvtojson": "^2.0.0", + "@types/js-yaml": "^4.0.9" }, "dependencies": { + "@hcengineering/attachment": "^0.6.14", + "@hcengineering/collaboration": "^0.6.0", + "@hcengineering/document": "^0.6.0", + "@hcengineering/text": "^0.6.5", + "@hcengineering/model-attachment": "^0.6.0", + "@hcengineering/model-core": "^0.6.0", "@hcengineering/core": "^0.6.32", "@hcengineering/platform": "^0.6.11", + "@hcengineering/server-tool": "^0.6.0", "@hcengineering/server-client": "^0.6.0", - "@hcengineering/importer": "^0.6.1", - "commander": "^8.1.0" + "@hcengineering/rank": "^0.6.4", + "@hcengineering/tracker": "^0.6.24", + "commander": "^8.1.0", + "mime-types": "~2.1.34", + "csvtojson": "^2.0.10", + "@hcengineering/task": "^0.6.20", + "@hcengineering/contact": "^0.6.24", + "@hcengineering/chunter": "^0.6.20", + "js-yaml": "^4.1.0" } } diff --git a/dev/import-tool/docs/clickup/README.md b/dev/import-tool/src/clickup/README.md similarity index 100% rename from dev/import-tool/docs/clickup/README.md rename to dev/import-tool/src/clickup/README.md diff --git a/packages/importer/src/clickup/clickup.ts b/dev/import-tool/src/clickup/clickup.ts similarity index 93% rename from packages/importer/src/clickup/clickup.ts rename to dev/import-tool/src/clickup/clickup.ts index 431c759bfd..b39d00a67b 100644 --- a/packages/importer/src/clickup/clickup.ts +++ b/dev/import-tool/src/clickup/clickup.ts @@ -25,9 +25,9 @@ import { type ImportProject, type ImportProjectType } from '../importer/importer' -import { type Logger } from '../importer/logger' -import { BaseMarkdownPreprocessor } from '../importer/preprocessor' import { type FileUploader } from '../importer/uploader' +import { BaseMarkdownPreprocessor } from '../importer/preprocessor' + interface ClickupTask { 'Task ID': string 'Task Name': string @@ -86,8 +86,7 @@ class ClickupImporter { constructor ( private readonly client: TxOperations, - private readonly fileUploader: FileUploader, - private readonly logger: Logger + private readonly fileUploader: FileUploader ) {} async importClickUpTasks (file: string): Promise { @@ -103,13 +102,13 @@ class ClickupImporter { spaces } - this.logger.log('========================================') - this.logger.log('IMPORT DATA STRUCTURE: ', JSON.stringify(importData, null, 4)) - this.logger.log('========================================') + console.log('========================================') + console.log('IMPORT DATA STRUCTURE: ', JSON.stringify(importData, null, 4)) + console.log('========================================') const postprocessor = new ClickupMarkdownPreprocessor(this.personsByName) - await new WorkspaceImporter(this.client, this.logger, this.fileUploader, importData, postprocessor).performImport() - this.logger.log('========================================') - this.logger.log('IMPORT SUCCESS ') + await new WorkspaceImporter(this.client, this.fileUploader, importData, postprocessor).performImport() + console.log('========================================') + console.log('IMPORT SUCCESS ') } private async processTasksCsv (file: string, process: (json: ClickupTask) => Promise | void): Promise { @@ -137,8 +136,8 @@ class ClickupImporter { statuses.add(clickupTask.Status) }) - this.logger.log('Projects: ' + JSON.stringify(projects)) - this.logger.log('Statuses: ' + JSON.stringify(statuses)) + console.log(projects) + console.log(statuses) const importProjectType = this.createClickupProjectType(Array.from(statuses)) diff --git a/dev/import-tool/docs/huly/README.md b/dev/import-tool/src/huly/README.md similarity index 100% rename from dev/import-tool/docs/huly/README.md rename to dev/import-tool/src/huly/README.md diff --git a/dev/import-tool/docs/huly/example-workspace/Documentation.yaml b/dev/import-tool/src/huly/example-workspace/Documentation.yaml similarity index 94% rename from dev/import-tool/docs/huly/example-workspace/Documentation.yaml rename to dev/import-tool/src/huly/example-workspace/Documentation.yaml index 781c2b6f12..3279e182fb 100644 --- a/dev/import-tool/docs/huly/example-workspace/Documentation.yaml +++ b/dev/import-tool/src/huly/example-workspace/Documentation.yaml @@ -1,6 +1,5 @@ class: document:class:Teamspace title: Documentation -emoji: 📖 private: false autoJoin: true owners: diff --git a/dev/import-tool/docs/huly/example-workspace/Documentation/Getting Started.md b/dev/import-tool/src/huly/example-workspace/Documentation/Getting Started.md similarity index 100% rename from dev/import-tool/docs/huly/example-workspace/Documentation/Getting Started.md rename to dev/import-tool/src/huly/example-workspace/Documentation/Getting Started.md diff --git a/dev/import-tool/docs/huly/example-workspace/Documentation/User Guide.md b/dev/import-tool/src/huly/example-workspace/Documentation/User Guide.md similarity index 100% rename from dev/import-tool/docs/huly/example-workspace/Documentation/User Guide.md rename to dev/import-tool/src/huly/example-workspace/Documentation/User Guide.md diff --git a/dev/import-tool/docs/huly/example-workspace/Documentation/User Guide/Installation.md b/dev/import-tool/src/huly/example-workspace/Documentation/User Guide/Installation.md similarity index 100% rename from dev/import-tool/docs/huly/example-workspace/Documentation/User Guide/Installation.md rename to dev/import-tool/src/huly/example-workspace/Documentation/User Guide/Installation.md diff --git a/dev/import-tool/docs/huly/example-workspace/Documentation/files/architecture.png b/dev/import-tool/src/huly/example-workspace/Documentation/files/architecture.png similarity index 100% rename from dev/import-tool/docs/huly/example-workspace/Documentation/files/architecture.png rename to dev/import-tool/src/huly/example-workspace/Documentation/files/architecture.png diff --git a/dev/import-tool/docs/huly/example-workspace/Project Alpha.yaml b/dev/import-tool/src/huly/example-workspace/Project Alpha.yaml similarity index 95% rename from dev/import-tool/docs/huly/example-workspace/Project Alpha.yaml rename to dev/import-tool/src/huly/example-workspace/Project Alpha.yaml index ee5389ad33..0e13f3656c 100644 --- a/dev/import-tool/docs/huly/example-workspace/Project Alpha.yaml +++ b/dev/import-tool/src/huly/example-workspace/Project Alpha.yaml @@ -1,7 +1,6 @@ class: tracker:class:Project title: Project Alpha identifier: ALPHA -emoji: 🦄 private: false autoJoin: true owners: diff --git a/dev/import-tool/docs/huly/example-workspace/Project Alpha/1.Project Setup.md b/dev/import-tool/src/huly/example-workspace/Project Alpha/1.Project Setup.md similarity index 94% rename from dev/import-tool/docs/huly/example-workspace/Project Alpha/1.Project Setup.md rename to dev/import-tool/src/huly/example-workspace/Project Alpha/1.Project Setup.md index 53a4813312..833418a26d 100644 --- a/dev/import-tool/docs/huly/example-workspace/Project Alpha/1.Project Setup.md +++ b/dev/import-tool/src/huly/example-workspace/Project Alpha/1.Project Setup.md @@ -16,8 +16,6 @@ comments: - author: john.doe@example.com text: | Perfect, don't forget to update [documentation](../Documentation/User%20Guide/Installation.md) when you're done. - attachments: - - ./files/screenshot.png --- **Initial project infrastructure setup.** diff --git a/dev/import-tool/docs/huly/example-workspace/Project Alpha/1.Project Setup/2.Configure CI.md b/dev/import-tool/src/huly/example-workspace/Project Alpha/1.Project Setup/2.Configure CI.md similarity index 100% rename from dev/import-tool/docs/huly/example-workspace/Project Alpha/1.Project Setup/2.Configure CI.md rename to dev/import-tool/src/huly/example-workspace/Project Alpha/1.Project Setup/2.Configure CI.md diff --git a/dev/import-tool/docs/huly/example-workspace/Project Alpha/4.Update Docs.md b/dev/import-tool/src/huly/example-workspace/Project Alpha/4.Update Docs.md similarity index 100% rename from dev/import-tool/docs/huly/example-workspace/Project Alpha/4.Update Docs.md rename to dev/import-tool/src/huly/example-workspace/Project Alpha/4.Update Docs.md diff --git a/dev/import-tool/docs/huly/example-workspace/Project Alpha/files/config.yaml b/dev/import-tool/src/huly/example-workspace/Project Alpha/files/config.yaml similarity index 100% rename from dev/import-tool/docs/huly/example-workspace/Project Alpha/files/config.yaml rename to dev/import-tool/src/huly/example-workspace/Project Alpha/files/config.yaml diff --git a/packages/importer/src/huly/unified.ts b/dev/import-tool/src/huly/unified.ts similarity index 76% rename from packages/importer/src/huly/unified.ts rename to dev/import-tool/src/huly/unified.ts index 264ed772fd..67713179a4 100644 --- a/packages/importer/src/huly/unified.ts +++ b/dev/import-tool/src/huly/unified.ts @@ -18,9 +18,8 @@ import contact, { type Person, type PersonAccount } from '@hcengineering/contact import { type Class, type Doc, generateId, type Ref, type Space, type TxOperations } from '@hcengineering/core' import document, { type Document } from '@hcengineering/document' import { MarkupMarkType, type MarkupNode, MarkupNodeType, traverseNode, traverseNodeMarks } from '@hcengineering/text' -import tracker, { type Issue, Project } from '@hcengineering/tracker' +import tracker, { type Issue } from '@hcengineering/tracker' import * as fs from 'fs' -import sizeOf from 'image-size' import * as yaml from 'js-yaml' import { contentType } from 'mime-types' import * as path from 'path' @@ -29,7 +28,6 @@ import { type ImportAttachment, type ImportComment, type ImportDocument, - ImportDrawing, type ImportIssue, type ImportProject, type ImportProjectType, @@ -37,14 +35,12 @@ import { type ImportWorkspace, WorkspaceImporter } from '../importer/importer' -import { type Logger } from '../importer/logger' import { BaseMarkdownPreprocessor } from '../importer/preprocessor' import { type FileUploader } from '../importer/uploader' interface UnifiedComment { author: string text: string - attachments?: string[] } interface UnifiedIssueHeader { @@ -63,17 +59,14 @@ interface UnifiedSpaceSettings { title: string private?: boolean autoJoin?: boolean - archived?: boolean owners?: string[] members?: string[] description?: string - emoji?: string } interface UnifiedProjectSettings extends UnifiedSpaceSettings { class: 'tracker:class:Project' identifier: string - id?: 'tracker:project:DefaultProject' projectType?: string defaultIssueStatus?: string } @@ -104,7 +97,6 @@ interface UnifiedWorkspaceSettings { class HulyMarkdownPreprocessor extends BaseMarkdownPreprocessor { constructor ( private readonly urlProvider: (id: string) => string, - private readonly logger: Logger, private readonly metadataByFilePath: Map, private readonly metadataById: Map, DocMetadata>, private readonly attachMetadataByPath: Map, @@ -138,7 +130,7 @@ class HulyMarkdownPreprocessor extends BaseMarkdownPreprocessor { const attachmentMeta = this.attachMetadataByPath.get(fullPath) if (attachmentMeta === undefined) { - this.logger.error(`Attachment image not found for ${fullPath}`) + console.warn(`Attachment image not found for ${fullPath}`) return } @@ -168,7 +160,7 @@ class HulyMarkdownPreprocessor extends BaseMarkdownPreprocessor { this.updateAttachmentMetadata(fullPath, attachmentMeta, id, spaceId, sourceMeta) } } else { - this.logger.log('Unknown link type, leave it as is: ' + href) + console.log('Unknown link type, leave it as is:', href) } }) } @@ -226,7 +218,7 @@ class HulyMarkdownPreprocessor extends BaseMarkdownPreprocessor { private getSourceMetadata (id: Ref): DocMetadata | null { const sourceMeta = this.metadataById.get(id) if (sourceMeta == null) { - this.logger.error(`Source metadata not found for ${id}`) + console.warn(`Source metadata not found for ${id}`) return null } return sourceMeta @@ -267,118 +259,56 @@ interface AttachmentMetadata { export class UnifiedFormatImporter { private readonly metadataById = new Map, DocMetadata>() private readonly metadataByFilePath = new Map() - private readonly fileMetadataByPath = new Map() + private readonly attachMetadataByPath = new Map() private personsByName = new Map>() private accountsByEmail = new Map>() constructor ( private readonly client: TxOperations, - private readonly fileUploader: FileUploader, - private readonly logger: Logger + private readonly fileUploader: FileUploader ) {} async importFolder (folderPath: string): Promise { await this.cachePersonsByNames() await this.cacheAccountsByEmails() - await this.collectFileMetadata(folderPath) - const workspaceData = await this.processImportFolder(folderPath) - this.logger.log('========================================') - this.logger.log('IMPORT DATA STRUCTURE: ' + JSON.stringify(workspaceData)) - this.logger.log('========================================') + console.log('========================================') + console.log('IMPORT DATA STRUCTURE: ', JSON.stringify(workspaceData, null, 4)) + console.log('========================================') - this.logger.log('Importing documents...') + console.log('Importing documents...') const preprocessor = new HulyMarkdownPreprocessor( this.fileUploader.getFileUrl, - this.logger, this.metadataByFilePath, this.metadataById, - this.fileMetadataByPath, + this.attachMetadataByPath, this.personsByName ) - await new WorkspaceImporter( - this.client, - this.logger, - this.fileUploader, - workspaceData, - preprocessor - ).performImport() + await new WorkspaceImporter(this.client, this.fileUploader, workspaceData, preprocessor).performImport() - this.logger.log('Importing attachments...') - - const attachments: ImportAttachment[] = await Promise.all( - Array.from(this.fileMetadataByPath.values()) - .filter((attachMeta) => attachMeta.parentId !== undefined) - .map(async (attachMeta: AttachmentMetadata) => await this.processAttachment(attachMeta)) - ) - await new WorkspaceImporter(this.client, this.logger, this.fileUploader, { attachments }).performImport() - - this.logger.log('========================================') - this.logger.log('IMPORT SUCCESS') - } - - private async processAttachment (attachMeta: AttachmentMetadata): Promise { - const fileType = contentType(attachMeta.name) - - const attachment: ImportAttachment = { - id: attachMeta.id, - title: path.basename(attachMeta.path), - blobProvider: async () => { - const data = fs.readFileSync(attachMeta.path) - const props = fileType !== false ? { type: fileType } : undefined - return new Blob([data], props) - }, - parentId: attachMeta.parentId, - parentClass: attachMeta.parentClass, - spaceId: attachMeta.spaceId - } - - if (fileType !== false && fileType?.startsWith('image/')) { - try { - const imageDimensions = sizeOf(attachMeta.path) - attachment.metadata = { - originalWidth: imageDimensions.width ?? 0, - originalHeight: imageDimensions.height ?? 0 + console.log('Importing attachments...') + const attachments: ImportAttachment[] = Array.from(this.attachMetadataByPath.values()) + .filter((attachment) => attachment.parentId !== undefined) + .map((attachment) => { + return { + id: attachment.id, + title: path.basename(attachment.path), + blobProvider: async () => { + const data = fs.readFileSync(attachment.path) + return new Blob([data]) + }, + parentId: attachment.parentId, + parentClass: attachment.parentClass, + spaceId: attachment.spaceId } - } catch (error) { - this.logger.error(`Failed to get image dimensions: ${attachMeta.path}`, error) - } + }) + await new WorkspaceImporter(this.client, this.fileUploader, { attachments }).performImport() - const pathDetails = path.parse(attachMeta.path) - const childrenDir = path.join(pathDetails.dir, pathDetails.name.replace(pathDetails.ext, '')) - if (fs.existsSync(childrenDir) && fs.statSync(childrenDir).isDirectory()) { - attachment.drawings = await this.processDrawings(childrenDir) - } - } - - return attachment - } - - private async processDrawings (folderPath: string): Promise { - this.logger.log(`Processing drawings in ${folderPath}...`) - const entries = fs.readdirSync(folderPath, { withFileTypes: true }) - const drawings: ImportDrawing[] = [] - for (const entry of entries) { - const fullPath = path.join(folderPath, entry.name) - if (entry.isFile() && entry.name.endsWith('.json')) { - const content = fs.readFileSync(fullPath, 'utf8') - const json = JSON.parse(content) - if (json.class !== 'drawing:class:Drawing') { - this.logger.log(`Skipping ${fullPath}: not a drawing`) - continue - } - - drawings.push({ - contentProvider: async () => { - return JSON.stringify(json.content) - } - }) - } - } - return drawings + console.log('========================================') + console.log('IMPORT SUCCESS') } private async processImportFolder (folderPath: string): Promise { @@ -406,11 +336,11 @@ export class UnifiedFormatImporter { const spacePath = path.join(folderPath, spaceName) try { - this.logger.log(`Processing ${spaceName}...`) + console.log(`Processing ${spaceName}...`) const spaceConfig = yaml.load(fs.readFileSync(yamlPath, 'utf8')) as UnifiedSpaceSettings - if (spaceConfig?.class === undefined) { - this.logger.error(`Skipping ${spaceName}: not a space - no class specified`) + if (spaceConfig.class === undefined) { + console.warn(`Skipping ${spaceName}: not a space - no class specified`) continue } @@ -443,6 +373,8 @@ export class UnifiedFormatImporter { } } + await this.processAttachments(folderPath) + return builder.build() } @@ -460,7 +392,7 @@ export class UnifiedFormatImporter { const issueHeader = (await this.readYamlHeader(issuePath)) as UnifiedIssueHeader if (issueHeader.class === undefined) { - this.logger.error(`Skipping ${issueFile}: not an issue`) + console.warn(`Skipping ${issueFile}: not an issue`) continue } @@ -488,7 +420,7 @@ export class UnifiedFormatImporter { priority: issueHeader.priority, estimation: issueHeader.estimation, remainingTime: issueHeader.remainingTime, - comments: await this.processComments(currentPath, issueHeader.comments), + comments: this.processComments(issueHeader.comments), subdocs: [], // Will be added via builder assignee: this.findPersonByName(issueHeader.assignee) } @@ -538,7 +470,7 @@ export class UnifiedFormatImporter { const docHeader = (await this.readYamlHeader(docPath)) as UnifiedDocumentHeader if (docHeader.class === undefined) { - this.logger.error(`Skipping ${docFile}: not a document`) + console.warn(`Skipping ${docFile}: not a document`) continue } @@ -574,27 +506,13 @@ export class UnifiedFormatImporter { } } - private processComments (currentPath: string, comments: UnifiedComment[] = []): Promise { - return Promise.all( - comments.map(async (comment) => { - const attachments: ImportAttachment[] = [] - if (comment.attachments !== undefined) { - for (const attachmentPath of comment.attachments) { - const fullPath = path.resolve(currentPath, attachmentPath) - const attachmentMeta = this.fileMetadataByPath.get(fullPath) - if (attachmentMeta !== undefined) { - const importAttachment = await this.processAttachment(attachmentMeta) - attachments.push(importAttachment) - } - } - } - return { - text: comment.text, - author: this.findAccountByEmail(comment.author), - attachments - } - }) - ) + private processComments (comments: UnifiedComment[] = []): ImportComment[] { + return comments.map((comment) => { + return { + text: comment.text, + author: this.findAccountByEmail(comment.author) + } + }) } private processProjectTypes (wsHeader: UnifiedWorkspaceSettings): ImportProjectType[] { @@ -616,14 +534,11 @@ export class UnifiedFormatImporter { private async processProject (projectHeader: UnifiedProjectSettings): Promise { return { class: tracker.class.Project, - id: projectHeader.id as Ref, title: projectHeader.title, identifier: projectHeader.identifier, private: projectHeader.private ?? false, autoJoin: projectHeader.autoJoin ?? true, - archived: projectHeader.archived ?? false, description: projectHeader.description, - emoji: projectHeader.emoji, defaultIssueStatus: projectHeader.defaultIssueStatus !== undefined ? { name: projectHeader.defaultIssueStatus } : undefined, owners: @@ -640,9 +555,7 @@ export class UnifiedFormatImporter { title: spaceHeader.title, private: spaceHeader.private ?? false, autoJoin: spaceHeader.autoJoin ?? true, - archived: spaceHeader.archived ?? false, description: spaceHeader.description, - emoji: spaceHeader.emoji, owners: spaceHeader.owners !== undefined ? spaceHeader.owners.map((email) => this.findAccountByEmail(email)) : [], members: spaceHeader.members !== undefined ? spaceHeader.members.map((email) => this.findAccountByEmail(email)) : [], @@ -651,7 +564,7 @@ export class UnifiedFormatImporter { } private async readYamlHeader (filePath: string): Promise { - this.logger.log('Read YAML header from: ' + filePath) + console.log('Read YAML header from: ', filePath) const content = fs.readFileSync(filePath, 'utf8') const match = content.match(/^---\n([\s\S]*?)\n---/) if (match != null) { @@ -688,7 +601,7 @@ export class UnifiedFormatImporter { }, new Map()) } - private async collectFileMetadata (folderPath: string): Promise { + private async processAttachments (folderPath: string): Promise { const processDir = async (dir: string): Promise => { const entries = fs.readdirSync(dir, { withFileTypes: true }) @@ -698,8 +611,11 @@ export class UnifiedFormatImporter { if (entry.isDirectory()) { await processDir(fullPath) } else if (entry.isFile()) { - const attachmentId = generateId() - this.fileMetadataByPath.set(fullPath, { id: attachmentId, name: entry.name, path: fullPath }) + // Skip files that are already processed as documents or issues + if (!this.metadataByFilePath.has(fullPath)) { + const attachmentId = generateId() + this.attachMetadataByPath.set(fullPath, { id: attachmentId, name: entry.name, path: fullPath }) + } } } } diff --git a/packages/importer/src/importer/builder.ts b/dev/import-tool/src/importer/builder.ts similarity index 94% rename from packages/importer/src/importer/builder.ts rename to dev/import-tool/src/importer/builder.ts index c9f0ccbc48..d47a84a5f6 100644 --- a/packages/importer/src/importer/builder.ts +++ b/dev/import-tool/src/importer/builder.ts @@ -256,18 +256,6 @@ export class ImportWorkspaceBuilder { errors.push('defaultIssueStatus not found: ' + project.defaultIssueStatus.name) } - if (project.id !== undefined && project.id !== tracker.project.DefaultProject) { - errors.push('update operation is only allowed for tracker:project:DefaultProject') - } - - if (project.archived !== undefined) { - errors.push(...this.validateType(project.archived, 'boolean', 'archived')) - } - - if (project.emoji !== undefined) { - errors.push(...this.validateEmoji(project.emoji)) - } - errors.push(...this.validateProjectIdentifier(project.identifier)) return errors } @@ -315,22 +303,9 @@ export class ImportWorkspaceBuilder { errors.push(...this.validateType(teamspace.description, 'string', 'description')) } - if (teamspace.archived !== undefined) { - errors.push(...this.validateType(teamspace.archived, 'boolean', 'archived')) - } - - if (teamspace.emoji !== undefined) { - errors.push(...this.validateType(teamspace.emoji, 'string', 'emoji')) - } - - if (teamspace.emoji !== undefined) { - errors.push(...this.validateEmoji(teamspace.emoji)) - } - if (!this.validateStringDefined(teamspace.title)) { errors.push('title is required') } - if (teamspace.class !== document.class.Teamspace) { errors.push('invalid class: ' + teamspace.class) } @@ -471,14 +446,6 @@ export class ImportWorkspaceBuilder { issue.subdocs = childIssues } - private validateEmoji (emoji: string): string[] { - const errors: string[] = [] - if (typeof emoji === 'string' && emoji.codePointAt(0) == null) { - errors.push('Invalid emoji: ' + emoji) - } - return errors - } - private validateType (value: unknown, type: 'string' | 'number' | 'boolean', fieldName: string): string[] { const errors: string[] = [] switch (type) { diff --git a/packages/importer/src/importer/dowloader.ts b/dev/import-tool/src/importer/dowloader.ts similarity index 100% rename from packages/importer/src/importer/dowloader.ts rename to dev/import-tool/src/importer/dowloader.ts diff --git a/packages/importer/src/importer/importer.ts b/dev/import-tool/src/importer/importer.ts similarity index 73% rename from packages/importer/src/importer/importer.ts rename to dev/import-tool/src/importer/importer.ts index 571e6fc786..93f285ee70 100644 --- a/packages/importer/src/importer/importer.ts +++ b/dev/import-tool/src/importer/importer.ts @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. // -import attachment, { Drawing, type Attachment } from '@hcengineering/attachment' +import attachment, { type Attachment } from '@hcengineering/attachment' import chunter, { type ChatMessage } from '@hcengineering/chunter' import { type Person } from '@hcengineering/contact' import core, { @@ -51,10 +51,8 @@ import tracker, { type Project, TimeReportDayType } from '@hcengineering/tracker' -import view from '@hcengineering/view' import { type MarkdownPreprocessor, NoopMarkdownPreprocessor } from './preprocessor' import { type FileUploader } from './uploader' -import { Logger } from './logger' export interface ImportWorkspace { projectTypes?: ImportProjectType[] @@ -84,9 +82,7 @@ export interface ImportSpace { title: string private: boolean autoJoin?: boolean - archived?: boolean description?: string - emoji?: string owners?: Ref[] members?: Ref[] docs: T[] @@ -111,7 +107,6 @@ export interface ImportDocument extends ImportDoc { export interface ImportProject extends ImportSpace { class: Ref> - id?: Ref identifier: string projectType?: ImportProjectType defaultIssueStatus?: ImportStatus @@ -144,17 +139,6 @@ export interface ImportAttachment { parentId?: Ref parentClass?: Ref>> spaceId?: Ref - metadata?: ImportImageMetadata - drawings?: ImportDrawing[] -} - -export interface ImportImageMetadata { - originalWidth: number - originalHeight: number -} - -export interface ImportDrawing { - contentProvider: () => Promise } export class WorkspaceImporter { @@ -163,7 +147,6 @@ export class WorkspaceImporter { constructor ( private readonly client: TxOperations, - private readonly logger: Logger, private readonly fileUploader: FileUploader, private readonly workspaceData: ImportWorkspace, private readonly preprocessor: MarkdownPreprocessor = new NoopMarkdownPreprocessor() @@ -251,9 +234,9 @@ export class WorkspaceImporter { } async importTeamspace (space: ImportTeamspace): Promise> { - this.logger.log('Creating teamspace: ' + space.title) + console.log('Creating teamspace: ', space.title) const teamspaceId = await this.createTeamspace(space) - this.logger.log('Teamspace created: ' + teamspaceId) + console.log('Teamspace created: ', teamspaceId) for (const doc of space.docs) { await this.createDocumentWithSubdocs(doc, document.ids.NoParent, teamspaceId) } @@ -265,9 +248,9 @@ export class WorkspaceImporter { parentId: Ref, teamspaceId: Ref ): Promise> { - this.logger.log('Creating document: ' + doc.title) + console.log('Creating document: ', doc.title) const documentId = await this.createDocument(doc, parentId, teamspaceId) - this.logger.log('Document created: ' + documentId) + console.log('Document created: ', documentId) for (const child of doc.subdocs) { await this.createDocumentWithSubdocs(child, documentId, teamspaceId) } @@ -276,19 +259,16 @@ export class WorkspaceImporter { async createTeamspace (space: ImportTeamspace): Promise> { const teamspaceId = generateId() - const codePoint = space.emoji?.codePointAt(0) const data = { type: document.spaceType.DefaultTeamspaceType, description: space.description ?? '', title: space.title, name: space.title, private: space.private, - color: codePoint, - icon: codePoint === undefined ? undefined : view.ids.IconWithEmoji, owners: space.owners ?? [], members: space.members ?? [], autoJoin: space.autoJoin, - archived: space.archived ?? false + archived: false } await this.client.createDoc(document.class.Teamspace, core.space.Space, data, teamspaceId) return teamspaceId @@ -307,7 +287,7 @@ export class WorkspaceImporter { const lastRank = await getFirstRank(this.client, teamspaceId, parentId) const rank = makeRank(lastRank, undefined) - const data: Data = { + const attachedData: Data = { title: doc.title, content: contentId, parent: parentId, @@ -319,22 +299,14 @@ export class WorkspaceImporter { rank } - await this.client.createDoc(document.class.Document, teamspaceId, data, id) + await this.client.createDoc(document.class.Document, teamspaceId, attachedData, id) return id } async importProject (project: ImportProject): Promise> { - let projectId: Ref - if (project.id === tracker.project.DefaultProject) { - this.logger.log('Setting up default project: ' + project.title) - projectId = tracker.project.DefaultProject - await this.updateProject(projectId, project) - this.logger.log('Default project updated: ' + projectId) - } else { - this.logger.log('Creating project: ', project.title) - projectId = await this.createProject(project) - this.logger.log('Project created: ' + projectId) - } + console.log('Creating project: ', project.title) + const projectId = await this.createProject(project) + console.log('Project created: ' + projectId) const projectDoc = await this.client.findOne(tracker.class.Project, { _id: projectId }) if (projectDoc === undefined) { @@ -342,7 +314,7 @@ export class WorkspaceImporter { } for (const issue of project.docs) { - await this.createIssueWithSubissues(issue, tracker.ids.NoParent, projectDoc, projectId, []) + await this.createIssueWithSubissues(issue, tracker.ids.NoParent, projectDoc, []) } return projectId } @@ -351,69 +323,31 @@ export class WorkspaceImporter { issue: ImportIssue, parentId: Ref, project: Project, - spaceId: Ref, parentsInfo: IssueParentInfo[] ): Promise<{ id: Ref, identifier: string }> { - this.logger.log('Creating issue: ' + issue.title) - const issueResult = await this.createIssue(issue, project, parentId, spaceId, parentsInfo) - this.logger.log('Issue created: ' + JSON.stringify(issueResult)) + console.log('Creating issue: ', issue.title) + const issueResult = await this.createIssue(issue, project, parentId, parentsInfo) + console.log('Issue created: ', issueResult) if (issue.subdocs.length > 0) { const parentsInfoEx = [ { parentId: issueResult.id, parentTitle: issue.title, - space: spaceId, + space: project._id, identifier: issueResult.identifier }, ...parentsInfo ] for (const child of issue.subdocs) { - await this.createIssueWithSubissues(child as ImportIssue, issueResult.id, project, spaceId, parentsInfoEx) + await this.createIssueWithSubissues(child as ImportIssue, issueResult.id, project, parentsInfoEx) } } return issueResult } - async updateProject (projectId: Ref, project: ImportProject): Promise> { - const oldProject = await this.client.findOne(tracker.class.Project, { _id: projectId }) - if (oldProject === undefined) { - throw new Error('Project not found: ' + projectId) - } - const maxIssueNumber = this.getMaxImportIssueNumber(project) - const codePoint = project.emoji?.codePointAt(0) - const projectData = { - name: project.title, - private: project.private, - description: project.description ?? oldProject.description, - members: project.members ?? oldProject.members, - owners: project.owners ?? oldProject.owners, - archived: project.archived ?? oldProject.archived, - autoJoin: project.autoJoin ?? oldProject.autoJoin, - identifier: - project.identifier !== undefined - ? await this.uniqueProjectIdentifier(project.identifier) - : oldProject.identifier, - sequence: Math.max(oldProject.sequence, maxIssueNumber), - color: codePoint ?? oldProject.color, - icon: codePoint === undefined ? undefined : view.ids.IconWithEmoji, - defaultIssueStatus: - project.defaultIssueStatus !== undefined - ? this.issueStatusByName.get(project.defaultIssueStatus.name) - : oldProject.defaultIssueStatus, - defaultTimeReportDay: oldProject.defaultTimeReportDay, - type: - project.projectType !== undefined - ? this.projectTypeByName.get(project.projectType.name) ?? tracker.ids.ClassingProjectType - : oldProject.type - } - - await this.client.updateDoc(tracker.class.Project, core.space.Space, projectId, projectData) - return projectId - } - async createProject (project: ImportProject): Promise> { const projectId = generateId() @@ -428,7 +362,6 @@ export class WorkspaceImporter { : tracker.status.Backlog const identifier = await this.uniqueProjectIdentifier(project.identifier) - const codePoint = project.emoji?.codePointAt(0) const projectData = { name: project.title, description: project.description ?? '', @@ -438,9 +371,7 @@ export class WorkspaceImporter { archived: false, autoJoin: project.autoJoin, identifier, - sequence: this.getMaxImportIssueNumber(project), - color: codePoint, - icon: codePoint != null ? view.ids.IconWithEmoji : undefined, + sequence: 0, defaultIssueStatus: defaultIssueStatus ?? tracker.status.Backlog, defaultTimeReportDay: TimeReportDayType.PreviousWorkDay, type: projectType as Ref @@ -453,30 +384,24 @@ export class WorkspaceImporter { return projectId } - private getMaxImportIssueNumber (project: ImportProject): number { - const maxIssueNumber = Math.max(...project.docs.map((doc) => doc.number ?? 0)) - return maxIssueNumber - } - async createIssue ( issue: ImportIssue, project: Project, parentId: Ref, - spaceId: Ref, parentsInfo: IssueParentInfo[] ): Promise<{ id: Ref, identifier: string }> { const issueId = issue.id ?? generateId() const content = await issue.descrProvider() const collabId = makeCollabId(tracker.class.Issue, issueId, 'description') - const contentId = await this.createCollaborativeContent(issueId, collabId, content, spaceId) + const contentId = await this.createCollaborativeContent(issueId, collabId, content, project._id) const { number, identifier } = issue.number !== undefined ? { number: issue.number, identifier: `${project.identifier}-${issue.number}` } - : await this.getNextIssueIdentifier(project, spaceId) + : await this.getNextIssueIdentifier(project) const kind = await this.getIssueKind(project) - const rank = await this.getIssueRank(project, spaceId) + const rank = await this.getIssueRank(project) const status = await this.findIssueStatusByName(issue.status.name) const priority = issue.priority !== undefined @@ -511,7 +436,7 @@ export class WorkspaceImporter { await this.client.addCollection( tracker.class.Issue, - spaceId, + project._id, parentId, tracker.class.Issue, 'subIssues', @@ -520,19 +445,16 @@ export class WorkspaceImporter { ) if (issue.comments !== undefined) { - await this.importComments(issueId, issue.comments, spaceId) + await this.importComments(issueId, issue.comments, project._id) } return { id: issueId, identifier } } - private async getNextIssueIdentifier ( - project: Project, - spaceId: Ref - ): Promise<{ number: number, identifier: string }> { + private async getNextIssueIdentifier (project: Project): Promise<{ number: number, identifier: string }> { const incResult = await this.client.updateDoc( tracker.class.Project, core.space.Space, - spaceId, + project._id, { $inc: { sequence: 1 } }, true ) @@ -545,15 +467,15 @@ export class WorkspaceImporter { const taskKind = project?.type !== undefined ? { parent: project.type } : {} const kind = await this.client.findOne(task.class.TaskType, taskKind) if (kind === undefined) { - throw new Error(`Task type not found for project: ${project.name}`) + throw new Error(`Task type not found for project: ${project._id}`) } return kind } - private async getIssueRank (project: Project, spaceId: Ref): Promise { + private async getIssueRank (project: Project): Promise { const lastIssue = await this.client.findOne( tracker.class.Issue, - { space: spaceId }, + { space: project._id }, { sort: { rank: SortingOrder.Descending } } ) return makeRank(lastIssue?.rank, undefined) @@ -607,44 +529,28 @@ export class WorkspaceImporter { ): Promise { const blob = await attachment.blobProvider() if (blob === null) { - this.logger.error('Failed to read attachment file: ' + attachment.title) + console.warn('Failed to read attachment file: ', attachment.title) return } - const file = new File([blob], attachment.title, { type: blob.type }) + const file = new File([blob], attachment.title) try { - const blobId = await this.createAttachment( - attachment.id ?? generateId(), - spaceId, - parentId, - parentClass, - file, - attachment.metadata - ) - - if (attachment.drawings !== undefined) { - for (const drawing of attachment.drawings) { - await this.createDrawing(blobId, drawing, spaceId) - } - } + await this.createAttachment(attachment.id ?? generateId(), file, spaceId, parentId, parentClass) } catch { - this.logger.error('Failed to upload attachment file: ', attachment.title) + console.warn('Failed to upload attachment file: ', attachment.title) } } private async createAttachment ( id: Ref, + file: File, spaceId: Ref, parentId: Ref, - parentClass: Ref>>, - file: File, - metadata?: ImportImageMetadata - ): Promise> { - const uploadResult = await this.fileUploader.uploadFile(id, file) - if (!uploadResult.success) { - throw new Error('Failed to upload attachment file: ' + file.name) - } + parentClass: Ref>> + ): Promise> { + const attachmentId = generateId() + const blobId = await this.fileUploader.uploadFile(id, file) await this.client.addCollection( attachment.class.Attachment, spaceId, @@ -652,31 +558,15 @@ export class WorkspaceImporter { parentClass, 'attachments', { - file: uploadResult.id, + file: blobId, lastModified: Date.now(), name: file.name, size: file.size, - type: file.type, - metadata + type: file.type }, id ) - return uploadResult.id - } - - private async createDrawing ( - blobId: Ref, - drawing: ImportDrawing, - spaceId: Ref - ): Promise> { - const id = generateId() - const data: Data = { - parent: blobId, - parentClass: core.class.Blob, - content: await drawing.contentProvider() - } - await this.client.createDoc(attachment.class.Drawing, spaceId, data, id) - return id + return attachmentId } // Collaborative content handling @@ -690,12 +580,9 @@ export class WorkspaceImporter { const processedJson = this.preprocessor.process(json, id, spaceId) const markup = jsonToMarkup(processedJson) + const buffer = Buffer.from(markup) - const result = await this.fileUploader.uploadCollaborativeDoc(collabId, markup) - if (result.success) { - return result.id - } - throw new Error('Failed to upload collaborative document: ' + id) + return await this.fileUploader.uploadCollaborativeDoc(collabId, buffer) } async findIssueStatusByName (name: string): Promise> { diff --git a/packages/importer/src/importer/preprocessor.ts b/dev/import-tool/src/importer/preprocessor.ts similarity index 100% rename from packages/importer/src/importer/preprocessor.ts rename to dev/import-tool/src/importer/preprocessor.ts diff --git a/packages/importer/src/importer/frontUploader.ts b/dev/import-tool/src/importer/uploader.ts similarity index 53% rename from packages/importer/src/importer/frontUploader.ts rename to dev/import-tool/src/importer/uploader.ts index 95fe9b09b7..d2ae4fb7d9 100644 --- a/packages/importer/src/importer/frontUploader.ts +++ b/dev/import-tool/src/importer/uploader.ts @@ -13,27 +13,19 @@ // limitations under the License. // import { + type Ref, + type Blob as PlatformBlob, type CollaborativeDoc, concatLink, - makeCollabJsonId, - Markup, - type Blob as PlatformBlob, - type Ref + makeCollabJsonId } from '@hcengineering/core' -import { FileUploader, UploadResult } from './uploader' -interface FileUploadError { - key: string - error: string +export interface FileUploader { + uploadFile: (name: string, file: Blob) => Promise> + uploadCollaborativeDoc: (collabId: CollaborativeDoc, data: Buffer) => Promise> + getFileUrl: (id: string) => string } -interface FileUploadSuccess { - key: string - id: string -} - -type FileUploadResult = FileUploadSuccess | FileUploadError - export class FrontFileUploader implements FileUploader { constructor ( private readonly frontUrl: string, @@ -43,11 +35,11 @@ export class FrontFileUploader implements FileUploader { this.getFileUrl = this.getFileUrl.bind(this) } - public async uploadFile (name: string, blob: Blob): Promise { + public async uploadFile (name: string, file: Blob): Promise> { const form = new FormData() - form.append('file', blob, name) + form.append('file', file, name) - const response = await fetch(concatLink(this.frontUrl, '/files'), { + const res = await fetch(concatLink(this.frontUrl, '/files'), { method: 'POST', headers: { Authorization: 'Bearer ' + this.token @@ -55,36 +47,20 @@ export class FrontFileUploader implements FileUploader { body: form }) - if (response.status !== 200) { - return { success: false, error: response.statusText } + if (res.ok && res.status === 200) { + return name as Ref } - const responseText = await response.text() - if (responseText === undefined) { - return { success: false, error: response.statusText } - } - - const uploadResult = JSON.parse(responseText) as FileUploadResult[] - if (!Array.isArray(uploadResult) || uploadResult.length === 0) { - return { success: false, error: response.statusText } - } - - const result = uploadResult[0] - if ('error' in result) { - return { success: false, error: result.error } - } - - return { success: true, id: result.id as Ref } + throw new Error('Failed to upload file') } public getFileUrl (id: string): string { return concatLink(this.frontUrl, `/files/${this.workspaceId}/${id}?file=${id}&workspace=${this.workspaceId}`) } - public async uploadCollaborativeDoc (collabId: CollaborativeDoc, content: Markup): Promise { - const buffer = Buffer.from(content) + public async uploadCollaborativeDoc (collabId: CollaborativeDoc, data: Buffer): Promise> { const blobId = makeCollabJsonId(collabId) - const blob = new Blob([buffer], { type: 'application/json' }) + const blob = new Blob([data], { type: 'application/json' }) return await this.uploadFile(blobId, blob) } } diff --git a/dev/import-tool/src/index.ts b/dev/import-tool/src/index.ts index ff40a87eaa..3ca141dd71 100644 --- a/dev/import-tool/src/index.ts +++ b/dev/import-tool/src/index.ts @@ -20,29 +20,11 @@ import serverClientPlugin, { selectWorkspace } from '@hcengineering/server-client' import { program } from 'commander' +import { importNotion } from './notion/notion' import { setMetadata } from '@hcengineering/platform' -import { - UnifiedFormatImporter, - ClickupImporter, - importNotion, - FrontFileUploader, - type FileUploader, - type Logger -} from '@hcengineering/importer' - -class ConsoleLogger implements Logger { - log (msg: string, data?: any): void { - console.log(msg, data) - } - - warn (msg: string, data?: any): void { - console.warn(msg, data) - } - - error (msg: string, data?: any): void { - console.error(msg, data) - } -} +import { FrontFileUploader, type FileUploader } from './importer/uploader' +import { ClickupImporter } from './clickup/clickup' +import { UnifiedFormatImporter } from './huly/unified' /** * @public @@ -145,7 +127,7 @@ export function importTool (): void { .action(async (file: string, cmd) => { const { workspace, user, password } = cmd await authorize(user, password, workspace, async (client, uploader) => { - const importer = new ClickupImporter(client, uploader, new ConsoleLogger()) + const importer = new ClickupImporter(client, uploader) await importer.importClickUpTasks(file) }) }) @@ -160,7 +142,7 @@ export function importTool (): void { .action(async (dir: string, cmd) => { const { workspace, user, password } = cmd await authorize(user, password, workspace, async (client, uploader) => { - const importer = new UnifiedFormatImporter(client, uploader, new ConsoleLogger()) + const importer = new UnifiedFormatImporter(client, uploader) await importer.importFolder(dir) }) }) diff --git a/dev/import-tool/docs/notion/README.md b/dev/import-tool/src/notion/README.md similarity index 100% rename from dev/import-tool/docs/notion/README.md rename to dev/import-tool/src/notion/README.md diff --git a/packages/importer/src/notion/notion.ts b/dev/import-tool/src/notion/notion.ts similarity index 98% rename from packages/importer/src/notion/notion.ts rename to dev/import-tool/src/notion/notion.ts index c61b56f207..aff260f759 100644 --- a/packages/importer/src/notion/notion.ts +++ b/dev/import-tool/src/notion/notion.ts @@ -441,13 +441,11 @@ async function importPageDocument ( preProcessMarkdown(json, documentMetaMap, fileUploader) } const markup = jsonToMarkup(json) + const buffer = Buffer.from(markup) const id = docMeta.id as Ref const collabId = makeCollabId(document.class.Document, id, 'content') - const uploadResult = await fileUploader.uploadCollaborativeDoc(collabId, markup) - if (!uploadResult.success) { - throw new Error('Failed to upload collaborative document: ' + docMeta.id) - } + const blobId = await fileUploader.uploadCollaborativeDoc(collabId, buffer) const parent = (parentMeta?.id as Ref) ?? document.ids.NoParent @@ -456,7 +454,7 @@ async function importPageDocument ( const attachedData: Data = { title: docMeta.name, - content: uploadResult.id, + content: blobId, parent, attachments: 0, embeddings: 0, diff --git a/dev/tool/src/index.ts b/dev/tool/src/index.ts index 66594fabab..4d99f62fa7 100644 --- a/dev/tool/src/index.ts +++ b/dev/tool/src/index.ts @@ -60,7 +60,7 @@ import serverClientPlugin, { } from '@hcengineering/server-client' import { createBackupPipeline, getConfig } from '@hcengineering/server-pipeline' import serverToken, { decodeToken, generateToken } from '@hcengineering/server-token' -import { FileModelLogger } from '@hcengineering/server-tool' +import toolPlugin, { FileModelLogger } from '@hcengineering/server-tool' import { createWorkspace, upgradeWorkspace } from '@hcengineering/workspace-service' import path from 'path' @@ -167,6 +167,11 @@ export function devTool ( console.error('please provide transactor url.') } + const initScriptUrl = process.env.INIT_SCRIPT_URL + if (initScriptUrl !== undefined) { + setMetadata(toolPlugin.metadata.InitScriptURL, initScriptUrl) + } + setMetadata(accountPlugin.metadata.Transactors, transactorUrl) setMetadata(serverClientPlugin.metadata.Endpoint, accountsUrl) setMetadata(serverToken.metadata.Secret, serverSecret) diff --git a/packages/importer/.eslintrc.js b/packages/importer/.eslintrc.js deleted file mode 100644 index 72235dc283..0000000000 --- a/packages/importer/.eslintrc.js +++ /dev/null @@ -1,7 +0,0 @@ -module.exports = { - extends: ['./node_modules/@hcengineering/platform-rig/profiles/default/eslint.config.json'], - parserOptions: { - tsconfigRootDir: __dirname, - project: './tsconfig.json' - } -} diff --git a/packages/importer/.npmignore b/packages/importer/.npmignore deleted file mode 100644 index e3ec093c38..0000000000 --- a/packages/importer/.npmignore +++ /dev/null @@ -1,4 +0,0 @@ -* -!/lib/** -!CHANGELOG.md -/lib/**/__tests__/ diff --git a/packages/importer/jest.config.js b/packages/importer/jest.config.js deleted file mode 100644 index 2cfd408b67..0000000000 --- a/packages/importer/jest.config.js +++ /dev/null @@ -1,7 +0,0 @@ -module.exports = { - preset: 'ts-jest', - testEnvironment: 'node', - testMatch: ['**/?(*.)+(spec|test).[jt]s?(x)'], - roots: ["./src"], - coverageReporters: ["text-summary", "html"] -} diff --git a/packages/importer/package.json b/packages/importer/package.json deleted file mode 100644 index 35424c2174..0000000000 --- a/packages/importer/package.json +++ /dev/null @@ -1,69 +0,0 @@ -{ - "name": "@hcengineering/importer", - "version": "0.6.1", - "main": "lib/index.js", - "svelte": "src/index.ts", - "types": "types/index.d.ts", - "files": [ - "lib/**/*", - "types/**/*", - "tsconfig.json" - ], - "author": "Anticrm Platform Contributors", - "license": "EPL-2.0", - "scripts": { - "build": "compile", - "build:watch": "compile", - "test": "jest --passWithNoTests --silent", - "format": "format src", - "_phase:build": "compile transpile src", - "_phase:test": "jest --passWithNoTests --silent", - "_phase:format": "format src", - "_phase:validate": "compile validate" - }, - "devDependencies": { - "@hcengineering/platform-rig": "^0.6.0", - "@typescript-eslint/eslint-plugin": "^6.11.0", - "@typescript-eslint/parser": "^6.11.0", - "eslint": "^8.54.0", - "eslint-config-standard-with-typescript": "^40.0.0", - "eslint-plugin-import": "^2.26.0", - "eslint-plugin-n": "^15.4.0", - "eslint-plugin-promise": "^6.1.1", - "prettier": "^3.1.0", - "typescript": "^5.3.3", - "jest": "^29.7.0", - "ts-jest": "^29.1.1", - "@types/jest": "^29.5.5", - "@types/node": "~20.11.16", - "@types/mime-types": "~2.1.1", - "@types/csvtojson": "^2.0.0", - "@types/js-yaml": "^4.0.9" - }, - "dependencies": { - "@hcengineering/attachment": "^0.6.14", - "@hcengineering/chunter": "^0.6.20", - "@hcengineering/collaboration": "^0.6.0", - "@hcengineering/contact": "^0.6.24", - "@hcengineering/core": "^0.6.32", - "@hcengineering/document": "^0.6.0", - "@hcengineering/model-attachment": "^0.6.0", - "@hcengineering/model-core": "^0.6.0", - "@hcengineering/platform": "^0.6.11", - "@hcengineering/rank": "^0.6.4", - "@hcengineering/server-core": "^0.6.1", - "@hcengineering/task": "^0.6.20", - "@hcengineering/text": "^0.6.5", - "@hcengineering/tracker": "^0.6.24", - "@hcengineering/view": "^0.6.13", - "commander": "^8.1.0", - "mime-types": "~2.1.34", - "csvtojson": "^2.0.10", - "js-yaml": "^4.1.0", - "image-size": "^1.1.1" - }, - "repository": "https://github.com/hcengineering/platform", - "publishConfig": { - "registry": "https://npm.pkg.github.com" - } -} diff --git a/packages/importer/src/importer/logger.ts b/packages/importer/src/importer/logger.ts deleted file mode 100644 index 5d734933af..0000000000 --- a/packages/importer/src/importer/logger.ts +++ /dev/null @@ -1,18 +0,0 @@ -// -// Copyright © 2024 Hardcore Engineering Inc. -// -// Licensed under the Eclipse Public License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. You may -// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// -// See the License for the specific language governing permissions and -// limitations under the License. -// -export interface Logger { - log: (msg: string, data?: any) => void - error: (msg: string, data?: any) => void -} diff --git a/packages/importer/src/importer/storageUploader.ts b/packages/importer/src/importer/storageUploader.ts deleted file mode 100644 index 3668d55985..0000000000 --- a/packages/importer/src/importer/storageUploader.ts +++ /dev/null @@ -1,59 +0,0 @@ -// -// Copyright © 2024 Hardcore Engineering Inc. -// -// Licensed under the Eclipse Public License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. You may -// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// -// See the License for the specific language governing permissions and -// limitations under the License. -// -import { saveCollabJson } from '@hcengineering/collaboration' -import { - CollaborativeDoc, - Markup, - MeasureContext, - Blob as PlatformBlob, - Ref, - WorkspaceIdWithUrl -} from '@hcengineering/core' -import type { StorageAdapter } from '@hcengineering/server-core' -import { FileUploader, UploadResult } from './uploader' - -export class StorageFileUploader implements FileUploader { - constructor ( - private readonly ctx: MeasureContext, - private readonly storageAdapter: StorageAdapter, - private readonly wsUrl: WorkspaceIdWithUrl - ) { - this.uploadFile = this.uploadFile.bind(this) - } - - public async uploadFile (id: string, blob: Blob): Promise { - try { - const arrayBuffer = await blob.arrayBuffer() - const buffer = Buffer.from(arrayBuffer) - await this.storageAdapter.put(this.ctx, this.wsUrl, id, buffer, blob.type, buffer.byteLength) - return { success: true, id: id as Ref } - } catch (error) { - return { success: false, error: error instanceof Error ? error.message : String(error) } - } - } - - public async uploadCollaborativeDoc (collabId: CollaborativeDoc, content: Markup): Promise { - try { - const blobId = await saveCollabJson(this.ctx, this.storageAdapter, this.wsUrl, collabId, content) - return { success: true, id: blobId } - } catch (error) { - return { success: false, error: error instanceof Error ? error.message : String(error) } - } - } - - public getFileUrl (id: string): string { - return '' - } -} diff --git a/packages/importer/src/importer/uploader.ts b/packages/importer/src/importer/uploader.ts deleted file mode 100644 index 7f2a4cc0c4..0000000000 --- a/packages/importer/src/importer/uploader.ts +++ /dev/null @@ -1,33 +0,0 @@ -// -// Copyright © 2024 Hardcore Engineering Inc. -// -// Licensed under the Eclipse Public License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. You may -// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// -// See the License for the specific language governing permissions and -// limitations under the License. -// -import { type CollaborativeDoc, type Blob as PlatformBlob, type Markup, type Ref } from '@hcengineering/core' - -export interface SuccessUploadResult { - success: true - id: Ref -} - -export interface FailureUploadResult { - success: false - error: string -} - -export type UploadResult = SuccessUploadResult | FailureUploadResult - -export interface FileUploader { - uploadFile: (name: string, blob: Blob) => Promise - uploadCollaborativeDoc: (collabId: CollaborativeDoc, content: Markup) => Promise - getFileUrl: (id: string) => string -} diff --git a/packages/importer/src/index.ts b/packages/importer/src/index.ts deleted file mode 100644 index 83ab51cf82..0000000000 --- a/packages/importer/src/index.ts +++ /dev/null @@ -1,23 +0,0 @@ -// -// Copyright © 2024 Hardcore Engineering Inc. -// -// Licensed under the Eclipse Public License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. You may -// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// -// See the License for the specific language governing permissions and -// limitations under the License. -// - -export * from './huly/unified' -export * from './clickup/clickup' -export * from './notion/notion' - -export * from './importer/uploader' -export * from './importer/storageUploader' -export * from './importer/frontUploader' -export * from './importer/logger' diff --git a/packages/importer/tsconfig.json b/packages/importer/tsconfig.json deleted file mode 100644 index 59e4fd4297..0000000000 --- a/packages/importer/tsconfig.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "extends": "./node_modules/@hcengineering/platform-rig/profiles/default/tsconfig.json", - - "compilerOptions": { - "rootDir": "./src", - "outDir": "./lib", - "declarationDir": "./types", - "tsBuildInfoFile": ".build/build.tsbuildinfo" - } -} \ No newline at end of file diff --git a/pods/workspace/Dockerfile b/pods/workspace/Dockerfile index 85a3276463..e648d52bde 100644 --- a/pods/workspace/Dockerfile +++ b/pods/workspace/Dockerfile @@ -14,7 +14,4 @@ ENV MALLOC_CONF=dirty_decay_ms:1000,narenas:2,background_thread:true COPY bundle/bundle.js ./ COPY bundle/bundle.js.map ./ -COPY ini[t]/ ./init-scripts/ - CMD [ "node", "bundle.js" ] - diff --git a/rush.json b/rush.json index fb79acc469..a2f8e5ed0e 100644 --- a/rush.json +++ b/rush.json @@ -455,11 +455,6 @@ "projectFolder": "packages/api-client", "shouldPublish": false }, - { - "packageName": "@hcengineering/importer", - "projectFolder": "packages/importer", - "shouldPublish": false - }, { "packageName": "@hcengineering/collaboration", "projectFolder": "server/collaboration", diff --git a/server/account-service/package.json b/server/account-service/package.json index cb7c4c5048..937390de69 100644 --- a/server/account-service/package.json +++ b/server/account-service/package.json @@ -53,6 +53,7 @@ "koa-router": "^12.0.1", "koa-bodyparser": "^4.4.1", "@koa/cors": "^5.0.0", + "@hcengineering/server-tool": "^0.6.0", "@hcengineering/server-token": "^0.6.11", "@hcengineering/analytics": "^0.6.0" } diff --git a/server/account-service/src/index.ts b/server/account-service/src/index.ts index 884289f5e9..3bfd42de8c 100644 --- a/server/account-service/src/index.ts +++ b/server/account-service/src/index.ts @@ -17,6 +17,7 @@ import { registerProviders } from '@hcengineering/auth-providers' import { metricsAggregate, type BrandingMap, type MeasureContext } from '@hcengineering/core' import platform, { Severity, Status, addStringsLoader, setMetadata } from '@hcengineering/platform' import serverToken, { decodeToken } from '@hcengineering/server-token' +import toolPlugin from '@hcengineering/server-tool' import cors from '@koa/cors' import { type IncomingHttpHeaders } from 'http' import Koa from 'koa' @@ -86,6 +87,11 @@ export function serveAccount (measureCtx: MeasureContext, brandings: BrandingMap setMetadata(serverToken.metadata.Secret, serverSecret) + const initScriptUrl = process.env.INIT_SCRIPT_URL + if (initScriptUrl !== undefined) { + setMetadata(toolPlugin.metadata.InitScriptURL, initScriptUrl) + } + const hasSignUp = process.env.DISABLE_SIGNUP !== 'true' const methods = getMethods(hasSignUp) diff --git a/server/tool/package.json b/server/tool/package.json index 51c9436fe2..387660b94d 100644 --- a/server/tool/package.json +++ b/server/tool/package.json @@ -46,7 +46,6 @@ "@hcengineering/contact": "^0.6.24", "@hcengineering/client-resources": "^0.6.27", "@hcengineering/client": "^0.6.18", - "@hcengineering/importer": "^0.6.1", "@hcengineering/model": "^0.6.11", "@hcengineering/rank": "^0.6.4", "uuid": "^8.3.2", diff --git a/server/tool/src/index.ts b/server/tool/src/index.ts index b7a945988a..2fb7408b86 100644 --- a/server/tool/src/index.ts +++ b/server/tool/src/index.ts @@ -192,20 +192,14 @@ export async function initializeWorkspace ( progress: (value: number) => Promise ): Promise { const initWS = branding?.initWorkspace ?? getMetadata(toolPlugin.metadata.InitWorkspace) - const initRepoDir = getMetadata(toolPlugin.metadata.InitRepoDir) - ctx.info('Init script details', { initWS, initRepoDir }) - if (initWS === undefined || initRepoDir === undefined) return - - const initScriptFile = path.resolve(initRepoDir, 'script.yaml') - if (!fs.existsSync(initScriptFile)) { - ctx.warn('Init script file not found in init directory', { initScriptFile }) - return - } - + const scriptUrl = getMetadata(toolPlugin.metadata.InitScriptURL) + ctx.info('Init script details', { scriptUrl, initWS }) + if (initWS === undefined || scriptUrl === undefined) return try { - const text = fs.readFileSync(initScriptFile, 'utf8') + // `https://raw.githubusercontent.com/hcengineering/init/main/script.yaml` + const req = await fetch(scriptUrl) + const text = await req.text() const scripts = yaml.load(text) as any as InitScript[] - let script: InitScript | undefined if (initWS !== undefined) { script = scripts.find((it) => it.name === initWS) @@ -217,7 +211,7 @@ export async function initializeWorkspace ( return } - const initializer = new WorkspaceInitializer(ctx, storageAdapter, wsUrl, client, initRepoDir) + const initializer = new WorkspaceInitializer(ctx, storageAdapter, wsUrl, client) await initializer.processScript(script, logger, progress) } catch (err: any) { ctx.error('Failed to initialize workspace', { error: err }) diff --git a/server/tool/src/initializer.ts b/server/tool/src/initializer.ts index 1ee620a67d..250115c633 100644 --- a/server/tool/src/initializer.ts +++ b/server/tool/src/initializer.ts @@ -15,11 +15,9 @@ import core, { } from '@hcengineering/core' import { ModelLogger } from '@hcengineering/model' import { makeRank } from '@hcengineering/rank' -import { StorageFileUploader, UnifiedFormatImporter } from '@hcengineering/importer' import type { StorageAdapter } from '@hcengineering/server-core' import { jsonToMarkup, parseMessageMarkdown } from '@hcengineering/text' import { v4 as uuid } from 'uuid' -import path from 'path' const fieldRegexp = /\${\S+?}/ @@ -37,7 +35,7 @@ export type InitStep = | UpdateStep | FindStep | UploadStep - | ImportStep + export interface CreateStep { type: 'create' _class: Ref> @@ -84,11 +82,6 @@ export interface UploadStep { resultVariable?: string } -export interface ImportStep { - type: 'import' - path: string -} - export type Props = Data & Partial & { space: Ref } export class WorkspaceInitializer { @@ -100,8 +93,7 @@ export class WorkspaceInitializer { private readonly ctx: MeasureContext, private readonly storageAdapter: StorageAdapter, private readonly wsUrl: WorkspaceIdWithUrl, - private readonly client: TxOperations, - private readonly initRepoDir: string + private readonly client: TxOperations ) {} async processScript ( @@ -126,8 +118,6 @@ export class WorkspaceInitializer { await this.processFind(step, vars) } else if (step.type === 'upload') { await this.processUpload(step, vars, logger) - } else if (step.type === 'import') { - await this.processImport(step, vars, logger) } await progress(Math.round(((index + 1) * 100) / script.steps.length)) @@ -162,18 +152,6 @@ export class WorkspaceInitializer { } } - private async processImport (step: ImportStep, vars: Record, logger: ModelLogger): Promise { - try { - const uploader = new StorageFileUploader(this.ctx, this.storageAdapter, this.wsUrl) - const initPath = path.resolve(this.initRepoDir, step.path) - const importer = new UnifiedFormatImporter(this.client, uploader, logger) - await importer.importFolder(initPath) - } catch (error) { - logger.error('Import failed', error) - throw error - } - } - private async processFind(step: FindStep, vars: Record): Promise { const query = this.fillProps(step.query, vars) const res = await this.client.findOne(step._class, { ...(query as any) }) diff --git a/server/tool/src/plugin.ts b/server/tool/src/plugin.ts index 3a3136caec..880560884d 100644 --- a/server/tool/src/plugin.ts +++ b/server/tool/src/plugin.ts @@ -11,7 +11,7 @@ export const toolId = 'tool' as Plugin const toolPlugin = plugin(toolId, { metadata: { InitWorkspace: '' as Metadata, - InitRepoDir: '' as Metadata + InitScriptURL: '' as Metadata } }) diff --git a/server/workspace-service/src/index.ts b/server/workspace-service/src/index.ts index 92f35bfd46..438642aafb 100644 --- a/server/workspace-service/src/index.ts +++ b/server/workspace-service/src/index.ts @@ -82,11 +82,12 @@ export function serveWorkspaceAccount ( if (initWS !== undefined) { setMetadata(toolPlugin.metadata.InitWorkspace, initWS) } - - const initRepoDir = process.env.INIT_REPO_DIR ?? './init-scripts' - setMetadata(toolPlugin.metadata.InitRepoDir, initRepoDir) - + const initScriptUrl = process.env.INIT_SCRIPT_URL + if (initScriptUrl !== undefined) { + setMetadata(toolPlugin.metadata.InitScriptURL, initScriptUrl) + } setMetadata(serverClientPlugin.metadata.UserAgent, 'WorkspaceService') + setMetadata(serverNotification.metadata.InboxOnlyNotifications, true) let canceled = false