Repository: arco-design/arco-cli Branch: main Commit: fb99d70e256c Files: 700 Total size: 864.2 KB Directory structure: gitextract_i8dph4m1/ ├── .eslintignore ├── .eslintrc.json ├── .github/ │ ├── ISSUE_TEMPLATE/ │ │ ├── bug_report.md │ │ └── feature_request.md │ └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── .husky/ │ └── pre-commit ├── .prettierrc.json ├── .scripts/ │ ├── build-esm.sh │ ├── build-type.sh │ ├── build.sh │ ├── flatPackages.js │ ├── getLocalPackages.js │ ├── linkLocalPackages.js │ └── linkTsPaths.js ├── .stylelintignore ├── .stylelintrc ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── CONTRIBUTING.zh-CN.md ├── LICENSE ├── README.md ├── babel.config.js ├── jest.config.ts ├── lerna.json ├── package.json ├── packages/ │ ├── arco/ │ │ ├── bin/ │ │ │ └── arco │ │ ├── package.json │ │ ├── src/ │ │ │ ├── app.ts │ │ │ ├── arco.aspect.ts │ │ │ ├── arco.main.runtime.ts │ │ │ ├── index.ts │ │ │ ├── loadCli.ts │ │ │ ├── manifest.ts │ │ │ └── types.ts │ │ └── tsconfig.json │ ├── aspect/ │ │ ├── package.json │ │ ├── src/ │ │ │ ├── bundler/ │ │ │ │ ├── browserRuntime.ts │ │ │ │ ├── bundler.aspect.ts │ │ │ │ ├── bundler.main.runtime.ts │ │ │ │ ├── bundler.ts │ │ │ │ ├── bundlerContext.ts │ │ │ │ ├── componentServer.ts │ │ │ │ ├── dedupEnvs.ts │ │ │ │ ├── devServer.graphql.ts │ │ │ │ ├── devServer.service.ts │ │ │ │ ├── devServer.ts │ │ │ │ ├── devServerContext.ts │ │ │ │ ├── exceptions/ │ │ │ │ │ ├── bindError.ts │ │ │ │ │ └── index.ts │ │ │ │ ├── getEntry.ts │ │ │ │ └── index.ts │ │ │ ├── component/ │ │ │ │ ├── component.aspect.ts │ │ │ │ ├── component.graphql.ts │ │ │ │ ├── component.main.runtime.ts │ │ │ │ ├── component.ts │ │ │ │ ├── componentFactory.ts │ │ │ │ ├── componentMap.ts │ │ │ │ ├── exceptions/ │ │ │ │ │ ├── hostNotFoundError.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── invalidNameError.ts │ │ │ │ │ └── invalidVersionError.ts │ │ │ │ ├── extensionData.ts │ │ │ │ ├── index.ts │ │ │ │ ├── type/ │ │ │ │ │ └── custom.d.ts │ │ │ │ ├── uiRuntime/ │ │ │ │ │ ├── component.module.scss │ │ │ │ │ ├── component.tsx │ │ │ │ │ ├── component.ui.runtime.tsx │ │ │ │ │ ├── componentContext.tsx │ │ │ │ │ ├── componentError.tsx │ │ │ │ │ ├── componentMeta/ │ │ │ │ │ │ ├── componentMeta.module.scss │ │ │ │ │ │ ├── componentMeta.tsx │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── componentModel.ts │ │ │ │ │ ├── hooks/ │ │ │ │ │ │ └── useComponentQuery.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ └── utils/ │ │ │ │ │ └── getIdFromLocation.ts │ │ │ │ └── version/ │ │ │ │ ├── index.ts │ │ │ │ ├── version.ts │ │ │ │ └── versionParser.ts │ │ │ ├── docs/ │ │ │ │ ├── doc/ │ │ │ │ │ ├── doc.ts │ │ │ │ │ ├── docProp.ts │ │ │ │ │ ├── docPropList.ts │ │ │ │ │ └── index.ts │ │ │ │ ├── docs.aspect.ts │ │ │ │ ├── docs.graphql.ts │ │ │ │ ├── docs.main.runtime.ts │ │ │ │ ├── docs.previewDefinition.ts │ │ │ │ ├── docs.task.ts │ │ │ │ ├── exceptions/ │ │ │ │ │ ├── fileExtensionNotSupportedError.ts │ │ │ │ │ └── index.ts │ │ │ │ ├── index.ts │ │ │ │ ├── previewRuntime/ │ │ │ │ │ ├── docs.preview.runtime.tsx │ │ │ │ │ └── index.ts │ │ │ │ ├── type/ │ │ │ │ │ ├── custom.d.ts │ │ │ │ │ ├── docOutline.ts │ │ │ │ │ ├── docReader.ts │ │ │ │ │ ├── docSnippet.ts │ │ │ │ │ └── index.ts │ │ │ │ └── uiRuntime/ │ │ │ │ ├── docs.ui.runtime.tsx │ │ │ │ ├── index.tsx │ │ │ │ ├── overview.module.scss │ │ │ │ └── overview.tsx │ │ │ ├── envs/ │ │ │ │ ├── envDefinition.ts │ │ │ │ ├── envService.ts │ │ │ │ ├── environment.ts │ │ │ │ ├── envs.aspect.ts │ │ │ │ ├── envs.main.runtime.ts │ │ │ │ ├── exceptions/ │ │ │ │ │ └── envNotFoundError.ts │ │ │ │ ├── executionContext.ts │ │ │ │ ├── index.ts │ │ │ │ ├── runtime/ │ │ │ │ │ ├── envRuntime.ts │ │ │ │ │ ├── envsExecutionResult.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ └── runtime.ts │ │ │ │ └── types.ts │ │ │ ├── index.ts │ │ │ ├── jest/ │ │ │ │ ├── exceptions/ │ │ │ │ │ ├── index.ts │ │ │ │ │ └── jestError.ts │ │ │ │ ├── index.ts │ │ │ │ ├── jest.aspect.ts │ │ │ │ ├── jest.main.runtime.ts │ │ │ │ └── jest.tester.ts │ │ │ ├── less/ │ │ │ │ ├── compilerOptions.ts │ │ │ │ ├── index.ts │ │ │ │ ├── less.aspect.ts │ │ │ │ ├── less.compiler.ts │ │ │ │ └── less.main.runtime.ts │ │ │ ├── mdx/ │ │ │ │ ├── index.ts │ │ │ │ ├── loader/ │ │ │ │ │ ├── compileOutput.ts │ │ │ │ │ ├── importSpecifier.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── mdxCompiler.ts │ │ │ │ │ ├── mdxLoader.ts │ │ │ │ │ └── remarkPlugins/ │ │ │ │ │ ├── extractComponentDemos.ts │ │ │ │ │ ├── extractHeadings.ts │ │ │ │ │ ├── extractImports.ts │ │ │ │ │ ├── extractMetadata.ts │ │ │ │ │ └── index.ts │ │ │ │ ├── mdx.aspect.ts │ │ │ │ ├── mdx.docReader.ts │ │ │ │ └── mdx.main.runtime.ts │ │ │ ├── multi-compiler/ │ │ │ │ ├── index.ts │ │ │ │ ├── multiCompiler.aspect.ts │ │ │ │ ├── multiCompiler.compiler.ts │ │ │ │ └── multiCompiler.main.runtime.ts │ │ │ ├── pubsub/ │ │ │ │ ├── events/ │ │ │ │ │ ├── activeTabEvent.ts │ │ │ │ │ ├── arcoBaseEvent.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── locationHashEvent.ts │ │ │ │ │ └── sizeEvent.ts │ │ │ │ ├── exceptions/ │ │ │ │ │ ├── index.ts │ │ │ │ │ └── noParentError.ts │ │ │ │ ├── index.ts │ │ │ │ ├── previewRuntime/ │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── pubsub.preview.runtime.ts │ │ │ │ │ └── pubsub.ts │ │ │ │ ├── pubsub.aspect.ts │ │ │ │ ├── pubsub.main.runtime.ts │ │ │ │ ├── pubsub.ui.runtime.ts │ │ │ │ └── pubsubContext.tsx │ │ │ ├── react-router/ │ │ │ │ ├── index.ts │ │ │ │ ├── reactRouter.aspect.ts │ │ │ │ └── uiRuntime/ │ │ │ │ ├── hooks/ │ │ │ │ │ └── useQuery.ts │ │ │ │ ├── index.ts │ │ │ │ ├── reactRouter.ui.runtime.tsx │ │ │ │ └── slotRouter.tsx │ │ │ ├── sass/ │ │ │ │ ├── compilerOptions.ts │ │ │ │ ├── index.ts │ │ │ │ ├── sass.aspect.ts │ │ │ │ ├── sass.compiler.ts │ │ │ │ └── sass.main.runtime.ts │ │ │ ├── typescript/ │ │ │ │ ├── compilerOptions.ts │ │ │ │ ├── index.ts │ │ │ │ ├── typescript.aspect.ts │ │ │ │ ├── typescript.compiler.ts │ │ │ │ ├── typescript.main.runtime.ts │ │ │ │ ├── typescriptConfigMutator.ts │ │ │ │ └── utils/ │ │ │ │ └── flatTSConfig.ts │ │ │ ├── webpack/ │ │ │ │ ├── config/ │ │ │ │ │ ├── html.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── webpack.config.ts │ │ │ │ │ ├── webpack.dev.config.ts │ │ │ │ │ ├── webpackFallbacks.ts │ │ │ │ │ ├── webpackFallbacksAliases.ts │ │ │ │ │ └── webpackFallbacksProvidePluginConfig.ts │ │ │ │ ├── events/ │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── webpackCompilationDoneEvent.ts │ │ │ │ │ └── webpackCompilationStartedEvent.ts │ │ │ │ ├── generateStyleLoader.ts │ │ │ │ ├── index.ts │ │ │ │ ├── webpack.aspect.ts │ │ │ │ ├── webpack.bundler.ts │ │ │ │ ├── webpack.devServer.ts │ │ │ │ ├── webpack.main.runtime.ts │ │ │ │ └── webpackConfigMutator.ts │ │ │ └── workspace/ │ │ │ ├── events/ │ │ │ │ ├── index.ts │ │ │ │ ├── onComponentAddEvent.ts │ │ │ │ ├── onComponentChangeEvent.ts │ │ │ │ └── onComponentRemovedEvent.ts │ │ │ ├── exceptions/ │ │ │ │ ├── index.ts │ │ │ │ ├── noIdMatchPatternError.ts │ │ │ │ └── workspaceNotFoundError.ts │ │ │ ├── index.ts │ │ │ ├── type/ │ │ │ │ ├── custom.d.ts │ │ │ │ ├── index.ts │ │ │ │ ├── onComponentEvents.ts │ │ │ │ └── workspaceConfig.ts │ │ │ ├── uiRuntime/ │ │ │ │ ├── graphqlProvider.tsx │ │ │ │ ├── hooks/ │ │ │ │ │ └── useWorkspace.ts │ │ │ │ ├── index.ts │ │ │ │ ├── style/ │ │ │ │ │ └── workspace.module.scss │ │ │ │ ├── workspace.tsx │ │ │ │ ├── workspace.ui.runtime.tsx │ │ │ │ └── workspaceModel.ts │ │ │ ├── watch/ │ │ │ │ ├── watchQueue.ts │ │ │ │ └── watcher.ts │ │ │ ├── workspace.aspect.ts │ │ │ ├── workspace.graphql.ts │ │ │ ├── workspace.main.runtime.ts │ │ │ ├── workspace.ts │ │ │ └── workspace.uiRoot.ts │ │ └── tsconfig.json │ ├── core/ │ │ ├── package.json │ │ ├── src/ │ │ │ ├── aspect-loader/ │ │ │ │ ├── aspectDefinition.ts │ │ │ │ ├── aspectLoader.aspect.ts │ │ │ │ ├── aspectLoader.main.runtime.ts │ │ │ │ ├── coreAspect.ts │ │ │ │ ├── index.ts │ │ │ │ └── pluginDefinition.ts │ │ │ ├── cli/ │ │ │ │ ├── cli.aspect.ts │ │ │ │ ├── cli.main.runtime.ts │ │ │ │ ├── cliParser.ts │ │ │ │ ├── commandRunner.ts │ │ │ │ ├── commands/ │ │ │ │ │ └── help.ts │ │ │ │ ├── exceptions/ │ │ │ │ │ ├── alreadyExistsError.ts │ │ │ │ │ ├── commandNotFoundError.ts │ │ │ │ │ └── index.ts │ │ │ │ ├── getCommandId.ts │ │ │ │ ├── help.ts │ │ │ │ ├── index.ts │ │ │ │ ├── legacyCommandAdapter.ts │ │ │ │ ├── utils.ts │ │ │ │ └── yargsAdapter.ts │ │ │ ├── express/ │ │ │ │ ├── express.aspect.ts │ │ │ │ ├── express.main.runtime.ts │ │ │ │ ├── index.ts │ │ │ │ ├── middlewares/ │ │ │ │ │ ├── error.ts │ │ │ │ │ └── index.ts │ │ │ │ └── types/ │ │ │ │ ├── index.ts │ │ │ │ ├── middlewareManifest.ts │ │ │ │ ├── next.ts │ │ │ │ ├── request.ts │ │ │ │ ├── response.ts │ │ │ │ └── route.ts │ │ │ ├── graphql/ │ │ │ │ ├── graphql.aspect.ts │ │ │ │ ├── graphql.main.runtime.ts │ │ │ │ ├── index.ts │ │ │ │ └── schema.ts │ │ │ ├── index.ts │ │ │ └── logger/ │ │ │ ├── index.ts │ │ │ ├── logger.aspect.ts │ │ │ ├── logger.main.runtime.ts │ │ │ ├── logger.ts │ │ │ └── longProcessLogger.ts │ │ └── tsconfig.json │ ├── generator/ │ │ ├── .gitignore │ │ ├── bin/ │ │ │ └── arco-generate │ │ ├── package.json │ │ ├── src/ │ │ │ ├── app.ts │ │ │ ├── forker.ts │ │ │ ├── generator.ts │ │ │ ├── index.ts │ │ │ ├── templates/ │ │ │ │ ├── react-component/ │ │ │ │ │ ├── __arco.tpl.desc.json │ │ │ │ │ ├── __docs__/ │ │ │ │ │ │ ├── basic.tpl.tsx │ │ │ │ │ │ └── index.tpl.ts │ │ │ │ │ ├── __test__/ │ │ │ │ │ │ └── index.test.tpl.tsx │ │ │ │ │ ├── component.tpl.tsx │ │ │ │ │ ├── index.tpl.tsx │ │ │ │ │ ├── interface.tpl.ts │ │ │ │ │ └── style/ │ │ │ │ │ ├── index.less │ │ │ │ │ └── index.ts │ │ │ │ ├── react-package/ │ │ │ │ │ ├── __arco.tpl.desc.json │ │ │ │ │ ├── gitignore.tpl.ts │ │ │ │ │ ├── package.json.tpl.ts │ │ │ │ │ ├── src/ │ │ │ │ │ │ └── index.ts │ │ │ │ │ └── tsconfig.json │ │ │ │ └── react-workspace/ │ │ │ │ ├── .husky/ │ │ │ │ │ └── pre-commit │ │ │ │ ├── .scripts/ │ │ │ │ │ └── workspaceHooks/ │ │ │ │ │ └── afterComponentCreated.js │ │ │ │ ├── __arco.tpl.desc.json │ │ │ │ ├── arco.env.config.js │ │ │ │ ├── arco.workspace.jsonc.tpl.ts │ │ │ │ ├── eslintignore.tpl.ts │ │ │ │ ├── eslintrc.tpl.ts │ │ │ │ ├── gitignore.tpl.ts │ │ │ │ ├── jest.config.js │ │ │ │ ├── package.json.tpl.ts │ │ │ │ ├── packages/ │ │ │ │ │ └── __arco.dir.desc.ts │ │ │ │ ├── prettierrc.tpl.ts │ │ │ │ ├── src/ │ │ │ │ │ ├── __arco.dir.desc.ts │ │ │ │ │ └── index.ts │ │ │ │ ├── stylelintignore.tpl.ts │ │ │ │ ├── stylelintrc.tpl.ts │ │ │ │ └── tsconfig.json.tpl.ts │ │ │ ├── types.ts │ │ │ └── utils/ │ │ │ ├── execQuick.ts │ │ │ ├── installDependencies.ts │ │ │ ├── isGitStatusClean.ts │ │ │ ├── isInGitRepository.ts │ │ │ ├── toFsCompatible.ts │ │ │ └── wgetAsync.ts │ │ └── tsconfig.json │ ├── legacy/ │ │ ├── package.json │ │ ├── src/ │ │ │ ├── analytics/ │ │ │ │ ├── analytics.ts │ │ │ │ └── analyticsSender.ts │ │ │ ├── bootstrap.ts │ │ │ ├── cli/ │ │ │ │ ├── command.ts │ │ │ │ ├── commandGroups.ts │ │ │ │ ├── commandRegistry.ts │ │ │ │ ├── commands/ │ │ │ │ │ ├── host/ │ │ │ │ │ │ ├── host.ts │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── login/ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── login.ts │ │ │ │ │ │ └── loginSuccessPage.ts │ │ │ │ │ └── logout/ │ │ │ │ │ ├── index.ts │ │ │ │ │ └── logout.ts │ │ │ │ ├── defaultErrorHandler.ts │ │ │ │ ├── globalFlags.ts │ │ │ │ ├── handleErrors.ts │ │ │ │ ├── legacyCommand.ts │ │ │ │ ├── loader/ │ │ │ │ │ ├── index.ts │ │ │ │ │ └── loader.ts │ │ │ │ ├── registerCommands.ts │ │ │ │ ├── request.ts │ │ │ │ ├── uploadFile.ts │ │ │ │ └── user/ │ │ │ │ ├── checkUserLogin.ts │ │ │ │ ├── getUserInfoFromAPI.ts │ │ │ │ └── index.ts │ │ │ ├── constants.ts │ │ │ ├── error/ │ │ │ │ ├── abstractError.ts │ │ │ │ ├── arcoError.ts │ │ │ │ ├── cloneErrorObject.ts │ │ │ │ ├── generalError.ts │ │ │ │ └── hashErrorObject.ts │ │ │ ├── globalConfig/ │ │ │ │ ├── config.ts │ │ │ │ └── index.ts │ │ │ ├── logger/ │ │ │ │ ├── getPinoLogger.ts │ │ │ │ ├── index.ts │ │ │ │ ├── interface.ts │ │ │ │ ├── logger.ts │ │ │ │ ├── printWarning.ts │ │ │ │ └── profiler.ts │ │ │ ├── prompts/ │ │ │ │ ├── exceptions/ │ │ │ │ │ ├── index.ts │ │ │ │ │ └── promptCanceled.ts │ │ │ │ ├── index.ts │ │ │ │ ├── prompt.ts │ │ │ │ └── schemas/ │ │ │ │ ├── analyticsReporting.ts │ │ │ │ └── errorReporting.ts │ │ │ ├── types.ts │ │ │ ├── utils/ │ │ │ │ ├── buildCommandMessage.ts │ │ │ │ ├── cliVersion.ts │ │ │ │ ├── concurrency.ts │ │ │ │ ├── encryption/ │ │ │ │ │ └── sha1.ts │ │ │ │ ├── eol.ts │ │ │ │ ├── fs/ │ │ │ │ │ ├── isDirEmpty.ts │ │ │ │ │ ├── readDirIgnoreDsStore.ts │ │ │ │ │ ├── removeEmptyDir.ts │ │ │ │ │ ├── removeFilesAndEmptyDirsRecursively.ts │ │ │ │ │ └── zipFiles.ts │ │ │ │ ├── ignore.ts │ │ │ │ ├── index.ts │ │ │ │ ├── map/ │ │ │ │ │ └── toObject.ts │ │ │ │ ├── mapToObject.ts │ │ │ │ ├── network/ │ │ │ │ │ └── port.ts │ │ │ │ ├── number/ │ │ │ │ │ └── isNumeric.ts │ │ │ │ ├── packCommand.ts │ │ │ │ ├── path.ts │ │ │ │ ├── regexp/ │ │ │ │ │ └── style.ts │ │ │ │ ├── string/ │ │ │ │ │ ├── toBase64.ts │ │ │ │ │ └── toFsCompatible.ts │ │ │ │ ├── taskManager.ts │ │ │ │ └── timer/ │ │ │ │ ├── exceptions/ │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── timerAlreadyRunningError.ts │ │ │ │ │ └── timerNotStartedError.ts │ │ │ │ ├── index.ts │ │ │ │ ├── response.ts │ │ │ │ └── timer.ts │ │ │ └── workspace/ │ │ │ ├── component/ │ │ │ │ ├── dependencies/ │ │ │ │ │ ├── detectives/ │ │ │ │ │ │ ├── detectiveEs6.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ └── parserHelper.ts │ │ │ │ │ └── types/ │ │ │ │ │ └── dependencyTreeType.ts │ │ │ │ ├── exceptions/ │ │ │ │ │ ├── componentNotFoundInPathError.ts │ │ │ │ │ ├── fileSourceNotFoundError.ts │ │ │ │ │ └── index.ts │ │ │ │ └── sources/ │ │ │ │ ├── abstractVinyl.ts │ │ │ │ ├── dataToPersist.ts │ │ │ │ ├── dist.ts │ │ │ │ ├── index.ts │ │ │ │ ├── removePath.ts │ │ │ │ ├── sourceFile.ts │ │ │ │ └── vinylTypes.ts │ │ │ ├── componentIdTo.ts │ │ │ ├── componentInfo.ts │ │ │ ├── componentOps/ │ │ │ │ └── addComponents/ │ │ │ │ ├── addComponents.ts │ │ │ │ ├── exceptions/ │ │ │ │ │ ├── ignoredDirectoryError.ts │ │ │ │ │ └── index.ts │ │ │ │ └── index.ts │ │ │ ├── componentResult.ts │ │ │ └── workspaceLocator.ts │ │ └── tsconfig.json │ ├── migrator/ │ │ ├── bin/ │ │ │ └── arco-migrate │ │ ├── package.json │ │ ├── src/ │ │ │ ├── adapters/ │ │ │ │ ├── componentAdapter.ts │ │ │ │ ├── packageAdapter.ts │ │ │ │ └── workspaceAdapter.ts │ │ │ ├── app.ts │ │ │ ├── constant.ts │ │ │ ├── index.ts │ │ │ ├── migrator.ts │ │ │ ├── types.ts │ │ │ └── utils.ts │ │ └── tsconfig.json │ ├── react/ │ │ ├── package.json │ │ ├── src/ │ │ │ ├── index.ts │ │ │ ├── jest/ │ │ │ │ ├── index.ts │ │ │ │ ├── jest.base.config.js │ │ │ │ ├── jest.cjs.config.js │ │ │ │ ├── jest.esm.config.js │ │ │ │ ├── setupTests.js │ │ │ │ └── transformers/ │ │ │ │ ├── base-transformer-plugins.js │ │ │ │ ├── base-transformer-presets.js │ │ │ │ ├── base-transformer-process.js │ │ │ │ ├── cjs-transformer.js │ │ │ │ ├── esm-transformer.js │ │ │ │ ├── file-transformer.js │ │ │ │ └── style-transformer.js │ │ │ ├── react.aspect.ts │ │ │ ├── react.env.ts │ │ │ ├── react.main.runtime.ts │ │ │ ├── tsdoc/ │ │ │ │ ├── index.ts │ │ │ │ └── parser.ts │ │ │ ├── types/ │ │ │ │ └── reactConfig.ts │ │ │ ├── typescript/ │ │ │ │ ├── asset.d.ts │ │ │ │ ├── style.d.ts │ │ │ │ └── tsconfig.json │ │ │ └── webpack/ │ │ │ ├── overlay/ │ │ │ │ ├── formatWebpackMessages.js │ │ │ │ ├── launchEditorEndpoint.js │ │ │ │ ├── refreshOverlayInterop.js │ │ │ │ └── webpackHotDevClient.js │ │ │ ├── webpack.config.base.ts │ │ │ ├── webpack.config.component.dev.ts │ │ │ └── webpack.config.component.prod.ts │ │ └── tsconfig.json │ ├── service/ │ │ ├── package.json │ │ ├── src/ │ │ │ ├── builder/ │ │ │ │ ├── build.cmd.ts │ │ │ │ ├── buildPipe.ts │ │ │ │ ├── buildPipelineOrder.ts │ │ │ │ ├── buildTask.ts │ │ │ │ ├── builder.aspect.ts │ │ │ │ ├── builder.main.runtime.ts │ │ │ │ ├── builder.service.tsx │ │ │ │ ├── exceptions/ │ │ │ │ │ ├── artifactDefinitionError.ts │ │ │ │ │ ├── artifactStorageError.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ └── invalidTaskError.ts │ │ │ │ ├── index.ts │ │ │ │ ├── mergeComponentResults.ts │ │ │ │ ├── taskResultsList.ts │ │ │ │ └── tasksQueue.ts │ │ │ ├── compiler/ │ │ │ │ ├── compiler.aspect.ts │ │ │ │ ├── compiler.main.runtime.ts │ │ │ │ ├── compiler.task.ts │ │ │ │ ├── index.ts │ │ │ │ ├── types.ts │ │ │ │ └── utils/ │ │ │ │ ├── compileStyle.ts │ │ │ │ └── sortPackageBuildOrders.ts │ │ │ ├── fork/ │ │ │ │ ├── fork.aspect.ts │ │ │ │ ├── fork.cmd.ts │ │ │ │ ├── fork.main.runtime.ts │ │ │ │ └── index.ts │ │ │ ├── generator/ │ │ │ │ ├── create.cmd.ts │ │ │ │ ├── generator.aspect.ts │ │ │ │ ├── generator.main.runtime.ts │ │ │ │ └── index.ts │ │ │ ├── index.ts │ │ │ ├── preview/ │ │ │ │ ├── bundlingStrategy.ts │ │ │ │ ├── cli/ │ │ │ │ │ └── previewServerStatus/ │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── previewServerHeader.tsx │ │ │ │ │ ├── previewServerRow.tsx │ │ │ │ │ ├── previewServerStatus.tsx │ │ │ │ │ └── webpackError.tsx │ │ │ │ ├── exceptions/ │ │ │ │ │ ├── bundlingStrategyNotFoundError.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── previewNotFoundError.ts │ │ │ │ │ └── previewOutputFileNotFoundError.ts │ │ │ │ ├── executionRef.ts │ │ │ │ ├── generateLink.ts │ │ │ │ ├── index.ts │ │ │ │ ├── preview.aspect.ts │ │ │ │ ├── preview.main.runtime.ts │ │ │ │ ├── preview.startPlugin.tsx │ │ │ │ ├── preview.task.ts │ │ │ │ ├── previewRuntime/ │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── preview.preview.runtime.tsx │ │ │ │ │ └── previewModules.tsx │ │ │ │ ├── strategies/ │ │ │ │ │ ├── componentStrategy.ts │ │ │ │ │ ├── generatePreviewBundleEntry.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ └── strategiesNames.ts │ │ │ │ ├── types/ │ │ │ │ │ ├── custom.d.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── previewDefinition.ts │ │ │ │ │ ├── previewModule.ts │ │ │ │ │ ├── previewType.ts │ │ │ │ │ └── renderingContext.ts │ │ │ │ ├── uiRuntime/ │ │ │ │ │ ├── componentPreview.tsx │ │ │ │ │ ├── index.ts │ │ │ │ │ └── urls.ts │ │ │ │ └── webpackEventsListener.ts │ │ │ ├── syncer/ │ │ │ │ ├── index.ts │ │ │ │ ├── sync.cmd.ts │ │ │ │ ├── syncer.aspect.ts │ │ │ │ ├── syncer.main.runtime.ts │ │ │ │ └── type/ │ │ │ │ └── syncParams.ts │ │ │ ├── tester/ │ │ │ │ ├── index.ts │ │ │ │ ├── test.cmd.tsx │ │ │ │ ├── tester.aspect.ts │ │ │ │ ├── tester.main.runtime.ts │ │ │ │ ├── tester.service.tsx │ │ │ │ └── tester.ts │ │ │ └── ui/ │ │ │ ├── cli/ │ │ │ │ ├── time.tsx │ │ │ │ ├── uiServerConsole.tsx │ │ │ │ └── uiServerLoader.tsx │ │ │ ├── createRoot.ts │ │ │ ├── exceptions/ │ │ │ │ ├── index.ts │ │ │ │ └── unknownUIError.ts │ │ │ ├── index.ts │ │ │ ├── start.cmd.tsx │ │ │ ├── startPlugin.ts │ │ │ ├── ui.aspect.ts │ │ │ ├── ui.main.runtime.ts │ │ │ ├── uiRoot.ts │ │ │ ├── uiRuntime/ │ │ │ │ ├── index.ts │ │ │ │ ├── ui.ui.runtime.tsx │ │ │ │ └── uiRoot.ui.ts │ │ │ ├── uiServer.ts │ │ │ └── webpack/ │ │ │ ├── html.ts │ │ │ └── webpack.dev.config.ts │ │ └── tsconfig.json │ ├── stone/ │ │ ├── package.json │ │ ├── src/ │ │ │ ├── aspect/ │ │ │ │ ├── aspect.ts │ │ │ │ ├── aspectManifest.ts │ │ │ │ └── index.ts │ │ │ ├── config/ │ │ │ │ ├── config.ts │ │ │ │ └── index.ts │ │ │ ├── exception/ │ │ │ │ ├── extensionInitError.ts │ │ │ │ ├── extensionLoadError.ts │ │ │ │ ├── extensionPotentialCircularError.ts │ │ │ │ ├── index.ts │ │ │ │ ├── readConfigError.ts │ │ │ │ ├── runtimeModuleError.ts │ │ │ │ └── runtimeNotDefinedError.ts │ │ │ ├── extension/ │ │ │ │ ├── extension.ts │ │ │ │ ├── extensionManifest.ts │ │ │ │ └── index.ts │ │ │ ├── extensionGraph/ │ │ │ │ ├── extensionGraph.ts │ │ │ │ ├── fromExtension.ts │ │ │ │ └── index.ts │ │ │ ├── index.ts │ │ │ ├── runtimes/ │ │ │ │ ├── index.ts │ │ │ │ ├── runtimeDefinition.ts │ │ │ │ ├── runtimeManifest.ts │ │ │ │ └── runtimes.ts │ │ │ ├── slot/ │ │ │ │ ├── index.ts │ │ │ │ ├── registry.ts │ │ │ │ └── slot.ts │ │ │ ├── stone.ts │ │ │ ├── stoneConfig/ │ │ │ │ ├── configReader.ts │ │ │ │ ├── index.ts │ │ │ │ └── stoneConfig.ts │ │ │ ├── types.ts │ │ │ └── utils/ │ │ │ └── asyncForEach.ts │ │ └── tsconfig.json │ └── ui-foundation-react/ │ ├── package.json │ ├── src/ │ │ ├── backTop/ │ │ │ └── index.ts │ │ ├── baseUI/ │ │ │ ├── grid/ │ │ │ │ ├── grid.module.scss │ │ │ │ ├── grid.tsx │ │ │ │ ├── gridTemplate/ │ │ │ │ │ ├── gridTemplate.module.scss │ │ │ │ │ └── index.ts │ │ │ │ └── index.tsx │ │ │ ├── highlighter/ │ │ │ │ ├── codeHighlighter.tsx │ │ │ │ ├── index.ts │ │ │ │ └── syntaxHighlighter.tsx │ │ │ ├── index.ts │ │ │ └── table/ │ │ │ ├── index.ts │ │ │ ├── table.module.scss │ │ │ ├── table.tsx │ │ │ ├── tableColumn.module.scss │ │ │ ├── tableColumn.tsx │ │ │ ├── tableHeadingColumn.module.scss │ │ │ ├── tableHeadingColumn.tsx │ │ │ ├── tableHeadingRow.module.scss │ │ │ ├── tableHeadingRow.tsx │ │ │ ├── tableRow.module.scss │ │ │ └── tableRow.tsx │ │ ├── constants.ts │ │ ├── globalLoader/ │ │ │ ├── index.ts │ │ │ ├── loaderContext.tsx │ │ │ ├── useLoader.ts │ │ │ └── useLoaderApi.ts │ │ ├── hooks/ │ │ │ └── useDataQuery.ts │ │ ├── index.ts │ │ ├── markdown/ │ │ │ ├── components/ │ │ │ │ ├── anchor/ │ │ │ │ │ ├── anchor.module.scss │ │ │ │ │ ├── anchor.tsx │ │ │ │ │ └── index.ts │ │ │ │ ├── demoView/ │ │ │ │ │ ├── demoView.module.scss │ │ │ │ │ ├── demoView.tsx │ │ │ │ │ └── index.ts │ │ │ │ ├── docAnchor/ │ │ │ │ │ ├── docAnchor.module.scss │ │ │ │ │ ├── docAnchor.tsx │ │ │ │ │ └── index.ts │ │ │ │ ├── heading/ │ │ │ │ │ ├── heading.tsx │ │ │ │ │ ├── index.ts │ │ │ │ │ └── utils.ts │ │ │ │ ├── index.tsx │ │ │ │ ├── snippet/ │ │ │ │ │ ├── codeSnippet.module.scss │ │ │ │ │ ├── codeSnippet.tsx │ │ │ │ │ └── index.tsx │ │ │ │ └── tabs/ │ │ │ │ ├── index.tsx │ │ │ │ ├── tabs.module.scss │ │ │ │ └── tabs.tsx │ │ │ ├── live/ │ │ │ │ ├── index.ts │ │ │ │ ├── markdownLive.module.scss │ │ │ │ └── markdownLive.tsx │ │ │ ├── mdxLayout/ │ │ │ │ ├── index.ts │ │ │ │ ├── mdxLayout.module.scss │ │ │ │ └── mdxLayout.tsx │ │ │ └── style/ │ │ │ ├── markdown-limit.css │ │ │ ├── markdown.css │ │ │ └── variable.scss │ │ ├── navbar/ │ │ │ ├── index.ts │ │ │ ├── navbar.module.scss │ │ │ └── navbar.tsx │ │ ├── pages/ │ │ │ ├── contactFooter.tsx │ │ │ ├── errorPage.tsx │ │ │ ├── index.ts │ │ │ ├── notFoundPage.tsx │ │ │ └── style/ │ │ │ ├── contactFooter.module.scss │ │ │ └── errorPage.module.scss │ │ ├── preview/ │ │ │ ├── SlotRegister.ts │ │ │ ├── app.module.scss │ │ │ ├── app.tsx │ │ │ ├── doc/ │ │ │ │ ├── content.module.scss │ │ │ │ ├── content.tsx │ │ │ │ ├── propertiesTable.module.scss │ │ │ │ └── propertiesTable.tsx │ │ │ ├── index.tsx │ │ │ ├── previewContext/ │ │ │ │ └── index.tsx │ │ │ └── theme.tsx │ │ ├── sideBar/ │ │ │ ├── componentMenu/ │ │ │ │ ├── componentMenu.module.scss │ │ │ │ ├── componentMenu.tsx │ │ │ │ └── index.ts │ │ │ ├── index.ts │ │ │ ├── sideBar.module.scss │ │ │ └── sideBar.tsx │ │ ├── spin/ │ │ │ └── index.ts │ │ ├── style/ │ │ │ ├── colors.scss │ │ │ ├── global.scss │ │ │ └── z-indexes.scss │ │ ├── tabs/ │ │ │ └── index.ts │ │ ├── type/ │ │ │ └── custom.d.ts │ │ ├── workspaceContext/ │ │ │ ├── index.ts │ │ │ ├── workspaceContext.module.scss │ │ │ └── workspaceContext.tsx │ │ └── workspaceOverview/ │ │ ├── index.ts │ │ ├── workspaceOverview.module.scss │ │ └── workspaceOverview.tsx │ └── tsconfig.json ├── pnpm-workspace.yaml ├── tsconfig.json └── ui/ ├── .gitignore ├── .scripts/ │ └── workspaceHooks/ │ └── afterComponentCreated.js ├── arco.env.config.js ├── arco.workspace.jsonc ├── jest.config.js ├── package.json ├── src/ │ ├── Overview/ │ │ ├── Overview.tsx │ │ ├── __docs__/ │ │ │ ├── basic.tsx │ │ │ └── index.mdx │ │ ├── __test__/ │ │ │ └── index.test.tsx │ │ ├── index.ts │ │ ├── interface.ts │ │ └── style/ │ │ └── index.module.less │ ├── index.ts │ ├── type/ │ │ └── custom.d.ts │ └── utils/ │ ├── constant.ts │ ├── dom.ts │ ├── findNode.ts │ └── useConnectIframe.ts └── tsconfig.json ================================================ FILE CONTENTS ================================================ ================================================ FILE: .eslintignore ================================================ **/node_modules/** **/dist/** *.json **/template/** packages/*/lib/* fixtures ================================================ FILE: .eslintrc.json ================================================ { "extends": ["plugin:@typescript-eslint/recommended", "airbnb", "plugin:prettier/recommended"], "env": { "browser": true, "commonjs": true, "es6": true, "node": true }, "parser": "@typescript-eslint/parser", "parserOptions": { "sourceType": "module", "ecmaFeatures": { "jsx": true, "experimentalObjectRestSpread": true } }, "globals": { "describe": "readonly", "it": "readonly", "expect": "readonly", "jest": "readonly", "$": "readonly", "afterEach": "readonly", "beforeEach": "readonly", "NodeJS": "writable" }, "rules": { "prettier/prettier": [ "error", { "trailingComma": "es5", "printWidth": 100 } ], "linebreak-style": ["error", "unix"], "max-classes-per-file": 0, "no-case-declarations": 0, "no-continue": 0, "no-empty": 0, "no-empty-function": 0, "no-param-reassign": 0, "no-prototype-builtins": 0, "no-shadow": 0, "no-restricted-syntax": 0, "no-underscore-dangle": 0, "no-useless-constructor": 0, "no-useless-escape": 0, "no-use-before-define": ["error", {"functions": false, "classes": false}], "no-unused-vars": 0, "no-unused-expressions": ["error", {"allowShortCircuit": true, "allowTernary": true}], "no-plusplus": 0, "no-nested-ternary": 0, "one-var": 0, "prefer-promise-reject-errors": 0, "prefer-regex-literals": 0, "prefer-destructuring": 0, "global-require": 0, "guard-for-in": 0, "func-names": 0, "strict": 0, "radix": 0, "class-methods-use-this": 0, "import/no-extraneous-dependencies": 1, "import/no-dynamic-require": 0, "import/no-mutable-exports": 0, "import/no-unresolved": 0, "import/extensions": 0, "import/prefer-default-export": 0, "react/jsx-filename-extension": [1, {"extensions": [".js", ".jsx", "ts", "tsx"]}], "react/sort-comp": 0, "react/prefer-stateless-function": 0, "react/no-array-index-key": 0, "react/no-children-prop": 0, "react/jsx-props-no-spreading": 0, "react/jsx-one-expression-per-line": 0, "react/no-multi-comp": 0, "react/destructuring-assignment": 0, "react/button-has-type": 0, "react/require-default-props": 0, "jsx-a11y/click-events-have-key-events": 0, "jsx-a11y/no-static-element-interactions": 0, "jsx-a11y/no-noninteractive-element-interactions": 0, "@typescript-eslint/ban-ts-comment": 0, "@typescript-eslint/explicit-function-return-type": 0, "@typescript-eslint/explicit-module-boundary-types": 0, "@typescript-eslint/no-empty-function": 0, "@typescript-eslint/no-unused-vars": [2, {"vars": "all", "args": "none"}] } } ================================================ FILE: .github/ISSUE_TEMPLATE/bug_report.md ================================================ --- name: Bug report about: Create a report to help us improve title: '' labels: '' assignees: '' --- **Describe the bug** A clear and concise description of what the bug is. **To Reproduce** Steps to reproduce the behavior: 1. Go to '...' 2. Click on '....' 3. Scroll down to '....' 4. See error **Expected behavior** A clear and concise description of what you expected to happen. **Screenshots** If applicable, add screenshots to help explain your problem. **Desktop (please complete the following information):** - OS: [e.g. MacOS] - node version [e.g. v16.13.1] **Additional context** Add any other context about the problem here. ================================================ FILE: .github/ISSUE_TEMPLATE/feature_request.md ================================================ --- name: Feature request about: Suggest an idea for this project title: '' labels: '' assignees: '' --- **Is your feature request related to a problem? Please describe.** A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] **Describe the solution you'd like** A clear and concise description of what you want to happen. **Describe alternatives you've considered** A clear and concise description of any alternative solutions or features you've considered. **Additional context** Add any other context or screenshots about the feature request here. ================================================ FILE: .github/PULL_REQUEST_TEMPLATE.md ================================================ ## Types of changes - [ ] New feature - [ ] Bug fix - [ ] Documentation change - [ ] Coding style change - [ ] Refactoring - [ ] Performance improvement - [ ] Test cases - [ ] Continuous integration - [ ] Typescript definition change - [ ] Breaking change ## Background and context ## Solution ## How is the change tested? ## Changelog | Changelog(CN) | Changelog(EN) | Related issues | | ------------- | ------------- | -------------- | ## Checklist: - [ ] Provide changelog for relevant changes (e.g. bug fixes and new features) if applicable. - [ ] Changes are submitted to the appropriate branch (e.g. features should be submitted to `feature` branch and others should be submitted to `master` branch) ## Other information ================================================ FILE: .gitignore ================================================ # Logs logs *.log npm-debug.log* yarn-debug.log* yarn-error.log* # Runtime data pids *.pid *.seed *.pid.lock .npmrc # Directory for instrumented libs generated by jscoverage/JSCover lib-cov # Coverage directory used by tools like istanbul coverage # nyc test coverage .nyc_output # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) .grunt # Bower dependency directory (https://bower.io/) bower_components # node-waf configuration .lock-wscript # Compiled binary addons (https://nodejs.org/api/addons.html) build/Release # Dependency directories node_modules/ jspm_packages/ # TypeScript v1 declaration files typings/ # Optional npm cache directory .npm # Optional eslint/stylelint cache .eslintcache .stylelintcache # Optional REPL history .node_repl_history # Output of 'npm pack' *.tgz # Yarn Integrity file .yarn-integrity # dotenv environment variables file .env # next.js build output .next **/__test__/fixtures/es **/__test__/fixtures/lib **/__test__/fixtures/dist .idea **/.DS_Store package-lock.json fixtures/**/dist packages/**/dist ================================================ FILE: .husky/pre-commit ================================================ #!/usr/bin/env sh . "$(dirname -- "$0")/_/husky.sh" npm run eslint npm run stylelint ================================================ FILE: .prettierrc.json ================================================ { "arrowParens": "always", "jsxBracketSameLine": false, "jsxSingleQuote": false, "printWidth": 100, "semi": true, "singleQuote": true, "tabWidth": 2, "trailingComma": "es5", "useTabs": false } ================================================ FILE: .scripts/build-esm.sh ================================================ root=`git rev-parse --show-toplevel` babel_config_path="${root}/babel.config.js" babel_options="" if [[ $1 == dev ]]; then babel_options="--watch --source-maps" fi # Compile .ts with babel and copy tsc un-support files babel src -d dist -x ".ts,.tsx,.js" --copy-files --verbose --config-file $babel_config_path $babel_options ================================================ FILE: .scripts/build-type.sh ================================================ # disable this command for now # tsc --emitDeclarationOnly ================================================ FILE: .scripts/build.sh ================================================ root=`git rev-parse --show-toplevel` babel_config_path="${root}/babel.config.js" flag_no_source_maps="no-source-maps" babel_options="--plugins=@babel/plugin-transform-modules-commonjs" if [[ $1 == dev ]]; then babel_options="$babel_options --watch" fi if [[ "$*" != *"$flag_no_source_maps"* ]]; then babel_options="$babel_options --source-maps" fi # Compile .ts with babel and copy tsc un-support files babel src -d dist -x ".ts,.tsx,.js" --copy-files --verbose --config-file $babel_config_path $babel_options ================================================ FILE: .scripts/flatPackages.js ================================================ /* eslint-disable */ const path = require('path'); const fs = require('fs-extra'); const gulp = require('gulp'); const gulpReplace = require('gulp-replace'); const getLocalPackages = require('./getLocalPackages'); const NEW_PACKAGES_DIR = path.resolve(__dirname, '../_packages'); const PATH_IGNORE_PATTERN = /ui-foundation|app|stone|legacy/; const DEFAULT_PACKAGE_JSON = { version: '0.1.0', scripts: { dev: 'sh ../../.scripts/build.sh dev', build: 'sh ../../.scripts/build.sh', 'build-type': 'sh ../../.scripts/build-type.sh', clean: 'rm -rf dist', 'clean-type': 'find dist -name *.d.ts |xargs rm -rf', prepublishOnly: 'npm run build', }, files: ['dist'], license: 'MIT', }; async function flatPackages() { const originPackages = await getLocalPackages(); const newPackages = {}; originPackages.forEach(({ name, location }) => { if (location.match(PATH_IGNORE_PATTERN)) { return; } const parentDir = path.dirname(location); const dirname = parentDir.split('/').pop(); newPackages[parentDir] ||= { dirname, name: `@arco-cli/${dirname}`, path: path.resolve(NEW_PACKAGES_DIR, dirname), packages: [], children: [], }; newPackages[parentDir].packages.push(name); newPackages[parentDir].children.push(location); }); Object.values(newPackages).forEach(({ path: newPackagePath, name, children }) => { const newPackageJson = { name, ...DEFAULT_PACKAGE_JSON, }; const childrenDeps = { peerDependencies: {}, dependencies: {}, devDependencies: {}, }; children.forEach((location) => { const packageJson = fs.readJsonSync(path.resolve(location, 'package.json')); Object.assign(childrenDeps.peerDependencies, packageJson.peerDependencies); Object.assign(childrenDeps.dependencies, packageJson.dependencies); Object.assign(childrenDeps.devDependencies, packageJson.devDependencies); }); Object.entries(childrenDeps).forEach(([depType, deps]) => { Object.entries(deps) .sort(([keyA], [keyB]) => { return keyB.startsWith('@') && !keyA.startsWith('@') ? 1 : keyA > keyB ? 1 : -1; }) .map(([key, value]) => { if (key.startsWith('@arco-cli/')) { const newPackagesInfo = Object.values(newPackages); for (const { name, packages } of newPackagesInfo) { if (packages.indexOf(key) > -1) { return [name, value]; } } } return [key, value]; }) .forEach(([key, value]) => { if (key !== newPackageJson.name) { newPackageJson[depType] ||= {}; newPackageJson[depType][key] = value; } }); }); // write new packageJson fs.ensureDirSync(`${newPackagePath}/src`); fs.writeJsonSync(path.join(newPackagePath, 'package.json'), newPackageJson, { spaces: 2 }); // copy children's source file for (const childLocation of children) { fs.copySync( `${childLocation}/src`, `${newPackagePath}/src/${childLocation.split('/').pop()}` ); } }); return Promise.all( Object.values(newPackages).map(({ name, path: newPackagePath, packages }) => { return new Promise((resolve, reject) => { gulp .src(`${newPackagePath}/src/**/*.*`) .pipe( gulpReplace(/'@arco-cli\/[^']+'/g, function doReplace(match) { if (PATH_IGNORE_PATTERN.test(match)) { return match; } const fileRelativePath = this.file.relative; const splitPaths = match.replace(/'/g, '').split('/'); const originPackage = splitPaths.slice(0, 2).join('/'); const originImportFrom = splitPaths.slice(2).join('/'); let newPackage = Object.values(newPackages).find(({ packages }) => { return packages.indexOf(originPackage) > -1; })?.name; if (!newPackage) { return 'NO_MATCH'; } const newImportFrom = `${ newPackage === name ? `@self` : `${newPackage}/dist` }/${originPackage.split('/').pop()}`; if (originImportFrom) { return `'${newImportFrom}/${originImportFrom.replace('dist/', '')}'`; } else { return `'${newImportFrom}'`; } }) ) .pipe(gulp.dest(`${newPackagePath}/src`)) .on('end', () => { console.log(`_________ ${newPackagePath}`); resolve(); }) .on('error', (error) => { reject(error); console.error(error); }); }); }) ); } flatPackages() .then() .catch((err) => { console.log(err); }); ================================================ FILE: .scripts/getLocalPackages.js ================================================ /* eslint-disable */ const path = require('path'); const { exec } = require('child_process'); const DIR_PACKAGES = path.resolve(__dirname, '../packages'); /** * @returns {Promise>} */ module.exports = function () { return new Promise((resolve, reject) => { exec('lerna list --json', (error, stdout, stderr) => { const errMsg = 'Failed to collect packages info via [lerna list]'; if (error) { reject({ error, msg: errMsg, }); } try { const infoList = JSON.parse(stdout).filter(({ location }) => location.startsWith(DIR_PACKAGES) ); resolve(infoList); } catch (error) { reject({ error, msg: stderr || errMsg, }); } }); }); }; ================================================ FILE: .scripts/linkLocalPackages.js ================================================ /* eslint-disable */ const path = require('path'); const fs = require('fs-extra'); const chalk = require('chalk'); const getLocalPackages = require('./getLocalPackages'); const DIR_NODE_MODULES = path.resolve(__dirname, '../node_modules'); async function linkLocalPackages() { try { const packages = await getLocalPackages(); const packagesDone = []; packages.forEach(({ name, location }) => { const from = location; const to = `${DIR_NODE_MODULES}/${name}`; const nodeModulesEntryDir = path.resolve( DIR_NODE_MODULES, name.split('/').slice(0, -1).join('') ); try { if (!fs.existsSync(to)) { fs.ensureDirSync(nodeModulesEntryDir); fs.symlinkSync(from, to, 'dir'); } packagesDone.push(name); } catch (error) { console.error(chalk.red(`Failed to link ${to} from ${from}\n`), error); } }); console.log( chalk.green( 'Link workspace packages successfully. Following packages are linked to node_modules:\n' ), packagesDone ); process.exit(0); } catch ({ error, msg }) { console.error(chalk.red(`${msg}\n`), error); process.exit(1); } } linkLocalPackages().finally(() => {}); ================================================ FILE: .scripts/linkTsPaths.js ================================================ /* eslint-disable */ const path = require('path'); const fs = require('fs-extra'); const chalk = require('chalk'); const getLocalPackages = require('./getLocalPackages'); const ROOT_PATH = path.resolve(__dirname, '..'); const FILE_TSCONFIG = path.resolve(ROOT_PATH, 'tsconfig.paths.json'); async function linkLocalPackages() { try { const packages = await getLocalPackages(); const tsconfig = fs.readJsonSync(FILE_TSCONFIG); const paths = {}; packages.forEach(({ name, location }) => { location = `./node_modules/${name}`; paths[name] = [`${location}/src/index.ts`]; paths[`${name}/dist/*`] = [`${location}/src/*`]; }); tsconfig.compilerOptions.paths = paths; fs.writeFileSync(FILE_TSCONFIG, JSON.stringify(tsconfig, null, 2)); console.error(chalk.green('Generate tsconfig paths successfully.')); } catch ({ error, msg }) { console.error(chalk.red(`${msg}\n`), error); process.exit(1); } } linkLocalPackages().finally(() => {}); ================================================ FILE: .stylelintignore ================================================ **/node_modules/** **/dist/** ================================================ FILE: .stylelintrc ================================================ { "extends": "stylelint-config-standard-scss", "rules": { "block-no-empty": null, "block-opening-brace-space-before": "always", "declaration-block-trailing-semicolon": "always", "font-family-no-missing-generic-family-keyword": null, "import-notation": null, "no-invalid-double-slash-comments": null, "no-duplicate-selectors": null, "selector-class-pattern": null, "selector-pseudo-class-no-unknown": [ true, { "ignorePseudoClasses": ["global"] } ], "string-quotes": "single", "scss/no-global-function-names": null } } ================================================ FILE: CODE_OF_CONDUCT.md ================================================ # Contributor Covenant Code of Conduct ## Our Pledge In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. ## Our Standards Examples of behavior that contributes to creating a positive environment include: - Using welcoming and inclusive language - Being respectful of differing viewpoints and experiences - Gracefully accepting constructive criticism - Focusing on what is best for the community - Showing empathy towards other community members Examples of unacceptable behavior by participants include: - The use of sexualized language or imagery and unwelcome sexual attention or advances - Trolling, insulting/derogatory comments, and personal or political attacks - Public or private harassment - Publishing others' private information, such as a physical or electronic address, without explicit permission - Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. ## Scope This Code of Conduct applies within all project spaces, and it also applies when an individual is representing the project or its community in public spaces. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at pengjiyuan@bytendance.com. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org), version 1.4, available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html For answers to common questions about this code of conduct, see https://www.contributor-covenant.org/faq ================================================ FILE: CONTRIBUTING.md ================================================ > English | [简体中文](./CONTRIBUTING.zh-CN.md) # Contributing Thank you for taking your time to contribute and make this project better! Here are some guidelines to help you get started. Please make sure to take a moment and read through them before submitting your contributions. ## Code of Conduct This project is governed by the [Contributor Covenant Code of Conduct](./CODE_OF_CONDUCT.md). By participating, you are expected to adhere to it. ## Open Development All work happens directly on GitHub. Both core team members and external contributors send pull requests which go through the same review process. ## Semantic Versioning This project follows semantic versioning. We release patch versions for bug fixes or other changes that do not change the behavior of the API, minor versions for new features that are backward-compatible, and major versions for any breaking changes. Every significant change is documented in the changelog file. ## Reporting Issues We use [Github issues](https://github.com/arco-design/arco-cli/issues) for bug reports and feature requests. Before reporting an issue, please make sure you have searched for similar [issues](https://github.com/arco-design/arco-cli/issues) as they may have been already answered or being fixed. A new issue should be submitted via [issue helper](https://arco-design/issue-helper?repo=arco-cli). For bug reporting, please include the minimum code that can be used to reproduce the problem. For feature request, please specify what changes you want and what behavior you expect. ## Sending a pull request 1. Fork [the repository](https://github.com/arco-design/arco-cli) and create your branch from `master`. For new feature, please submit your changes directly to the `feature` branch. Other changes should go against `master` branch. 1. Run `npm run init` in the repository root. 1. Make changes to the codebase. Please add tests if applicable. 1. Commit your changes, adhering to the [Commit Guidelines](#commit-guidelines) 1. Open a new pull request, [referencing corresponding issues](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword) if available. ## Commit Guidelines Commit messages are required to follow the [conventional-changelog standard](https://www.conventionalcommits.org/en/v1.0.0/): ```bash [optional scope]: [optional body] [optional footer(s)] ``` ### Commit types The following is a list of commit types: - feat: A new feature or functionality - fix: A bug fix - docs: Documentation only changes - style: Code formatting - refactor: Code changes that neither fixes a bug nor adds a feature. - perf: Improve performance. - test: Add missing or correct existing tests. - chore: Other commits that don’t modify src or test files. ## License By contributing your code to the repository, you agree to license your contribution under the [MIT license](./LICENSE). ================================================ FILE: CONTRIBUTING.zh-CN.md ================================================ > [English](./CONTRIBUTING.md) | 简体中文 # 贡献指南 感谢你的宝贵时间。你的贡献将使这个项目变得更好!在提交贡献之前,请务必花点时间阅读下面的入门指南。 ## 行为准则 该项目有一份 [行为准则](./CODE_OF_CONDUCT.md),希望参与项目的贡献者都能严格遵守。 ## 透明的开发 所有工作都直接透明地在 GitHub 上进行。核心团队成员和外部贡献者的 pull requests 都需要经过相同的 review 流程。 ## 语义化版本 该项目遵循语义化版本。我们对重要的漏洞修复发布修订号,对新特性或不重要的变更发布次版本号,对重大且不兼容的变更发布主版本号。 每个重大更改都将记录在 changelog 中。 ## 报告 Issues 我们使用 [Github issues](https://github.com/arco-design/arco-cli/issues) 进行 bug 报告和新 feature 建议。在报告 bug 之前,请确保已经搜索过类似的 [问题](https://github.com/arco-design/arco-cli/issues?repo=arco-cli),因为它们可能已经得到解答或正在被修复。新问题应通过 [问题助手](https://arco-design/issue-helper) 提交。对于 bug 报告,请包含可用于重现问题的代码。对于新 feature 建议,请指出你想要的更改以及期望的行为。 ## 提交 Pull Request 1. Fork [此仓库](https://github.com/arco-design/arco-cli),从 `master` 创建分支。新功能实现请发 pull request 到 `feature` 分支。其他更改发到 `master` 分支。 1. 在仓库根目录下执行 `npm run init`。 1. 对代码库进行更改。如果适用的话,请确保写了相应的测试。 1. 提交 git commit, 请同时遵守 [Commit 规范](#commit-guidelines)。 1. 提交 pull request, 如果有对应的 issue,请进行[关联](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword)。 ## Commit 指南 Commit messages 请遵循[conventional-changelog 标准](https://www.conventionalcommits.org/en/v1.0.0/): ```bash <类型>[可选 范围]: <描述> [可选 正文] [可选 脚注] ``` ### Commit 类型 以下是 commit 类型列表: - feat: 新特性或功能 - fix: 缺陷修复 - docs: 文档更新 - style: 代码风格更新 - refactor: 代码重构,不引入新功能和缺陷修复 - perf: 性能优化 - test: 单元测试 - chore: 其他不修改 src 或测试文件的提交 ## License [MIT 协议](./LICENSE). ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2021 Bytedance, Inc. and its affiliates. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # Arco CLI A command-line interface tool for creating, developing and managing Arco materials. ## How to start `npm run init` to set up environment. `lerna run dev` to enter dev mode. ================================================ FILE: babel.config.js ================================================ // eslint-disable-next-line @typescript-eslint/no-var-requires const createResolvePath = require('babel-plugin-tsconfig-paths-module-resolver/create-resolve'); const defaultResolvePath = createResolvePath(); module.exports = function (api) { api.cache(true); const presets = ['@babel/preset-react', '@babel/typescript']; const plugins = [ 'babel-plugin-transform-typescript-metadata', '@babel/plugin-transform-runtime', ['@babel/plugin-proposal-decorators', { legacy: true }], '@babel/plugin-proposal-export-namespace-from', '@babel/plugin-proposal-object-rest-spread', '@babel/plugin-proposal-optional-chaining', '@babel/plugin-proposal-class-properties', '@babel/plugin-proposal-logical-assignment-operators', [ 'tsconfig-paths-module-resolver', { resolvePath: function customResolvePath(sourceFile, currentFile, opts) { // alias for @arco-cli/packages is only for IDE ts-hint // we don't need to transform it while compiling if (sourceFile.startsWith('@arco-cli/')) { return null; } return defaultResolvePath(sourceFile, currentFile, opts); }, }, ], ]; return { presets, plugins, only: ['**/*.ts', '**/*.tsx'], // in generator/src/templates, babel should only compile files NOT ends with tpl.ts ignore: [/generator\/src\/templates\/([^/]+\/)*.+(? { console.error('uncaughtException', error); process.exit(1); }); import { bootstrap } from '@arco-cli/legacy/dist/bootstrap'; import { handleErrorAndExit } from '@arco-cli/legacy/dist/cli/handleErrors'; import { setCliVersion } from '@arco-cli/legacy/dist/utils'; import { runCLI } from './loadCli'; import packageJson from '../package.json'; (async function initApp() { try { setCliVersion(packageJson.version); await bootstrap(); await runCLI(); } catch (err: any) { const originalError = err.originalError || err; await handleErrorAndExit(originalError, process.argv[2]); } })(); ================================================ FILE: packages/arco/src/arco.aspect.ts ================================================ import { Aspect } from '@arco-cli/stone'; import { CORE_ASPECT_ID_MAP } from '@arco-cli/legacy/dist/constants'; export const ArcoAspect = Aspect.create({ id: CORE_ASPECT_ID_MAP.APP_ARCO, }); export default ArcoAspect; ================================================ FILE: packages/arco/src/arco.main.runtime.ts ================================================ import { MainRuntime } from '@arco-cli/core/dist/cli'; import { manifestMap } from './manifest'; const manifests = Object.values(manifestMap); export const ArcoMain = { name: 'arco', runtime: MainRuntime, dependencies: manifests, provider: () => {}, }; ================================================ FILE: packages/arco/src/index.ts ================================================ ================================================ FILE: packages/arco/src/loadCli.ts ================================================ import path from 'path'; import logger from '@arco-cli/legacy/dist/logger'; import { Stone, Extension, RuntimeDefinition } from '@arco-cli/stone'; import { ConfigOptions, StoneConfig as Config } from '@arco-cli/stone/dist/stoneConfig'; import { AspectLoaderAspect, AspectLoaderMain, getAspectDef, } from '@arco-cli/core/dist/aspect-loader'; import { CLIAspect, MainRuntime, CLIMain } from '@arco-cli/core/dist/cli'; import { getWorkspaceInfo } from '@arco-cli/legacy/dist/workspace/workspaceLocator'; import { ArcoAspect } from './arco.aspect'; import { ArcoMain } from './arco.main.runtime'; import { manifestMap } from './manifest'; // add ArcoMain here to make sure every aspect in manifest is loaded ArcoAspect.addRuntime(ArcoMain); export async function requireAspects(aspect: Extension, runtime: RuntimeDefinition) { const id = aspect.name; if (!id) throw new Error('could not retrieve aspect id'); // resolve aspect packages from the dir where @arco-cli/arco located // otherwise resolve.resolve can't find these packages const resolveModuleFrom = path.resolve(__dirname, '../../..'); const { runtimePath } = await getAspectDef(id, runtime.name, resolveModuleFrom); // eslint-disable-next-line return runtimePath ? require(runtimePath) : null; } async function getConfig(cwd = process.cwd()): Promise { const workspaceInfo = await getWorkspaceInfo(cwd); if (workspaceInfo) { const configOpts: ConfigOptions = { shouldThrow: false, cwd: workspaceInfo?.path, }; return Config.load(workspaceInfo.configFilename, configOpts); } return null; } async function loadArco(path = process.cwd()) { logger.info(`*** Loading arco *** argv:\n${process.argv.join('\n')}`); const config = await getConfig(); const configMap = config ? config.toObject() : {}; configMap[ArcoAspect.id] ||= {}; configMap[ArcoAspect.id].cwd = path; const stone = await Stone.load([CLIAspect, ArcoAspect], MainRuntime.name, configMap); await stone.run(async (aspect: Extension, runtime: RuntimeDefinition) => requireAspects(aspect, runtime) ); const aspectLoader = stone.get(AspectLoaderAspect.id); aspectLoader.setCoreAspects(Object.values(manifestMap)); return stone; } export async function runCLI() { const stone = await loadArco(); const cli = stone.get(CLIAspect.id); cli.run(false); } ================================================ FILE: packages/arco/src/manifest.ts ================================================ import { AspectLoaderAspect } from '@arco-cli/core/dist/aspect-loader'; import { LoggerAspect } from '@arco-cli/core/dist/logger'; import { PubsubAspect } from '@arco-cli/aspect/dist/pubsub'; import { ExpressAspect } from '@arco-cli/core/dist/express'; import { GraphqlAspect } from '@arco-cli/core/dist/graphql'; import { ComponentAspect } from '@arco-cli/aspect/dist/component'; import { EnvsAspect } from '@arco-cli/aspect/dist/envs'; import { ReactAspect } from '@arco-cli/react'; import { ReactRouterAspect } from '@arco-cli/aspect/dist/react-router'; import { BundlerAspect } from '@arco-cli/aspect/dist/bundler'; import { JestAspect } from '@arco-cli/aspect/dist/jest'; import { DocsAspect } from '@arco-cli/aspect/dist/docs'; import { WorkspaceAspect } from '@arco-cli/aspect/dist/workspace'; import { LessAspect } from '@arco-cli/aspect/dist/less'; import { SassAspect } from '@arco-cli/aspect/dist/sass'; import { MDXAspect } from '@arco-cli/aspect/dist/mdx'; import { UIAspect } from '@arco-cli/service/dist/ui'; import { PreviewAspect } from '@arco-cli/service/dist/preview'; import { TesterAspect } from '@arco-cli/service/dist/tester'; import { CompilerAspect } from '@arco-cli/service/dist/compiler'; import { BuilderAspect } from '@arco-cli/service/dist/builder'; import { SyncerAspect } from '@arco-cli/service/dist/syncer'; import { ForkAspect } from '@arco-cli/service/dist/fork'; import { GeneratorAspect } from '@arco-cli/service/dist/generator'; import { ArcoAspect } from './arco.aspect'; export const manifestMap = { [AspectLoaderAspect.id]: AspectLoaderAspect, [LoggerAspect.id]: LoggerAspect, [PubsubAspect.id]: PubsubAspect, [ExpressAspect.id]: ExpressAspect, [GraphqlAspect.id]: GraphqlAspect, [ComponentAspect.id]: ComponentAspect, [EnvsAspect.id]: EnvsAspect, [ReactAspect.id]: ReactAspect, [ReactRouterAspect.id]: ReactRouterAspect, [BundlerAspect.id]: BundlerAspect, [JestAspect.id]: JestAspect, [DocsAspect.id]: DocsAspect, [LessAspect.id]: LessAspect, [SassAspect.id]: SassAspect, [WorkspaceAspect.id]: WorkspaceAspect, [MDXAspect.id]: MDXAspect, [UIAspect.id]: UIAspect, [PreviewAspect.id]: PreviewAspect, [CompilerAspect.id]: CompilerAspect, [TesterAspect.id]: TesterAspect, [BuilderAspect.id]: BuilderAspect, [SyncerAspect.id]: SyncerAspect, [ForkAspect.id]: ForkAspect, [GeneratorAspect.id]: GeneratorAspect, }; export function isCoreAspect(id: string) { const _reserved = [ArcoAspect.id]; if (_reserved.includes(id)) return true; return !!manifestMap[id]; } export function getAllCoreAspectsIds(): string[] { const _reserved = [ArcoAspect.id]; return [...Object.keys(manifestMap), ..._reserved]; } ================================================ FILE: packages/arco/src/types.ts ================================================ import type { WorkspaceConfig } from '@arco-cli/aspect/dist/workspace'; import type { CompilerAspectConfig } from '@arco-cli/service/dist/compiler'; import type { GeneratorConfig } from '@arco-cli/service/dist/generator'; /** * type of arco.workspace.json config */ export type ArcoWorkspaceFile = { 'arco.aspect/workspace'?: WorkspaceConfig; 'arco.service/compiler'?: CompilerAspectConfig; 'arco.service/generator'?: GeneratorConfig; }; /** * type of arco.env.js config */ export type { ArcoEnvConfig } from '@arco-cli/aspect/dist/envs/types'; ================================================ FILE: packages/arco/tsconfig.json ================================================ { "extends": "../../tsconfig.json", "include": ["src"], "compilerOptions": { "baseUrl": ".", "outDir": "dist", "paths": { "@arco-cli/stone": ["../stone/src"], "@arco-cli/stone/dist/*": ["../stone/src/*"], "@arco-cli/react": ["../react/src"], "@arco-cli/legacy/dist/*": ["../legacy/src/*"], "@arco-cli/core/dist/*": ["../core/src/*"], "@arco-cli/aspect/dist/*": ["../aspect/src/*"], "@arco-cli/service/dist*": ["../service/src/*"], "@arco-cli/ui-foundation-react": ["../ui-foundation-react/src"], "@arco-cli/ui-foundation-react/dist/*": ["../ui-foundation-react/src/*"] } } } ================================================ FILE: packages/aspect/package.json ================================================ { "name": "@arco-cli/aspect", "version": "2.4.5", "homepage": "https://github.com/arco-design/arco-cli", "repository": "https://github.com/arco-design/arco-cli", "main": "./dist/index.js", "scripts": { "dev": "sh ../../.scripts/build.sh dev", "build": "sh ../../.scripts/build.sh", "build-type": "sh ../../.scripts/build-type.sh", "clean": "rm -rf dist", "clean-type": "find dist -name *.d.ts |xargs rm -rf", "prepublishOnly": "npm run build" }, "files": [ "dist" ], "license": "MIT", "dependencies": { "@apollo/client": "^3.7.2", "@arco-cli/core": "^2.3.2", "@arco-cli/legacy": "^2.3.2", "@arco-cli/service": "^2.4.5", "@arco-cli/stone": "^2.0.0-beta.0", "@arco-cli/ui-foundation-react": "^2.4.5", "@arco-design/web-react": "^2.42.2", "@babel/runtime": "^7.20.6", "@jest/test-result": "^29.5.0", "@mdx-js/mdx": "^1.6.22", "assert": "^2.0.0", "browserify-zlib": "^0.2.0", "buffer": "^6.0.3", "chalk": "^4.1.2", "chokidar": "^3.5.3", "comment-json": "^4.2.3", "compression-webpack-plugin": "^10.0.0", "constants-browserify": "^1.0.0", "crypto-browserify": "^3.12.0", "domain-browser": "^4.22.0", "enhanced-resolve": "^4.5.0", "eventemitter2": "^6.4.9", "fs-extra": "^10.1.0", "glob": "^8.0.3", "graphql-tag": "^2.12.6", "html-webpack-plugin": "^5.5.0", "https-browserify": "^1.0.0", "less": "^4.1.3", "loader-utils": "^3.2.1", "lodash": "^4.17.21", "lodash-es": "^4.17.21", "minimatch": "^6.1.6", "os-browserify": "^0.3.0", "p-map-series": "^2.1.0", "p-queue": "^6.6.2", "path-browserify": "^1.0.1", "penpal": "^6.2.2", "process": "^0.11.10", "punycode": "^2.1.1", "querystring-es3": "^0.2.1", "react-dev-utils": "^12.0.1", "remark-admonitions": "^1.2.1", "remark-frontmatter": "^2.0.0", "reset-css": "^5.0.1", "sass": "^1.57.0", "semver": "^7.3.8", "speed-measure-webpack-plugin": "^1.5.0", "stream-browserify": "^3.0.0", "stream-http": "^3.2.0", "string_decoder": "^1.3.0", "timers-browserify": "^2.0.12", "tty-browserify": "^0.0.1", "typescript": "^4.9.3", "unist-util-remove": "^2.1.0", "unist-util-visit": "^2.0.3", "url": "^0.11.0", "util": "^0.12.5", "vfile": "^4.2.1", "vm-browserify": "^1.1.2", "webpack": "^5.75.0", "webpack-assets-manifest": "^5.1.0", "webpack-dev-server": "^4.11.1", "webpack-merge": "^5.8.0", "yaml": "^1.10.2" }, "devDependencies": { "@types/glob": "^8.0.1", "@types/less": "^3.0.3" } } ================================================ FILE: packages/aspect/src/bundler/browserRuntime.ts ================================================ import { ExecutionContext } from '@aspect/envs'; export type BrowserRuntime = { entry: (context: ExecutionContext) => Promise; }; ================================================ FILE: packages/aspect/src/bundler/bundler.aspect.ts ================================================ import { Aspect } from '@arco-cli/stone'; export const BundlerAspect = Aspect.create({ id: 'arco.aspect/bundler', }); export default BundlerAspect; ================================================ FILE: packages/aspect/src/bundler/bundler.main.runtime.ts ================================================ import { MainRuntime } from '@arco-cli/core/dist/cli'; import { Slot, SlotRegistry } from '@arco-cli/stone'; import { GraphqlAspect, GraphqlMain } from '@arco-cli/core/dist/graphql'; import { EnvsAspect, EnvsMain } from '@aspect/envs'; import { ComponentAspect, Component } from '@aspect/component'; import { BundlerAspect } from './bundler.aspect'; import { BrowserRuntime } from './browserRuntime'; import { DevServerService } from './devServer.service'; import { ComponentServer } from './componentServer'; import getDevServerSchema from './devServer.graphql'; export type BrowserRuntimeSlot = SlotRegistry; export class BundlerMain { static runtime = MainRuntime; static dependencies = [EnvsAspect, GraphqlAspect, ComponentAspect]; static slots = [Slot.withType()]; static async provider( [envs, graphql]: [EnvsMain, GraphqlMain], _config, [runtimeSlot]: [BrowserRuntimeSlot] ) { const devServerService = new DevServerService(runtimeSlot); const bundler = new BundlerMain(envs, runtimeSlot, devServerService); graphql.register(getDevServerSchema(bundler)); return bundler; } constructor( private envs: EnvsMain, private runtimeSlot: BrowserRuntimeSlot, private devService: DevServerService ) {} private componentServers: ComponentServer[] = []; /** * register a new browser runtime environment. */ registerTarget(runtime: BrowserRuntime) { this.runtimeSlot.register(runtime); return this; } async devServer(components: Component[]): Promise { const envRuntime = await this.envs.createEnvironment(components); const servers: ComponentServer[] = await envRuntime.runOnce(this.devService); this.componentServers = servers; return servers; } /** * get a dev server instance containing a component. * @param component */ getComponentServer(component: Component): undefined | ComponentServer { const envId = component.env; const server = this.componentServers.find( (componentServer) => componentServer.context.relatedContexts.includes(envId) || componentServer.context.id === envId ); return server; } } BundlerAspect.addRuntime(BundlerMain); ================================================ FILE: packages/aspect/src/bundler/bundler.ts ================================================ import { Component } from '@aspect/component'; export type Asset = { /** * name of the asset. */ name: string; /** * size of the asset in bytes. */ size: number; /** * size of the compressed asset in bytes. */ compressedSize?: number; }; export type Chunk = { runtime: string[]; files: string[]; auxiliaryFiles: string[]; }; export type ChunksAssetsMap = { [assetName: string]: string[]; }; export type EntryAssets = { assets: Asset[]; auxiliaryAssets: Asset[]; assetsSize: number; compressedAssetsSize?: number; auxiliaryAssetsSize: number; compressedAuxiliaryAssetsSize: number; }; export type EntriesAssetsMap = { [entryId: string]: EntryAssets; }; export type BundlerResult = { /** * list of generated assets. */ assets: Asset[]; /** * A map of assets names for each chunk */ assetsByChunkName?: ChunksAssetsMap; /** * A map of assets for each entry point */ entriesAssetsMap?: EntriesAssetsMap; /** * Chunks info for built assets */ chunks?: Chunk[]; /** * errors thrown during the bundling process. */ errors: Error[]; /** * warnings thrown during the bundling process. */ warnings: string[]; /** * components included in the bundling process. */ components: Component[]; /** * timestamp in milliseconds when the task started */ startTime?: number; /** * timestamp in milliseconds when the task ended */ endTime?: number; /** * out put path of the Bundler Result */ outputPath?: string; }; export interface Bundler { run(): Promise; } export type BundlerMode = 'dev' | 'prod'; ================================================ FILE: packages/aspect/src/bundler/bundlerContext.ts ================================================ import { BuildContext } from '@arco-cli/service/dist/builder'; import { Component } from '@aspect/component'; export type LibraryOptions = { /** * Specify a name for the library */ name: string; /** * Configure how the library will be exposed * could be values like: 'umd', 'umd2', 'amd', 'commonjs', */ type?: string; }; export type Chunking = { /** * include all types of chunks (async / non-async) in splitting */ splitChunks: boolean; }; export type MetaData = { /** * Who initiate the bundling process */ initiator?: string; /** * Env id (used usually to calculate the config) */ envId?: string; }; export type HtmlConfig = { /** * The title to use for the generated HTML document */ title: string; /** * The file to write the HTML to. Defaults to index.html */ filename?: string; /** * Allows you to add only some chunks (e.g only the unit-test chunk) */ chunks?: string[]; /** * Load chunks according to their order in the `chunks` array * @default auto */ chunkOrder?: 'auto' | 'manual'; /** * provide an inline template */ templateContent: string; /** * Controls if and in what ways the output should be minified */ minify?: boolean; /** * The favicon for the html page */ favicon?: string; }; export type Entry = { /** * Specifies the name of each output file on disk */ filename: string; /** * Module(s) that are loaded upon startup */ import: string | string[]; /** * Specify library options to bundle a library from current entry */ library?: LibraryOptions; }; export type EntryMap = { [entryName: string]: Entry; }; export type ModuleTarget = { /** * name of the module. */ name: string; /** * module exposes. */ exposes: { [internalPath: string]: string; }; shared: { [key: string]: any; }; }; export type Target = { /** * entries of the target. */ entries: string[] | EntryMap; /** * array of components included in the target. */ components: Component[]; /** * output path of the target */ outputPath: string; /** * This option determines the name of each output bundle */ filename?: string; /** * This option determines the name of non-initial chunk files */ chunkFilename?: string; /** * Whether to run compression by the bundler */ compress?: boolean; /** * List of peer dependencies */ peers?: string[]; /** * config for html generation */ html?: HtmlConfig[]; /** * module targets to expose. */ modules?: ModuleTarget[]; /** * Name for the runtime chunk */ runtimeChunkName?: string; /** * Different configuration related to chunking */ chunking?: Chunking; /** * A path for the host root dir * Host root dir is usually the env root dir * This can be used in different bundle options which run require.resolve * for example when configuring webpack aliases or webpack expose loader on the peers deps */ hostRootDir?: string; }; export interface BundlerContext extends BuildContext { /** * all components about to be build */ components: Component[]; /** * targets for bundling. */ targets: Target[]; /** * determines whether it is a production build, default is `true`. * in development, expect the bundler to favour debugging on the expanse of optimization. */ development?: boolean; /** * public path output of the bundle. */ publicPath?: string; /** * root path */ rootPath?: string; /** * Whether to run compression by the bundler */ compress?: boolean; /** * config for html generation for all targets */ html?: HtmlConfig[]; /** * modules for bundle to expose. used by module federation at webpack, or with different methods applied by various bundlers. */ modules?: { name: string; fileName: string; exposes: { [key: string]: string }; }; /** * Additional info that can be used by the bundler for different stuff like logging info */ metaData?: MetaData; } ================================================ FILE: packages/aspect/src/bundler/componentServer.ts ================================================ import { AddressInfo } from 'net'; import { Port } from '@arco-cli/legacy/dist/utils/network/port'; import { ExecutionContext } from '@aspect/envs'; import { DevServer } from './devServer'; import { BindError } from './exceptions'; export class ComponentServer { errors?: Error[]; private _port: number; constructor( /** * components contained in the existing component server. */ readonly context: ExecutionContext, /** * port range of the component server. */ readonly portRange: number[], /** * env dev server. */ readonly devServer: DevServer ) {} hostname: string | undefined; private async selectPort(portRange?: number[] | number) { return Port.getPortFromRange(portRange || [3100, 3200]); } private getHostname(address: string | AddressInfo | null) { if (address === null) throw new BindError(); if (typeof address === 'string') return address; let hostname = address.address; if (hostname === '::') { hostname = 'localhost'; } return hostname; } get port() { return this._port; } /** * get the url of the component server. */ get url() { // tailing `/` is required! return `/preview/${this.context.envRuntime.id}/`; } async listen() { const port = await this.selectPort(this.portRange); this._port = port; const server = await this.devServer.listen(port); const address = server.address(); const hostname = this.getHostname(address); if (!address) throw new BindError(); this.hostname = hostname; } } ================================================ FILE: packages/aspect/src/bundler/dedupEnvs.ts ================================================ import type { ExecutionContext } from '@aspect/envs'; type GroupIdContextMap = Record; /** * de-duping dev servers by the amount of type the dev server configuration was overridden by envs. * This will split the dev server to groups of dev server that share the same webpack config */ export function dedupEnvs(contexts: ExecutionContext[]) { const groupedEnvs: GroupIdContextMap = {}; contexts.forEach((context) => { const envId = context.env?.getDevEnvId(context); if (!envId) return; if (!(envId in groupedEnvs)) groupedEnvs[envId] = []; groupedEnvs[envId].push(context); }); return groupedEnvs; } ================================================ FILE: packages/aspect/src/bundler/devServer.graphql.ts ================================================ import gql from 'graphql-tag'; import { Component } from '@aspect/component'; import { BundlerMain } from './bundler.main.runtime'; export default function (bundler: BundlerMain) { return { typeDefs: gql` extend type Component { server: ComponentServer } type ComponentServer { env: String url: String } `, resolvers: { Component: { server: (component: Component) => { const componentServer = bundler.getComponentServer(component); return componentServer ? { env: componentServer.context.envRuntime.id, url: componentServer.url, } : {}; }, }, }, }; } ================================================ FILE: packages/aspect/src/bundler/devServer.service.ts ================================================ import { sep } from 'path'; import { flatten } from 'lodash'; import { EnvService, ExecutionContext, EnvDefinition } from '@aspect/envs'; import { BrowserRuntimeSlot } from './bundler.main.runtime'; import { ComponentServer } from './componentServer'; import { dedupEnvs } from './dedupEnvs'; import { DevServer } from './devServer'; import { DevServerContext } from './devServerContext'; import { getEntry } from './getEntry'; export type DevServerDescriptor = { /** * id of the dev server (e.g. jest/mocha) */ id: string; /** * display name of the dev server (e.g. Jest / Mocha) */ displayName: string; /** * string containing the config for display. */ config: string; version?: string; }; export class DevServerService implements EnvService { name = 'dev server'; constructor( /** * browser runtime slot */ private runtimeSlot: BrowserRuntimeSlot ) {} private getComponentsFromContexts(contexts: ExecutionContext[]) { return flatten( contexts.map((context) => { return context.components; }) ); } /** * builds the execution context for the dev server. */ private async buildContext( context: ExecutionContext, additionalContexts: ExecutionContext[] = [] ): Promise { context.relatedContexts = additionalContexts.map((ctx) => ctx.envDefinition.id); context.components = context.components.concat( this.getComponentsFromContexts(additionalContexts) ); return Object.assign(context, { entry: await getEntry(context, this.runtimeSlot), // don't start with a leading "/" because it generates errors on Windows rootPath: `preview/${context.envRuntime.id}`, publicPath: `${sep}public`, }); } async getDescriptor( environment: EnvDefinition, context?: ExecutionContext[] ): Promise { if (!environment.env.getDevServer || !context) return undefined; const mergedContext = await this.buildContext(context[0], []); const devServer: DevServer = environment.env.getDevServer(mergedContext); return { id: devServer.id || '', displayName: devServer.displayName || '', config: devServer.displayConfig ? devServer.displayConfig() : '', version: devServer.version ? devServer.version() : '?', }; } async runOnce(contexts: ExecutionContext[]): Promise { const groupedEnvs = await dedupEnvs(contexts); const servers = await Promise.all( Object.entries(groupedEnvs).map(async ([id, contextList]) => { const mainContext = contextList.find((context) => context.envDefinition.id === id) || contextList[0]; const additionalContexts = contextList.filter((context) => context.envDefinition.id !== id); const devServerContext = await this.buildContext(mainContext, additionalContexts); const devServer: DevServer = await devServerContext.envRuntime.env.getDevServer( devServerContext ); return new ComponentServer(devServerContext, [3300, 3400], devServer); }) ); return servers; } } ================================================ FILE: packages/aspect/src/bundler/devServer.ts ================================================ import { Server } from 'http'; /** * interface for implementing dev servers. */ export interface DevServer { /** * attach to given port and start an http server */ listen(port: number): Server | Promise; /** * display name of the dev-server. */ displayName?: string; /** * icon of the dev-server. */ icon?: string; /** * serialized config of the dev-server. */ displayConfig?(): string; /** * path to the config in the filesystem. */ configPath?: string; /** * id of the dev-server. */ id: string; /** * return the dev-server version. */ version?(): string; } ================================================ FILE: packages/aspect/src/bundler/devServerContext.ts ================================================ import { ExecutionContext } from '@aspect/envs'; export interface DevServerContext extends ExecutionContext { /** * array of files to include. */ entry: string[]; /** * public path. */ publicPath: string; /** * root path of the workspace. */ rootPath: string; /** * title of the page. */ title?: string; /** * favicon of the page. */ favicon?: string; } ================================================ FILE: packages/aspect/src/bundler/exceptions/bindError.ts ================================================ export class BindError extends Error {} ================================================ FILE: packages/aspect/src/bundler/exceptions/index.ts ================================================ export { BindError } from './bindError'; ================================================ FILE: packages/aspect/src/bundler/getEntry.ts ================================================ import { ExecutionContext } from '@aspect/envs'; import { BrowserRuntimeSlot } from './bundler.main.runtime'; /** * computes the bundler entry. */ export async function getEntry( context: ExecutionContext, runtimeSlot: BrowserRuntimeSlot ): Promise { const slotEntries = await Promise.all( runtimeSlot.values().map(async (browserRuntime) => browserRuntime.entry(context)) ); const slotPaths = slotEntries.reduce((acc, current) => { acc = acc.concat(current); return acc; }); return slotPaths; } ================================================ FILE: packages/aspect/src/bundler/index.ts ================================================ import { BundlerAspect } from './bundler.aspect'; export default BundlerAspect; export { BundlerAspect }; export { DevServer } from './devServer'; export type { BundlerMain } from './bundler.main.runtime'; export type { DevServerContext } from './devServerContext'; export { ComponentServer } from './componentServer'; export { Bundler, BundlerResult, BundlerMode, Asset, Chunk, ChunksAssetsMap, EntriesAssetsMap, EntryAssets, } from './bundler'; export { BundlerContext, Target, ModuleTarget, HtmlConfig as BundlerHtmlConfig, EntryMap as BundlerEntryMap, Entry as BundlerEntry, MetaData as BundlerContextMetaData, } from './bundlerContext'; ================================================ FILE: packages/aspect/src/component/component.aspect.ts ================================================ import { Aspect } from '@arco-cli/stone'; export const ComponentAspect = Aspect.create({ id: 'arco.aspect/component', }); export default ComponentAspect; ================================================ FILE: packages/aspect/src/component/component.graphql.ts ================================================ import gql from 'graphql-tag'; import type { Component } from './component'; import { ComponentMain } from './component.main.runtime'; import { ComponentFactory } from './componentFactory'; export default function (componentExtension: ComponentMain) { return { typeDefs: gql` type Component { # id of the component. id: String! # display name of the component name: String! # labels of the component labels: [String]! # package name of the component packageName: String! # version of the component version: String! # author of the component author: String! # additional styles of the component extraStyles: [ComponentExtraStyles]! } type ComponentExtraStyles { title: String href: String } type ComponentHost { id: String! name: String! # load a component. get(id: String!): Component # list components list(offset: Int, limit: Int): [Component]! } type Query { getHost(id: String): ComponentHost } `, resolvers: { Component: { id: (component: Component) => component.id, name: (component: Component) => component.name, labels: (component: Component) => component.labels, version: (component: Component) => component.version, packageName: (component: Component) => component.packageName, author: (component: Component) => component.author, }, ComponentHost: { id: async (host: ComponentFactory) => { return host.name; }, name: async (host: ComponentFactory) => { return host.name; }, list: async (host: ComponentFactory) => { return host.list(); }, get: async (host: ComponentFactory, { id }: { id: string }) => { try { const component = await host.get(id); return component; } catch (error: any) { return null; } }, }, Query: { getHost: () => { return componentExtension.getHost(); }, }, }, }; } ================================================ FILE: packages/aspect/src/component/component.main.runtime.ts ================================================ import { Slot, SlotRegistry } from '@arco-cli/stone'; import { MainRuntime } from '@arco-cli/core/dist/cli'; import { GraphqlAspect, GraphqlMain } from '@arco-cli/core/dist/graphql'; import ComponentAspect from './component.aspect'; import { ComponentFactory } from './componentFactory'; import { HostNotFoundError } from './exceptions'; import getComponentSchema from './component.graphql'; export type ComponentHostSlot = SlotRegistry; export class ComponentMain { static runtime = MainRuntime; static slots = [Slot.withType()]; static dependencies = [GraphqlAspect]; static provider([graphql]: [GraphqlMain], _config, [hostSlot]: [ComponentHostSlot]) { const componentMain = new ComponentMain(hostSlot); graphql.register(getComponentSchema(componentMain)); return componentMain; } private _priorHost: ComponentFactory | undefined; constructor(private hostSlot: ComponentHostSlot) {} private getPriorHost() { if (this._priorHost) return this._priorHost; const hosts = this.hostSlot.values(); return hosts[0]; } registerHost(host: ComponentFactory) { this.hostSlot.register(host); return this; } getHost(id?: string): ComponentFactory { if (id) { const host = this.hostSlot.get(id); if (!host) throw new HostNotFoundError(id); return host; } return this.getPriorHost(); } } ComponentAspect.addRuntime(ComponentMain); ================================================ FILE: packages/aspect/src/component/component.ts ================================================ import fs from 'fs-extra'; import path from 'path'; import { SourceFile } from '@arco-cli/legacy/dist/workspace/component/sources'; import { ComponentInfo } from '@arco-cli/legacy/dist/workspace/componentInfo'; import { DEFAULT_ENV } from '@arco-cli/legacy/dist/constants'; import { ComponentNotFoundInPathError } from '@arco-cli/legacy/dist/workspace/component/exceptions'; import { ExtensionDataEntry, ExtensionDataList } from './extensionData'; export class Component { public extensions: ExtensionDataList = new ExtensionDataList(); constructor(private info: ComponentInfo, public files: SourceFile[] = []) {} get id() { return this.info.id; } get name() { return this.info.name; } get group() { return this.info.group; } get author() { return this.info.author; } get labels() { return this.info.labels; } get env() { return DEFAULT_ENV; } get language() { return 'javascript'; } get version() { return this.info.version; } get packageName() { return this.info.packageName; } get dependencies() { return this.info.dependencies; } get devDependencies() { return this.info.devDependencies; } get peerDependencies() { return this.info.peerDependencies; } get rootDir() { return this.info.rootDir; } get componentDir() { return path.join(this.info.rootDir, this.entries.base); } get packageDir() { return this.info.packageDir; } get packageDirAbs() { return this.info.packageDirAbs; } get entries() { return this.info.entries; } get repository() { return this.info.repository; } get uiResource() { return this.info.uiResource; } get extraStyles() { return this.info.extraStyles; } get forkable() { return this.info.forkable; } get rawConfig() { return this.info.rawConfig; } async upsertExtensionData(extension: string, data: Record) { if (!data) return; const existingExtension = this.extensions.findExtension(extension); if (existingExtension) { // Only merge top level of extension data Object.assign(existingExtension.data, data); } else { this.extensions.push(await new ExtensionDataEntry(extension, undefined, data)); } } static async loadFromFileSystem(info: ComponentInfo, projectPath: string) { const rootDirAbs = path.join(projectPath, info.rootDir); if (!fs.existsSync(rootDirAbs)) throw new ComponentNotFoundInPathError(rootDirAbs); const files = info.files.map((file) => { const filePath = path.join(rootDirAbs, file.relativePath); return SourceFile.load(filePath, rootDirAbs, projectPath, { test: file.test, }); }); return new Component(info, files); } } ================================================ FILE: packages/aspect/src/component/componentFactory.ts ================================================ import { AspectDefinition } from '@arco-cli/core/dist/aspect-loader'; import { Component } from './component'; export interface ComponentFactory { /** * name of the component host. */ name: string; /** * path to the component host. */ path: string; /** * returns a component by id. */ get(id: string): Promise; /** * returns many components by ids. */ getMany(ids: string[]): Promise; /** * get component-ids matching the given pattern. a pattern can have multiple patterns separated by a comma. * it uses multimatch (https://www.npmjs.com/package/multimatch) package for the matching algorithm, which supports * (among others) negate character "!" to exclude ids. See the package page for more supported characters. */ getManyByPattern(pattern: string, throwForNoMatch?: boolean): Promise; /** * list all components in the host. */ list(): Promise; /** * resolve dirs for aspects */ resolveAspects: (runtimeName?: string) => Promise; } ================================================ FILE: packages/aspect/src/component/componentMap.ts ================================================ import { Component } from './component'; /** * allows to index components -> values. */ export class ComponentMap { constructor(readonly hashMap: Map) {} get components() { return this.toArray().map(([component]) => component); } /** * get a value for a component. */ get(component: Component) { return this.hashMap.get(component.id); } /** * get a value by the component-id */ getValueByComponentId(componentId: string): T | null { const tuple = this.hashMap.get(componentId); if (!tuple) return null; return tuple[1]; } /** * returns an array. */ toArray() { return Array.from(this.hashMap.values()); } /** * map entries and return a new component map. */ map(predicate: (value: T, component: Component) => NewType): ComponentMap { const tuples: [string, [Component, NewType]][] = this.toArray().map(([component, value]) => { const newValue = predicate(value, component); return [component.id, [component, newValue]]; }); return new ComponentMap(new Map(tuples)); } /** * similar to Array.forEach, but here you get both, the value and the component. */ forEach(predicate: (value: T, component: Component) => void): void { this.toArray().forEach(([component, value]) => { predicate(value, component); }); } /** * flatten values of all components into a single array. */ flattenValue(): T[] { return this.toArray().reduce((acc: T[], [, value]) => { acc = acc.concat(value); return acc; }, []); } /** * filter all components with empty values and return a new map. */ filter(predicate: (value: T) => boolean): ComponentMap { const tuples = this.toArray().filter(([, value]) => { return predicate(value); }); const asMap: [string, [Component, T]][] = tuples.map(([component, value]) => { return [component.id, [component, value]]; }); return new ComponentMap(new Map(asMap)); } /** * get all component ids. */ keys() { return this.hashMap.keys(); } static create(rawMap: [Component, U][]) { const newMap: [string, [Component, U]][] = rawMap.map(([component, data]) => { return [component.id, [component, data]]; }); return new ComponentMap(new Map(newMap)); } /** * create a component map from components and a value predicate. * @param components components to zip into the map. * @param predicate predicate for returning desired value. */ static as(components: Component[], predicate: (component: Component) => U): ComponentMap { const tuples: [string, [Component, U]][] = components.map((component) => { return [component.id, [component, predicate(component)]]; }); return new ComponentMap(new Map(tuples)); } } ================================================ FILE: packages/aspect/src/component/exceptions/hostNotFoundError.ts ================================================ export class HostNotFoundError extends Error { constructor(private hostName: string) { super(); } toString() { return `[component] error: host '${this.hostName}' was not found`; } } ================================================ FILE: packages/aspect/src/component/exceptions/index.ts ================================================ export { HostNotFoundError } from './hostNotFoundError'; export { InvalidNameError } from './invalidNameError'; export { InvalidVersionError } from './invalidVersionError'; ================================================ FILE: packages/aspect/src/component/exceptions/invalidNameError.ts ================================================ export class InvalidNameError extends Error { componentName: string; constructor(componentName: string) { super( `error: "${componentName}" is invalid, component names can only contain alphanumeric, lowercase characters, and the following ["-", "_", "$", "!", "/"]` ); } } ================================================ FILE: packages/aspect/src/component/exceptions/invalidVersionError.ts ================================================ export class InvalidVersionError extends Error { version: string | null | undefined; constructor(version?: string | null) { super( `error: version ${ version || '(empty)' } is not a valid semantic version. learn more: https://semver.org` ); } } ================================================ FILE: packages/aspect/src/component/extensionData.ts ================================================ import { cloneDeep, compact, isEmpty } from 'lodash'; type RemoveExtensionSpecialSign = '-'; type ExtensionConfig = { [extName: string]: any } | RemoveExtensionSpecialSign; type ConfigOnlyEntry = { id: string; config: ExtensionConfig; }; export const REMOVE_EXTENSION_SPECIAL_SIGN = '-'; export class ExtensionDataEntry { constructor( public extensionId?: string, public rawConfig: ExtensionConfig = {}, public data: { [key: string]: any } = {} ) {} get config(): { [key: string]: any } { if (this.rawConfig === REMOVE_EXTENSION_SPECIAL_SIGN) return {}; return this.rawConfig; } set config(val: { [key: string]: any }) { this.rawConfig = val; } get isRemoved(): boolean { return this.rawConfig === REMOVE_EXTENSION_SPECIAL_SIGN; } toObject() { return { extensionId: this.extensionId, // Do not use raw config here config: this.config, data: this.data, }; } clone(): ExtensionDataEntry { return new ExtensionDataEntry( this.extensionId, cloneDeep(this.rawConfig), cloneDeep(this.data) ); } } export class ExtensionDataList extends Array { static coreExtensionsNames: Map = new Map(); static registerCoreExtensionName(name: string) { ExtensionDataList.coreExtensionsNames.set(name, ''); } static registerManyCoreExtensionNames(names: string[]) { names.forEach((name) => { ExtensionDataList.coreExtensionsNames.set(name, ''); }); } get ids(): string[] { return this.map((entry) => entry.extensionId); } findExtension(extensionId: string): ExtensionDataEntry | undefined { return this.find((extEntry) => extEntry.extensionId === extensionId); } remove(id) { return ExtensionDataList.fromArray( this.filter((entry) => { return entry.extensionId !== id; }) ); } toConfigObject() { const res = {}; this.forEach((entry) => { if (entry.rawConfig && !isEmpty(entry.rawConfig)) { res[entry.extensionId] = entry.rawConfig; } }); return res; } toConfigArray(): ConfigOnlyEntry[] { const arr = this.map((entry) => { // Remove extensions without config const clonedEntry = entry.clone(); if (clonedEntry.rawConfig && !isEmpty(clonedEntry.rawConfig)) { return isEmpty(clonedEntry.rawConfig) ? null : { id: clonedEntry.extensionId, config: clonedEntry.config }; } return null; }); return compact(arr); } clone(): ExtensionDataList { const extensionDataEntries = this.map((extensionData) => extensionData.clone()); return new ExtensionDataList(...extensionDataEntries); } static fromConfigObject(obj: { [extensionId: string]: any } = {}): ExtensionDataList { const arr = Object.keys(obj).map((extensionId) => { return new ExtensionDataEntry(extensionId, obj[extensionId]); }); return this.fromArray(arr); } static fromArray(entries: ExtensionDataEntry[]): ExtensionDataList { if (!entries || !entries.length) { return new ExtensionDataList(); } return new ExtensionDataList(...entries); } } ================================================ FILE: packages/aspect/src/component/index.ts ================================================ import { ComponentAspect } from './component.aspect'; export default ComponentAspect; export { ComponentAspect }; export { Component } from './component'; export type { ComponentMain } from './component.main.runtime'; export type { ComponentFactory } from './componentFactory'; export { ComponentMap } from './componentMap'; ================================================ FILE: packages/aspect/src/component/type/custom.d.ts ================================================ declare module '*.module.css' { const classes: { readonly [key: string]: string }; export default classes; } declare module '*.module.scss' { const classes: { readonly [key: string]: string }; export default classes; } declare module '*.module.sass' { const classes: { readonly [key: string]: string }; export default classes; } declare module '*.module.less' { const classes: { readonly [key: string]: string }; export default classes; } declare module '*.less' { const classes: { readonly [key: string]: string }; export default classes; } declare module '*.css' { const classes: { readonly [key: string]: string }; export default classes; } declare module '*.sass' { const classes: { readonly [key: string]: string }; export default classes; } declare module '*.scss' { const classes: { readonly [key: string]: string }; export default classes; } declare module '*.mdx' { const component: any; export default component; } ================================================ FILE: packages/aspect/src/component/uiRuntime/component.module.scss ================================================ .container { } ================================================ FILE: packages/aspect/src/component/uiRuntime/component.tsx ================================================ import React, { useMemo, ReactNode, useEffect } from 'react'; // eslint-disable-next-line import/no-extraneous-dependencies import { RouteProps } from 'react-router-dom'; import { flatten } from 'lodash'; import { SlotRegistry } from '@arco-cli/stone'; import { SlotRouter } from '@aspect/react-router/uiRuntime'; import { ComponentProvider } from './componentContext'; import { ComponentModel } from './componentModel'; import { useComponentQuery } from './hooks/useComponentQuery'; import { getIdFromLocation } from './utils/getIdFromLocation'; import styles from './component.module.scss'; export type ComponentPageElement = { type: 'before' | 'after'; content: ReactNode; }; export type ComponentPageSlot = SlotRegistry; export type ComponentProps = { host: string; path?: string; componentId: string; routes: RouteProps[]; containerSlot?: ComponentPageSlot; onComponentChange?: (activeComponent?: ComponentModel) => void; }; export function Component({ host, path, containerSlot, routes, componentId, onComponentChange, }: ComponentProps) { const idFromLocation = getIdFromLocation(); const resolvedComponentIdStr = path || idFromLocation; const { component, error } = useComponentQuery(componentId || idFromLocation, host); useEffect(() => onComponentChange?.(component), [component]); // cleanup when unmounting component useEffect(() => () => onComponentChange?.(undefined), []); const pageItems = useMemo(() => flatten(containerSlot?.values()), [containerSlot]); const before = useMemo( () => pageItems.filter((x) => x.type === 'before').map((x) => x.content), [pageItems] ); const after = useMemo( () => pageItems.filter((x) => x.type === 'after').map((x) => x.content), [pageItems] ); if (error) { return error.renderError(); } if (!component) { return null; } return ( {before}
{routes ? : null}
{after}
); } ================================================ FILE: packages/aspect/src/component/uiRuntime/component.ui.runtime.tsx ================================================ import React from 'react'; import type { RouteProps } from 'react-router-dom'; import { UIRuntime } from '@arco-cli/service/dist/ui/ui.aspect'; import { Slot, SlotRegistry } from '@arco-cli/stone'; import ComponentAspect from '../component.aspect'; import { Component, ComponentPageElement, ComponentPageSlot } from './component'; type RouteSlot = SlotRegistry; export class ComponentUI { static runtime = UIRuntime; static dependencies = []; static slots = [Slot.withType(), Slot.withType()]; static provider(_deps, _config, [routeSlot, pageItemSlot]: [RouteSlot, ComponentPageSlot]) { const componentUI = new ComponentUI(routeSlot, pageItemSlot); return componentUI; } constructor(private routeSlot: RouteSlot, private pageItemSlot: ComponentPageSlot) {} readonly routePath = '/*'; registerRoute(routes: RouteProps) { this.routeSlot.register(routes); return this; } registerPageItem = (...items: ComponentPageElement[]) => { this.pageItemSlot.register(items); return this; }; getComponentUI(host: string, componentId?: string) { return ( ); } } ComponentAspect.addRuntime(ComponentUI); ================================================ FILE: packages/aspect/src/component/uiRuntime/componentContext.tsx ================================================ import React, { createContext, PropsWithChildren } from 'react'; import { ComponentModel } from './componentModel'; export const ComponentContext: React.Context = createContext( ComponentModel.empty() ); export function ComponentProvider({ component, children, }: PropsWithChildren<{ component: ComponentModel }>) { return {children}; } ================================================ FILE: packages/aspect/src/component/uiRuntime/componentError.tsx ================================================ import React from 'react'; import { NotFoundPage } from '@arco-cli/ui-foundation-react'; export class ComponentError { constructor( /** * http status code of error */ public readonly code: number, /** * error message of the error */ public readonly message?: string ) {} renderError() { return ; } } ================================================ FILE: packages/aspect/src/component/uiRuntime/componentMeta/componentMeta.module.scss ================================================ .metadata { .flexBetween { display: flex; justify-content: space-between; } .title { margin: 0 0 12px; padding: 0; font-size: var(--preview-font-size-display-1); font-weight: 600; line-height: 1.3; color: var(--preview-color-text-1); } .descriptions { margin: 0 0 2px; font-size: var(--preview-font-size-body-3); line-height: 1.5; color: var(--preview-color-text-2); } .bottomWrapper { display: flex; align-items: flex-end; justify-content: space-between; } .labels { display: flex; align-items: center; font-size: var(--preview-font-size-body-3); color: var(--preview-color-text-3); :global(.arco-icon) { margin-right: 8px; } } .rightWrapper { display: flex; justify-content: center; > * { margin-left: 12px; } .extraStyleSelect { display: inline-flex; width: auto; :global(.arco-select-prefix) { margin-right: 8px; } :global(.arco-select-suffix) { margin-left: 8px; } } .usage { display: inline-flex; align-items: center; height: 32px; padding: 0 12px; border-radius: 2px; background: var(--color-fill-2); color: var(--color-text-1); } } } ================================================ FILE: packages/aspect/src/component/uiRuntime/componentMeta/componentMeta.tsx ================================================ import React, { useEffect } from 'react'; import { Typography, Select } from '@arco-design/web-react'; import { IconTag, IconSkin } from '@arco-design/web-react/icon'; import { ComponentModel } from '../componentModel'; import styles from './componentMeta.module.scss'; export interface ComponentMetaProps { component: ComponentModel; onComponentExtraStyleChange?: (href: string) => void; } export function ComponentMeta({ component, onComponentExtraStyleChange }: ComponentMetaProps) { const defaultExtraStyle = component.extraStyles?.[0]?.href; useEffect(() => { defaultExtraStyle && onComponentExtraStyleChange?.(defaultExtraStyle); }, []); return (

{component.name}

{component.description}

{component.labels.join(' / ')}
{component.extraStyles?.length ? ( } value={filterText} onChange={setFilterText} placeholder="Filter components" />
{ const key = node.dataRef.key; const isFolder = Array.isArray(node.dataRef.children); const eleText = ( {node.title} ); return isFolder ? (
{node.expanded ? : } {eleText}
) : ( onComponentChange(key)} > {eleText} ); }} />
); } ================================================ FILE: packages/ui-foundation-react/src/sideBar/componentMenu/index.ts ================================================ export { ComponentMenu } from './componentMenu'; export type { ComponentMenuProps } from './componentMenu'; ================================================ FILE: packages/ui-foundation-react/src/sideBar/index.ts ================================================ export { SideBar } from './sideBar'; export type { SideBarProps } from './sideBar'; ================================================ FILE: packages/ui-foundation-react/src/sideBar/sideBar.module.scss ================================================ .sideBar { height: 100%; width: 240px; padding: 12px; border-right: 1px solid var(--preview-color-border); overflow: auto; } ================================================ FILE: packages/ui-foundation-react/src/sideBar/sideBar.tsx ================================================ // eslint-disable-next-line import/no-extraneous-dependencies import React from 'react'; import cs from 'classnames'; import { ComponentMenu, ComponentMenuProps } from './componentMenu'; import styles from './sideBar.module.scss'; export interface SideBarProps { className?: string | string[]; componentMenuProps?: ComponentMenuProps; } export function SideBar({ className, componentMenuProps }: SideBarProps) { return (
); } ================================================ FILE: packages/ui-foundation-react/src/spin/index.ts ================================================ export { Spin } from '@arco-design/web-react'; export type { SpinProps } from '@arco-design/web-react'; ================================================ FILE: packages/ui-foundation-react/src/style/colors.scss ================================================ /** * Font size */ body { --preview-font-size-display-3: 56px; --preview-font-size-display-2: 48px; --preview-font-size-display-1: 36px; --preview-font-size-title-3: 24px; --preview-font-size-title-2: 20px; --preview-font-size-title-1: 16px; --preview-font-size-body-3: 14px; --preview-font-size-body-2: 13px; --preview-font-size-body-1: 12px; } /** * Colors */ body { --preview-color-success: rgb(0 180 42); --preview-color-warning: rgb(255 125 0); --preview-color-danger: rgb(245 63 63); --preview-color-primary: rgb(22 93 255); --preview-color-bg-1: #fff; --preview-color-bg-2: #fff; --preview-color-bg-3: #fff; --preview-color-bg-4: #fff; --preview-color-border: rgb(242 243 245); --preview-color-link: rgb(22 93 255); --preview-color-text-1: rgb(29 33 41); --preview-color-text-2: rgb(78 89 105); --preview-color-text-3: rgb(134 144 156); --preview-color-text-4: rgb(201 205 212); --preview-color-fill-1: rgb(247 248 250); --preview-color-fill-2: rgb(242 243 245); --preview-color-fill-3: rgb(229 230 235); --preview-color-fill-4: rgb(201 205 212); } body[arco-theme='dark'] { --preview-color-success: rgb(39 195 70); --preview-color-warning: rgb(251 233 75); --preview-color-danger: rgb(247 105 101); --preview-color-primary: rgb(60 126 255); --preview-color-bg-1: #17171a; --preview-color-bg-2: #232324; --preview-color-bg-3: #2a2a2b; --preview-color-bg-4: #313132; --preview-color-border: #333335; --preview-color-link: rgb(60 126 255); --preview-color-text-1: rgb(255 255 255 / 90%); --preview-color-text-2: rgb(255 255 255 / 70%); --preview-color-text-3: rgb(255 255 255 / 50%); --preview-color-text-4: rgb(255 255 255 / 30%); --preview-color-fill-1: rgb(255 255 255 / 4%); --preview-color-fill-2: rgb(255 255 255 / 8%); --preview-color-fill-3: rgb(255 255 255 / 12%); --preview-color-fill-4: rgb(255 255 255 / 16%); } ================================================ FILE: packages/ui-foundation-react/src/style/global.scss ================================================ @import '~@arco-design/web-react/dist/css/arco.css'; @import './colors'; html, body { * { box-sizing: border-box; } } ================================================ FILE: packages/ui-foundation-react/src/style/z-indexes.scss ================================================ // 5 < z < 20 - internal for components (if cannot be avoided) // 20 < z < 100 - internal for pages / sections // 100 < z < 1000 - navigation / hud $nav-z-index: 100; $navbar-z-index: 102; // 5000 < z < 6000 - modals and overlays $modal-z-index: 5000; // 15000 < 16000 - meta "always on top" items $highlighter-z-index: 15500; // theoretical max value is around // 32,768 (2^16) // and can be up to 1,073,741,824 (2^30), depending on the browser // (depending on the browser) ================================================ FILE: packages/ui-foundation-react/src/tabs/index.ts ================================================ export { Tabs } from '@arco-design/web-react'; export type { TabsProps } from '@arco-design/web-react'; ================================================ FILE: packages/ui-foundation-react/src/type/custom.d.ts ================================================ declare module '*.module.css' { const classes: { readonly [key: string]: string }; export default classes; } declare module '*.module.scss' { const classes: { readonly [key: string]: string }; export default classes; } declare module '*.module.sass' { const classes: { readonly [key: string]: string }; export default classes; } declare module '*.module.less' { const classes: { readonly [key: string]: string }; export default classes; } declare module '*.less' { const classes: { readonly [key: string]: string }; export default classes; } declare module '*.css' { const classes: { readonly [key: string]: string }; export default classes; } declare module '*.sass' { const classes: { readonly [key: string]: string }; export default classes; } declare module '*.scss' { const classes: { readonly [key: string]: string }; export default classes; } declare module '*.mdx' { const component: any; export default component; } declare module '*.svg' { const component: any; export default component; } ================================================ FILE: packages/ui-foundation-react/src/workspaceContext/index.ts ================================================ export { WorkspaceContext, WorkspaceContextProvider } from './workspaceContext'; ================================================ FILE: packages/ui-foundation-react/src/workspaceContext/workspaceContext.module.scss ================================================ .loader { position: fixed; left: 0; right: 0; top: 0; bottom: 0; z-index: 20; } ================================================ FILE: packages/ui-foundation-react/src/workspaceContext/workspaceContext.tsx ================================================ // eslint-disable-next-line import/no-extraneous-dependencies import React, { createContext, ReactNode, useEffect, useMemo, useState } from 'react'; import { Spin } from '@arco-design/web-react'; import { ComponentModel } from '@arco-cli/aspect/dist/component/uiRuntime'; import { LoaderContext, useLoaderApi } from '../globalLoader'; import { LOCAL_STORAGE_KEY_WORKSPACE_DARK_MODE } from '../constants'; import '../style/global.scss'; import styles from './workspaceContext.module.scss'; type WorkspaceContextType = { name: string; darkMode: boolean; setDarkMode: (dark: boolean) => void; components: ComponentModel[]; overviewScrollContainerID: string; }; export const WorkspaceContext = createContext({ name: '', darkMode: false, setDarkMode: () => {}, components: [], overviewScrollContainerID: '', }); export function WorkspaceContextProvider( props: { children: ReactNode; } & Pick ) { const { name, children, overviewScrollContainerID } = props; const [loaderApi, isLoading] = useLoaderApi(); const [darkMode, setDarkMode] = useState(() => { return +localStorage.getItem(LOCAL_STORAGE_KEY_WORKSPACE_DARK_MODE) > 0; }); const workspaceContextValue = useMemo(() => { return { name, darkMode, setDarkMode: (dark) => { setDarkMode(dark); localStorage.setItem(LOCAL_STORAGE_KEY_WORKSPACE_DARK_MODE, dark ? '1' : '0'); }, overviewScrollContainerID, components: props.components, }; }, [name, darkMode, setDarkMode, overviewScrollContainerID, JSON.stringify(props.components)]); useEffect(() => { darkMode ? document.body.setAttribute('arco-theme', 'dark') : document.body.removeAttribute('arco-theme'); }, [darkMode]); return ( {children} ); } ================================================ FILE: packages/ui-foundation-react/src/workspaceOverview/index.ts ================================================ export { WorkspaceOverview } from './workspaceOverview'; ================================================ FILE: packages/ui-foundation-react/src/workspaceOverview/workspaceOverview.module.scss ================================================ .workspaceOverview { $component-card-margin: 10px; display: flex; flex-wrap: wrap; min-height: 100%; margin: 0 (-$component-card-margin); .emptyPlaceholder { margin-top: 20vh; } .componentCard { position: relative; display: flex; flex-direction: column; width: calc(50% - $component-card-margin * 2); height: 244px; margin: $component-card-margin; padding: 24px; text-decoration: none; border: 1px solid var(--preview-color-border); border-radius: 8px; color: var(--preview-color-text-3); font-size: var(--preview-font-size-body-1); overflow: hidden; @media (min-width: 1000px) { width: calc(33.33% - $component-card-margin * 2); } @media (min-width: 1400px) { width: calc(25% - $component-card-margin * 2); } @media (min-width: 1800px) { width: calc(20% - $component-card-margin * 2); } &::before, &::after { content: ' '; position: absolute; bottom: -60px; right: -60px; width: 140px; height: 140px; border-radius: 100px; } &::before { right: 10px; background: #5addb4; opacity: 0.04; } &::after { background: #6aa1ff; opacity: 0.05; } &:hover { box-shadow: 0 0 6px var(--preview-color-fill-2); } .avatar { margin-bottom: 12px; } .title { display: flex; align-items: center; width: fit-content; max-width: 100%; margin-bottom: 8px; font-size: var(--preview-font-size-title-1); font-weight: 600; color: var(--preview-color-text-1); .titleText { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } :global(.arco-tag) { margin-left: 6px; } } .packageName { margin-bottom: 8px; font-size: var(--preview-font-size-body-1); font-weight: 500; line-height: 20px; color: var(--preview-color-text-2); } .description { flex: 1; overflow: hidden; text-overflow: ellipsis; margin: 0; color: var(--preview-color-text-3); } .labels { margin-top: 12px; :global(.arco-icon) { margin-right: 4px; } } } .empty { display: flex; align-items: center; } } ================================================ FILE: packages/ui-foundation-react/src/workspaceOverview/workspaceOverview.tsx ================================================ // eslint-disable-next-line import/no-extraneous-dependencies import React, { useContext } from 'react'; // eslint-disable-next-line import/no-extraneous-dependencies import { Link } from 'react-router-dom'; import cs from 'classnames'; import { Avatar, Empty, Tag, Typography } from '@arco-design/web-react'; import { IconTag } from '@arco-design/web-react/icon'; import { WorkspaceContext } from '../workspaceContext'; import styles from './workspaceOverview.module.scss'; const AVAILABLE_AVATAR_COLORS = ['#FF7D00', '#FADC19', '#9FDB1D', '#14C9C9', '#165DFF', '#722ED1']; export function WorkspaceOverview() { const { components } = useContext(WorkspaceContext); const isEmpty = !components?.length; return (
{isEmpty ? ( ) : ( components.map(({ id, name, packageName, version, author, description, labels }) => { const avatarText = author || 'Unknown'; return ( {avatarText}
{name} v{version}
{packageName}
{description}
{labels.join(' / ')}
); }) )}
); } ================================================ FILE: packages/ui-foundation-react/tsconfig.json ================================================ { "extends": "../../tsconfig.json", "include": ["src"], "compilerOptions": { "baseUrl": ".", "outDir": "dist", "paths": { "@arco-cli/stone": ["../stone/src"], "@arco-cli/legacy/dist/*": ["../legacy/src/*"], "@arco-cli/core/dist/*": ["../core/src/*"], "@arco-cli/aspect/dist/*": ["../aspect/src/*"], "@arco-cli/service/dist*": ["../service/src/*"], "@arco-cli/ui-foundation-react": ["../ui-foundation-react/src"], "@arco-cli/ui-foundation-react/dist/*": ["../ui-foundation-react/src/*"] } } } ================================================ FILE: pnpm-workspace.yaml ================================================ packages: - 'packages/*' - 'ui' - '!**/test/**' ================================================ FILE: tsconfig.json ================================================ { "compilerOptions": { "allowJs": true, "declaration": true, "sourceMap": true, "skipLibCheck": true, "skipDefaultLibCheck": true, "esModuleInterop": true, "noUnusedLocals": true, "noUnusedParameters": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, "allowSyntheticDefaultImports": true, "module": "CommonJS", "target": "es6", "moduleResolution": "node", "jsx": "react", "lib": ["DOM", "DOM.Iterable", "ES2020"], "resolveJsonModule": true }, "exclude": ["node_modules"] } ================================================ FILE: ui/.gitignore ================================================ es lib artifacts ================================================ FILE: ui/.scripts/workspaceHooks/afterComponentCreated.js ================================================ /* eslint-disable @typescript-eslint/no-var-requires */ const fs = require('fs'); const path = require('path'); module.exports = function (componentInfo) { const { path: pathComponentEntry, modules } = componentInfo || {}; let pathLibraryEntry = path.resolve(pathComponentEntry, '../index.ts'); if (!fs.existsSync(pathLibraryEntry)) { pathLibraryEntry = path.resolve(pathLibraryEntry, '../index.tsx'); } if (fs.existsSync(pathLibraryEntry) && fs.existsSync(pathComponentEntry)) { const libraryEntryContent = fs.readFileSync(pathLibraryEntry).toString(); const exportExpressions = modules .map( ({ name, type }) => `export${type ? ' type' : ''} { ${name} } from '${path .relative(path.dirname(pathLibraryEntry), pathComponentEntry) .replace(/^[^.]/, (match) => `./${match}`)}';` ) .join('\n'); if (libraryEntryContent.indexOf(exportExpressions) === -1) { fs.writeFileSync(pathLibraryEntry, `${libraryEntryContent}\n${exportExpressions}\n`); } } }; ================================================ FILE: ui/arco.env.config.js ================================================ /* eslint-disable @typescript-eslint/no-var-requires */ const fs = require('fs'); const path = require('path'); module.exports = function defineConfig() { const webpackConfigTransformer = (config) => { const mdxLoader = config.raw.module.rules .find((rule) => rule.oneOf) ?.oneOf?.find((rule) => rule.test.test('.mdx')) ?.use?.find(({ loader }) => loader.indexOf('/mdx/loader') > -1); if (mdxLoader) { mdxLoader.options.preProcessFile = ({ path: filePath, content }) => { const componentStyleEntry = '../style/index.ts'; if (fs.existsSync(path.resolve(path.dirname(filePath), componentStyleEntry))) { return `${content}\nimport '${componentStyleEntry}';`; } return content; }; } return config.merge({ resolve: { alias: { react: require.resolve('react'), }, }, }); }; const config = { jest: { jestConfigPath: path.resolve(__dirname, './jest.config.js'), }, webpack: { previewConfig: [webpackConfigTransformer], devServerConfig: [webpackConfigTransformer], }, typescript: { buildConfig: [ (config) => { config.mergeTsConfig({}); return config; }, ], }, less: { lessOptions: {}, }, sass: { sassOptions: {}, }, }; return config; }; ================================================ FILE: ui/arco.workspace.jsonc ================================================ { "arco.aspect/workspace": { "name": "Arco Workspace UI", "components": { "extends": { "rootDir": "src", "entries": { "main": "index.ts", "style": "style/index.ts", "preview": "__docs__/index.mdx", "jsdoc": [ "interface.ts" ] } }, "members": [ { "name": "Overview", "entries": { "base": "Overview" } } ] } }, "arco.service/generator": { "defaultPath": "src", "hooks": { "afterComponentCreated": "./.scripts/workspaceHooks/afterComponentCreated.js" } }, "arco.service/compiler": { "skipDeleteDistDir": true } } ================================================ FILE: ui/jest.config.js ================================================ /* eslint-disable @typescript-eslint/no-var-requires */ // allow IDE to configure Jest config const defaultConfig = require(require.resolve('@arco-cli/react/dist/jest/jest.cjs.config.js')); module.exports = { ...defaultConfig, }; ================================================ FILE: ui/package.json ================================================ { "name": "@arco-cli/workspace-materials", "version": "0.7.0", "description": "", "main": "./lib/index.js", "module": "./es/index.js", "types": "./es/index.d.ts", "scripts": { "arco": "node ../packages/arco/dist/app.js", "start": "pnpm arco start", "build": "pnpm arco build", "build:esm": "pnpm arco build --tasks=\"TSCompilerESM\"", "sync": "pnpm arco sync", "clean": "rm -rf es lib artifacts", "prepublishOnly": "npm run build" }, "peerDependencies": { "@arco-design/web-react": "~2", "react": ">=16" }, "dependencies": { "classnames": "^2.3.2", "lodash-es": "^4.17.21", "penpal": "^6.2.2" }, "devDependencies": { "@testing-library/jest-dom": "^5.16.5" }, "sideEffects": [ "{es,lib,src}/**/style/*", "*.less" ], "files": [ "es", "lib" ], "license": "MIT" } ================================================ FILE: ui/src/Overview/Overview.tsx ================================================ import cs from 'classnames'; import debounce from 'lodash-es/debounce'; import React, { forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState, } from 'react'; import { Spin } from '@arco-design/web-react'; import { IconFullscreen, IconFullscreenExit, IconShareInternal } from '@arco-design/web-react/icon'; import { OverviewProps, OverviewHandle } from './interface'; import { useConnectIframe } from '../utils/useConnectIframe'; import { findNode } from '../utils/findNode'; import { on, off } from '../utils/dom'; // @ts-ignore import styles from './style/index.module.less'; import { PREVIEW_IFRAME_GLOBAL_VARIABLES_KEY, PUBSUB_TOPIC_PARENT_TO_CHILD, } from '../utils/constant'; const enum IFRAME_VALID_MESSAGE_TYPE { updateAnchorOffset = 'updateAnchorOffset', appendExtraStyle = 'appendExtraStyle', switchDarkMode = 'switchDarkMode', scrollIntoView = 'scrollIntoView', switchActiveTab = 'switchActiveTab', } function getContainer(targetContainer?: string | HTMLElement | Window) { if (typeof targetContainer === 'string') { return findNode(document, targetContainer); } return targetContainer || window; } function canAccessIFrame(iframe: HTMLIFrameElement) { let html = null; try { // deal with older browsers const doc = iframe.contentDocument || iframe.contentWindow.document; html = doc.body.innerHTML; } catch (err) {} return html !== null; } export const Overview = forwardRef(function (props: OverviewProps, ref) { const { style, className, src, iframe, extraStyle, darkMode, scrollContainer, scrollContainerOffset = 0, timeout = 15000, spinProps, onReady, onTimeout, onIframeLoad, onIframeError, onIframeLocationHashChange, onIframeActiveTabChange, } = props; const refIframe = useRef(null); const refScrollContainer = useRef(null); const refLoadingTimer = useRef(null); const [iframeLoadTimes, setIframeLoadTimes] = useState(0); const [iframeFullscreen, setIframeFullscreen] = useState(false); const { height, locationHash, activeTab, connection } = useConnectIframe(refIframe); const isLoading = !height; useEffect(() => { if (!isLoading) { onReady?.(); clearTimeout(refLoadingTimer.current); } }, [isLoading]); useEffect(() => { if (!isLoading) { onIframeLocationHashChange?.(locationHash); } }, [locationHash]); useEffect(() => { if (!isLoading) { onIframeActiveTabChange?.(activeTab); } }, [activeTab]); const operateIframe = useCallback( ({ type, data }: { type: IFRAME_VALID_MESSAGE_TYPE; data: Record }) => { if (!refIframe.current) return; const contentWindow = canAccessIFrame(refIframe.current) ? refIframe.current.contentWindow : null; // compatible with preview logic before 2.1.0, directly operate iframe DOM nodes if ( contentWindow && !(contentWindow as any)[PREVIEW_IFRAME_GLOBAL_VARIABLES_KEY]?.parentMessageIsSubscribed ) { switch (type) { case IFRAME_VALID_MESSAGE_TYPE.updateAnchorOffset: { const updateAnchorOffset = (contentWindow as any).__arcoPreviewMethods ?.updateAnchorOffset; if (typeof updateAnchorOffset === 'function') { try { updateAnchorOffset(data.offset); } catch (err) { console.warn(`Failed to update anchor position in component preview page, details: ${err.toString()}`); } } break; } case IFRAME_VALID_MESSAGE_TYPE.appendExtraStyle: { const eleClassName = '__arco-component-extra-style'; // clear all append styles at first contentWindow.document .querySelectorAll(`.${eleClassName}`) .forEach((node) => contentWindow.document.body.removeChild(node)); if (data.href) { const styleEle = document.createElement('link'); styleEle.setAttribute('class', eleClassName); styleEle.setAttribute('type', 'text/css'); styleEle.setAttribute('rel', 'stylesheet'); styleEle.setAttribute('href', data.href); contentWindow.document.body?.prepend(styleEle); } break; } case IFRAME_VALID_MESSAGE_TYPE.switchDarkMode: { const body = contentWindow.document.body; data.dark ? body.setAttribute('arco-theme', 'dark') : body.removeAttribute('arco-theme'); break; } case IFRAME_VALID_MESSAGE_TYPE.scrollIntoView: { try { contentWindow.document.body ?.querySelector(data.selector) ?.scrollIntoView(data.options); } catch (err) {} break; } default: break; } } else if (connection) { connection.pub(PUBSUB_TOPIC_PARENT_TO_CHILD, { type, data }); } }, [connection] ); const scrollHandler = useCallback( debounce((event) => { if (connection) { const scrollTop = (event.target?.scrollTop || 0) - scrollContainerOffset; const offset = Math.max(0, scrollTop); operateIframe({ type: IFRAME_VALID_MESSAGE_TYPE.updateAnchorOffset, data: { offset }, }); } }, 200), [scrollContainerOffset, operateIframe] ); useEffect(() => { refScrollContainer.current = getContainer(scrollContainer); on(refScrollContainer.current, 'scroll', scrollHandler); return () => { off(refScrollContainer.current, 'scroll', scrollHandler); }; }, [scrollContainer, scrollHandler]); useEffect(() => { if (iframeLoadTimes > 0 && extraStyle) { operateIframe({ type: IFRAME_VALID_MESSAGE_TYPE.appendExtraStyle, data: { href: extraStyle }, }); } }, [iframeLoadTimes, extraStyle, operateIframe]); useEffect(() => { if (iframeLoadTimes > 0) { operateIframe({ type: IFRAME_VALID_MESSAGE_TYPE.switchDarkMode, data: { dark: darkMode }, }); } }, [iframeLoadTimes, darkMode, operateIframe]); useImperativeHandle( ref, () => { return { scrollIntoView: (selector: string, options: any) => { operateIframe({ type: IFRAME_VALID_MESSAGE_TYPE.scrollIntoView, data: { selector, options }, }); }, updateMDXPreviewActiveTab: (tab: string) => { operateIframe({ type: IFRAME_VALID_MESSAGE_TYPE.switchActiveTab, data: { tab }, }); }, }; }, [operateIframe] ); return (
window.open(src, '_blank')} >
setIframeFullscreen(!iframeFullscreen)} > {iframeFullscreen ? : }