├── .changeset ├── README.md └── config.json ├── .eslintignore ├── .eslintrc.js ├── .github └── workflows │ ├── ci.yml │ └── publish.yml ├── .gitignore ├── .husky └── pre-commit ├── .nvmrc ├── .prettierignore ├── .prettierrc ├── .vscode ├── extensions.json ├── launch.json ├── settings.json └── tasks.json ├── README.md ├── apps ├── cli │ ├── .gitignore │ ├── CHANGELOG.md │ ├── jest.config.js │ ├── package.json │ ├── readme.md │ ├── src │ │ ├── __tests__ │ │ │ ├── __examples__ │ │ │ │ ├── .gitignore │ │ │ │ ├── action-backtick.ts │ │ │ │ ├── action-double-quote.ts │ │ │ │ ├── action-single-quote.ts │ │ │ │ ├── after.ts │ │ │ │ ├── basic.ts │ │ │ │ ├── bug-report-1.ts │ │ │ │ ├── delay-backtick.ts │ │ │ │ ├── delay-double-quote.ts │ │ │ │ ├── delay-single-quote.ts │ │ │ │ ├── event-backtick.ts │ │ │ │ ├── event-double-quote.ts │ │ │ │ ├── event-single-quote.ts │ │ │ │ ├── functionsInOptions.ts │ │ │ │ ├── guard-backtick.ts │ │ │ │ ├── guard-double-quote.ts │ │ │ │ ├── guard-single-quote.ts │ │ │ │ ├── multi.ts │ │ │ │ ├── service-backtick.ts │ │ │ │ ├── service-double-quote.ts │ │ │ │ ├── service-single-quote.ts │ │ │ │ ├── state-name-double-quote-compound.ts │ │ │ │ ├── state-name-double-quote-leaf.ts │ │ │ │ ├── state-name-double-quote.ts │ │ │ │ ├── state-name-single-quote-compound.ts │ │ │ │ ├── state-name-single-quote-leaf.ts │ │ │ │ ├── state-name-single-quote.ts │ │ │ │ ├── tag-backtick.ts │ │ │ │ ├── tag-double-quote.ts │ │ │ │ ├── tag-single-quote.ts │ │ │ │ ├── tsconfig.json │ │ │ │ └── type-safe-services.ts │ │ │ ├── __sky__ │ │ │ │ ├── .gitignore │ │ │ │ ├── actorFromStately.sky.ts │ │ │ │ ├── actorFromStately.ts │ │ │ │ ├── machineFromStately.sky.ts │ │ │ │ ├── machineFromStately.ts │ │ │ │ └── tsconfig.json │ │ │ ├── examples.test.ts │ │ │ └── sky.test.ts │ │ ├── bin.ts │ │ ├── sky │ │ │ ├── urlUtils.ts │ │ │ ├── writeConfigToFiles.ts │ │ │ └── writeToFetchedMachineFile.ts │ │ ├── typegen │ │ │ └── writeToFiles.ts │ │ └── utils.ts │ └── tsconfig.json └── extension │ ├── client │ ├── .vscodeignore │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── bundled-editor │ │ ├── Inter-italic.var.woff2 │ │ ├── Inter-roman.var.woff2 │ │ ├── assets │ │ │ └── index.9b3967c9.js │ │ └── index.html │ ├── jest.config.js │ ├── media │ │ └── icon.png │ ├── package.json │ ├── snippets │ │ └── xstate.code-snippets │ ├── src │ │ ├── checkTypegenNesting.ts │ │ ├── constants.ts │ │ ├── extension.ts │ │ ├── getWebviewContent.ts │ │ ├── initiateEditor.ts │ │ ├── initiateEditor.typegen.ts │ │ ├── initiateTypegen.ts │ │ ├── registerDisposable.ts │ │ ├── typeSafeLanguageClient.ts │ │ └── typeSafeVsCode.ts │ ├── testFixture │ │ ├── completion.txt │ │ ├── diagnostics.txt │ │ └── machine.ts │ └── tsconfig.json │ └── server │ ├── package.json │ ├── src │ ├── connection.ts │ ├── diagnostics │ │ ├── getMetaWarnings.ts │ │ ├── getTypegenGenericWarnings.ts │ │ ├── getUnusedActionImplementations.ts │ │ ├── getUnusedGuardImplementations.ts │ │ ├── getUnusedServicesImplementations.ts │ │ └── misc.ts │ ├── getCursorHoverType.ts │ ├── getDiagnostics.ts │ ├── getReferences.ts │ ├── log.ts │ ├── server.ts │ ├── typeSafeConnection.ts │ ├── types.ts │ └── utils.ts │ └── tsconfig.json ├── assets ├── autocomplete.png ├── editor.png ├── jump-to-definition.png ├── linting.png ├── typegenPrompt.png └── visualization.png ├── babel.config.js ├── jest.config.js ├── package.json ├── packages ├── machine-extractor │ ├── .gitignore │ ├── .npmignore │ ├── CHANGELOG.md │ ├── README.md │ ├── jest.config.js │ ├── package.json │ ├── sky │ │ └── package.json │ ├── src │ │ ├── MachineExtractResult.ts │ │ ├── RecordOfArrays.ts │ │ ├── __tests__ │ │ │ ├── actions.test.ts │ │ │ ├── after.test.ts │ │ │ ├── choose.test.ts │ │ │ ├── cond.test.ts │ │ │ ├── context.test.ts │ │ │ ├── createTestMachine.test.ts │ │ │ ├── delays.test.ts │ │ │ ├── enums.test.ts │ │ │ ├── ignore.test.ts │ │ │ ├── inline-implementations.test.ts │ │ │ ├── invoke.test.ts │ │ │ ├── layout-comment.test.ts │ │ │ ├── modifications │ │ │ │ ├── add_action.test.ts │ │ │ │ ├── add_guard.test.ts │ │ │ │ ├── add_guard_v5.test.ts │ │ │ │ ├── add_invoke.test.ts │ │ │ │ ├── add_state.test.ts │ │ │ │ ├── add_transition.test.ts │ │ │ │ ├── add_transition_v5.test.ts │ │ │ │ ├── batched.test.ts │ │ │ │ ├── change_transition_path.test.ts │ │ │ │ ├── edit_action.test.ts │ │ │ │ ├── edit_guard.test.ts │ │ │ │ ├── edit_guard_v5.test.ts │ │ │ │ ├── edit_invoke.test.ts │ │ │ │ ├── mark_transition_as_external.test.ts │ │ │ │ ├── mark_transition_as_external_v5.test.ts │ │ │ │ ├── reanchor_transition.test.ts │ │ │ │ ├── remove_action.test.ts │ │ │ │ ├── remove_guard.test.ts │ │ │ │ ├── remove_guard_v5.test.ts │ │ │ │ ├── remove_invoke.test.ts │ │ │ │ ├── remove_state.test.ts │ │ │ │ ├── remove_transition.test.ts │ │ │ │ ├── rename_state.test.ts │ │ │ │ ├── reparent_state.test.ts │ │ │ │ ├── set_description.test.ts │ │ │ │ ├── set_initial_state.test.ts │ │ │ │ ├── set_state_id.test.ts │ │ │ │ └── set_state_type.test.ts │ │ │ ├── options.test.ts │ │ │ ├── parseResult.test.ts │ │ │ ├── transitions.test.ts │ │ │ ├── typeParameters.test.ts │ │ │ ├── unparseable.test.ts │ │ │ └── validation.test.ts │ │ ├── actions.ts │ │ ├── conds.ts │ │ ├── constants.ts │ │ ├── context.ts │ │ ├── createParser.ts │ │ ├── extractAction.ts │ │ ├── extractMachinesFromFile.ts │ │ ├── getMachineExtractResult.ts │ │ ├── getMachineNodesFromFile.ts │ │ ├── getMachineOptions.ts │ │ ├── groupByUniqueName.ts │ │ ├── history.ts │ │ ├── identifiers.ts │ │ ├── index.ts │ │ ├── invoke.ts │ │ ├── machineCallExpression.ts │ │ ├── meta.ts │ │ ├── namedActions.ts │ │ ├── options.ts │ │ ├── scalars.ts │ │ ├── schema.ts │ │ ├── sky │ │ │ ├── index.ts │ │ │ ├── skyConfigCallExpression.ts │ │ │ ├── skyConfigExtractFromFile.ts │ │ │ ├── skyConfigExtractResult.ts │ │ │ ├── skyConfigModifySource.ts │ │ │ ├── skyConfigNode.ts │ │ │ ├── skyConfigUtils.ts │ │ │ └── skyEnvFileUtils.ts │ │ ├── stateNode.ts │ │ ├── testUtils.ts │ │ ├── toMachineConfig.ts │ │ ├── transitions.ts │ │ ├── tsAsExpression.ts │ │ ├── tsTypes.ts │ │ ├── typeParameters.ts │ │ ├── types.ts │ │ ├── unionType.ts │ │ ├── utils.ts │ │ └── wrapParserResult.ts │ └── tsconfig.json └── shared │ ├── .gitignore │ ├── CHANGELOG.md │ ├── forEachAction │ └── package.json │ ├── forEachEntity │ └── package.json │ ├── jest.config.js │ ├── package.json │ ├── src │ ├── __tests__ │ │ ├── __examples__ │ │ │ ├── action-missing.ts │ │ │ ├── action-provided-function.ts │ │ │ ├── actor-ondone-multiple-actions.ts │ │ │ ├── actor-ondone-single-action.ts │ │ │ ├── actor-onerror-multiple-actions.ts │ │ │ ├── actor-onerror-single-action.ts │ │ │ ├── after-numeric-like-delay.ts │ │ │ ├── after-string-delay.ts │ │ │ ├── choose-defined-in-config.ts │ │ │ ├── choose-defined-in-implementations.ts │ │ │ ├── entry-action-on-ancestor-of-targeted-descendant.ts │ │ │ ├── entry-action-on-initial-state-of-targeted-ancestor.ts │ │ │ ├── entry-action-on-state-between-lca-and-target.ts │ │ │ ├── entry-action-root-external-transition.ts │ │ │ ├── entry-actions-basic.ts │ │ │ ├── entry-actions-named-mixed-with-inline.ts │ │ │ ├── entry-actions-receive-init-event.ts │ │ │ ├── entry-actions-within-targeted-parallel.ts │ │ │ ├── exit-action-external-transition-out-of-source.ts │ │ │ ├── exit-action-external-transition-within-source.ts │ │ │ ├── exit-action-internal-transition.ts │ │ │ ├── exit-action-on-ancestor-with-external-transition-on-descendant.ts │ │ │ ├── exit-action-on-state-between-lca-and-source-state.ts │ │ │ ├── exit-action-parallel-root-event-only-within-final-configuration.ts │ │ │ ├── exit-action-parallel-root-with-final-states.ts │ │ │ ├── exit-action-parallel-root-with-missing-final-state.ts │ │ │ ├── exit-action-parallel-root-with-mixed-states.ts │ │ │ ├── exit-action-parallel-root-with-parallel-states.ts │ │ │ ├── exit-action-root-external-transition.ts │ │ │ ├── exit-action-root-final-state.ts │ │ │ ├── exit-action-root-multiple-final-states.ts │ │ │ ├── exit-action-within-ancestor-with-external-transition-outside-of-source.ts │ │ │ ├── exit-action-within-ancestor-with-internal-transition.ts │ │ │ ├── guard-provided-function.ts │ │ │ ├── inline-action-function-expression.ts │ │ │ ├── inline-action-identifier.ts │ │ │ ├── inline-actor-with-id.ts │ │ │ ├── inline-guard-function-expression.ts │ │ │ ├── inline-guard-identifier.ts │ │ │ ├── inline-service-done-event-causing-actions.ts │ │ │ ├── inline-service-src-function-expression.ts │ │ │ ├── inline-service-src-identifier.ts │ │ │ ├── invoke-within-targeted-parallel.ts │ │ │ ├── parametrized-guard-in-always-transition.ts │ │ │ ├── parametrized-guard-in-transition.ts │ │ │ └── service-provided-function.ts │ │ ├── __snapshots__ │ │ │ └── getTypegenOutput.test.ts.snap │ │ ├── forEachEntity.test.ts │ │ ├── getTypegenOutput.test.ts │ │ └── processFileEdits.test.ts │ ├── createIntrospectableMachine.ts │ ├── filterOutIgnoredMachines.ts │ ├── forEachEntity.ts │ ├── getInlineImplementations.ts │ ├── getRangeFromSourceLocation.ts │ ├── getRawTextFromNode.ts │ ├── getSetOfNames.ts │ ├── getStateMatchesObjectSyntax.ts │ ├── getTransitionsFromNode.ts │ ├── getTsTypesEdits.ts │ ├── getTypegenData.ts │ ├── getTypegenOutput.ts │ ├── index.ts │ ├── introspectMachine.ts │ ├── isCursorInPosition.ts │ ├── processFileEdits.ts │ ├── resolveUriToFilePrefix.ts │ └── types.ts │ └── tsconfig.json ├── patches └── ast-types+0.16.1.patch ├── scripts ├── e2e.sh ├── require-minified-bundled-editor.js └── tag-extension.js ├── tsconfig.json └── yarn.lock /.changeset/README.md: -------------------------------------------------------------------------------- 1 | # Changesets 2 | 3 | Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works 4 | with multi-package repos, or single-package repos to help you version and publish your code. You can 5 | find the full documentation for it [in our repository](https://github.com/changesets/changesets) 6 | 7 | We have a quick list of common questions to get you started engaging with this project in 8 | [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) 9 | -------------------------------------------------------------------------------- /.changeset/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/@changesets/config@1.7.0/schema.json", 3 | "commit": false, 4 | "fixed": [], 5 | "linked": [], 6 | "access": "public", 7 | "baseBranch": "main", 8 | "updateInternalDependencies": "patch", 9 | "ignore": [], 10 | "___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH": { 11 | "updateInternalDependents": "always" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/** 2 | apps/extension/client/node_modules/** 3 | apps/extension/client/out/** 4 | apps/extension/server/node_modules/** 5 | apps/extension/server/out/** -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | /**@type {import('eslint').Linter.Config} */ 2 | // eslint-disable-next-line no-undef 3 | module.exports = { 4 | // root: true, 5 | // parser: '@typescript-eslint/parser', 6 | // plugins: [ 7 | // '@typescript-eslint', 8 | // ], 9 | // extends: [ 10 | // 'eslint:recommended', 11 | // 'plugin:@typescript-eslint/recommended', 12 | // ], 13 | // rules: { 14 | // 'semi': [2, "always"], 15 | // '@typescript-eslint/no-unused-vars': 0, 16 | // '@typescript-eslint/no-explicit-any': 0, 17 | // '@typescript-eslint/explicit-module-boundary-types': 0, 18 | // '@typescript-eslint/no-non-null-assertion': 0, 19 | // } 20 | }; 21 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | push: 4 | branches: 5 | - '**' 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v3 12 | - uses: actions/setup-node@v3 13 | with: 14 | node-version: 18.x 15 | cache: 'yarn' 16 | 17 | - run: yarn 18 | - run: yarn deps:build 19 | - run: yarn turbo run build 20 | - run: yarn vscode:dev 21 | - run: yarn turbo run lint test 22 | - run: (cd apps/extension/client && npx vsce package --yarn) 23 | - uses: actions/upload-artifact@v3 24 | with: 25 | name: xstate-tools 26 | path: 'apps/extension/client/*.vsix' 27 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish 2 | on: 3 | push: 4 | branches: 5 | - 'main' 6 | 7 | concurrency: ${{ github.workflow }}-${{ github.ref }} 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v3 14 | - uses: actions/setup-node@v3 15 | with: 16 | node-version: 18.x 17 | cache: 'yarn' 18 | 19 | - run: yarn 20 | - run: yarn deps:build 21 | - run: yarn turbo run build 22 | - run: yarn vscode:prod 23 | - name: Create Release Pull Request or Publish 24 | id: changesets 25 | uses: changesets/action@v1 26 | with: 27 | publish: yarn release 28 | env: 29 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 30 | VSCE_PAT: ${{ secrets.VSCE_PAT }} 31 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 32 | OVSX_PAT: ${{ secrets.OVSX_PAT }} 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | out 2 | dist 3 | node_modules 4 | apps/extension/client/server 5 | apps/extension/client/scripts 6 | .vscode-test 7 | *.vsix 8 | *.zip 9 | *.log 10 | .DS_Store 11 | .turbo 12 | .idea 13 | .env 14 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | yarn lint-staged 5 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | v16.15.0 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | apps/extension/client/scripts 2 | dist 3 | bundled-editor 4 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all", 4 | "tabWidth": 2, 5 | "printWidth": 80 6 | } 7 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations. 3 | // Extension identifier format: ${publisher}.${name}. Example: vscode.csharp 4 | 5 | // List of extensions which should be recommended for users of this workspace. 6 | "recommendations": [ 7 | // The ESLint extension will help highlight errors in your code 8 | "dbaeumer.vscode-eslint", 9 | 10 | // We all love prettier for code formatting 11 | "esbenp.prettier-vscode", 12 | 13 | // This extension highlights TODO's 14 | "wayou.vscode-todo-highlight", 15 | 16 | // This extension highlights colors in your code 17 | "naumovs.color-highlight", 18 | 19 | // This makes it very easy to find updates to package.json 20 | "pflannery.vscode-versionlens", 21 | 22 | // Handy inline git details and much more 23 | "eamodio.gitlens", 24 | 25 | // Pretty TypeScript errors 26 | "yoavbls.pretty-ts-errors" 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | // A launch configuration that compiles the extension and then opens it inside a new window 2 | { 3 | "version": "0.2.0", 4 | "configurations": [ 5 | { 6 | "type": "node", 7 | "request": "launch", 8 | "name": "Jest Current File", 9 | "program": "${workspaceFolder}/node_modules/.bin/jest", 10 | "args": ["${file}", "--no-cache"], 11 | "console": "integratedTerminal", 12 | "internalConsoleOptions": "neverOpen", 13 | "disableOptimisticBPs": true, 14 | "windows": { 15 | "program": "${workspaceFolder}/node_modules/jest/bin/jest" 16 | } 17 | }, 18 | { 19 | "type": "extensionHost", 20 | "request": "launch", 21 | "name": "Launch Client", 22 | "runtimeExecutable": "${execPath}", 23 | "args": [ 24 | "--extensionDevelopmentPath=${workspaceRoot}/apps/extension/client" 25 | ], 26 | "outFiles": ["${workspaceRoot}/apps/extension/client/dist/**.js"], 27 | "preLaunchTask": "npm: vscode:dev" 28 | }, 29 | { 30 | "type": "node", 31 | "request": "attach", 32 | "name": "Attach to Server", 33 | "port": 6009, 34 | "restart": true, 35 | "outFiles": ["${workspaceRoot}/apps/extension/server/dist/**.js"] 36 | }, 37 | { 38 | "name": "Language Server E2E Test", 39 | "type": "extensionHost", 40 | "request": "launch", 41 | "runtimeExecutable": "${execPath}", 42 | "args": [ 43 | "--extensionDevelopmentPath=${workspaceRoot}/apps/extension/client", 44 | "--extensionTestsPath=${workspaceRoot}/apps/extension/client/out/test/index", 45 | "${workspaceRoot}apps/extension/client/testFixture" 46 | ], 47 | "outFiles": ["${workspaceRoot}/apps/extension/client/dist/**.js"] 48 | } 49 | ], 50 | "compounds": [ 51 | { 52 | "name": "Client + Server", 53 | "configurations": ["Launch Client", "Attach to Server"] 54 | } 55 | ] 56 | } 57 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.insertSpaces": false, 3 | "editor.formatOnSave": true, 4 | "editor.codeActionsOnSave": { 5 | "source.fixAll.eslint": true, 6 | "source.sortImports": true 7 | }, 8 | "npm.packageManager": "yarn", 9 | "typescript.tsdk": "node_modules/typescript/lib" 10 | } 11 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "type": "npm", 6 | "script": "compile", 7 | "group": "build", 8 | "presentation": { 9 | "panel": "dedicated", 10 | "reveal": "never" 11 | }, 12 | "problemMatcher": ["$tsc"] 13 | }, 14 | { 15 | "type": "npm", 16 | "script": "watch", 17 | "isBackground": true, 18 | "group": { 19 | "kind": "build", 20 | "isDefault": true 21 | }, 22 | "presentation": { 23 | "panel": "dedicated", 24 | "reveal": "never" 25 | }, 26 | "problemMatcher": ["$tsc-watch"] 27 | } 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # XState Tools 2 | 3 | Public monorepo for XState tooling, such as the [XState VS Code extension](https://marketplace.visualstudio.com/items?itemName=statelyai.stately-vscode) 4 | -------------------------------------------------------------------------------- /apps/cli/.gitignore: -------------------------------------------------------------------------------- 1 | bin 2 | node_modules 3 | *.local.* -------------------------------------------------------------------------------- /apps/cli/jest.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('@jest/types').Config.InitialOptions} */ 2 | module.exports = { 3 | testMatch: ['**/?(*.)+(spec|test).[jt]s?(x)'], 4 | watchPathIgnorePatterns: [ 5 | '/src/__tests__/__examples__/*.typegen.ts', 6 | ], 7 | watchPlugins: [ 8 | 'jest-watch-typeahead/filename', 9 | 'jest-watch-typeahead/testname', 10 | ], 11 | snapshotFormat: { 12 | printBasicPrototype: false, 13 | escapeString: false, 14 | }, 15 | transform: { 16 | '^.+\\.tsx?$': ['esbuild-jest', { sourcemap: true }], 17 | }, 18 | }; 19 | -------------------------------------------------------------------------------- /apps/cli/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@xstate/cli", 3 | "version": "0.5.17", 4 | "bin": { 5 | "xstate": "./bin/bin.js" 6 | }, 7 | "scripts": { 8 | "prepare": "yarn build", 9 | "lint": "tsc", 10 | "test": "jest", 11 | "build": "esbuild src/bin.ts --outdir=bin --platform=node --format=cjs --bundle --loader:.node=file" 12 | }, 13 | "devDependencies": { 14 | "@statelyai/sky": "0.0.10", 15 | "@types/isomorphic-fetch": "^0.0.36", 16 | "@types/jest": "^27.4.0", 17 | "@types/node": "^16.0.1", 18 | "esbuild": "^0.14.48", 19 | "jest": "^27.4.7" 20 | }, 21 | "dependencies": { 22 | "@babel/core": "^7.21.4", 23 | "@xstate/machine-extractor": "^0.16.0", 24 | "@xstate/tools-shared": "^4.1.0", 25 | "chokidar": "^3.5.3", 26 | "commander": "^8.0.0", 27 | "dotenv": "^16.0.3", 28 | "isomorphic-fetch": "^3.0.0", 29 | "prettier": "^2.8.8", 30 | "xstate": "^4.33.4", 31 | "xstate-beta": "npm:xstate@beta" 32 | }, 33 | "author": "Stately Team", 34 | "license": "MIT" 35 | } 36 | -------------------------------------------------------------------------------- /apps/cli/readme.md: -------------------------------------------------------------------------------- 1 | ## Commands 2 | 3 | ### xstate typegen 4 | 5 | `xstate typegen "src/**/*.ts?(x)"` 6 | 7 | Run the typegen against a glob. This will scan every file targeted, and generate a typegen file accompanying it. It will also import the typegen into your file, as described in [our typegen documentation](https://xstate.js.org/docs/guides/typescript.html#typegen-with-the-vscode-extension). 8 | 9 | #### Options 10 | 11 | `--watch` 12 | 13 | Runs the task on a watch, monitoring for changed files and running the typegen script against them. 14 | 15 | #### Experimental: xstate sky 16 | 17 | `xstate sky "src/**/*.ts?(x)"` 18 | 19 | This will scan through the source files and update any Stately Sky-compatible code. 20 | -------------------------------------------------------------------------------- /apps/cli/src/__tests__/__examples__/.gitignore: -------------------------------------------------------------------------------- 1 | *.typegen.ts -------------------------------------------------------------------------------- /apps/cli/src/__tests__/__examples__/action-backtick.ts: -------------------------------------------------------------------------------- 1 | import { createMachine } from 'xstate'; 2 | 3 | createMachine({ 4 | tsTypes: {} as import('./action-backtick.typegen').Typegen0, 5 | schema: { 6 | events: {} as { type: 'PING' }, 7 | }, 8 | on: { 9 | PING: { 10 | actions: 'log `event`', 11 | }, 12 | }, 13 | }); 14 | -------------------------------------------------------------------------------- /apps/cli/src/__tests__/__examples__/action-double-quote.ts: -------------------------------------------------------------------------------- 1 | import { createMachine } from 'xstate'; 2 | 3 | createMachine({ 4 | tsTypes: {} as import('./action-double-quote.typegen').Typegen0, 5 | schema: { 6 | events: {} as { type: 'PING' }, 7 | }, 8 | on: { 9 | PING: { 10 | actions: 'show me your "tricks"', 11 | }, 12 | }, 13 | }); 14 | -------------------------------------------------------------------------------- /apps/cli/src/__tests__/__examples__/action-single-quote.ts: -------------------------------------------------------------------------------- 1 | import { createMachine } from 'xstate'; 2 | 3 | createMachine({ 4 | tsTypes: {} as import('./action-single-quote.typegen').Typegen0, 5 | schema: { 6 | events: {} as { type: 'PING' }, 7 | }, 8 | on: { 9 | PING: { 10 | actions: "say 'We surely shall see the sun shine soon'", 11 | }, 12 | }, 13 | }); 14 | -------------------------------------------------------------------------------- /apps/cli/src/__tests__/__examples__/after.ts: -------------------------------------------------------------------------------- 1 | import { createMachine } from 'xstate'; 2 | 3 | createMachine({ 4 | tsTypes: {} as import('./after.typegen').Typegen0, 5 | after: { 6 | 500: { 7 | actions: 'sayHello', 8 | }, 9 | }, 10 | }).withConfig({ 11 | // xstate.cancel and xstate.send should not be here 12 | actions: { 13 | sayHello: () => {}, 14 | }, 15 | }); 16 | -------------------------------------------------------------------------------- /apps/cli/src/__tests__/__examples__/basic.ts: -------------------------------------------------------------------------------- 1 | import { createMachine } from 'xstate'; 2 | 3 | createMachine( 4 | { 5 | tsTypes: {} as import('./basic.typegen').Typegen0, 6 | schema: { 7 | events: {} as 8 | | { 9 | type: 'FOO'; 10 | } 11 | | { 12 | type: 'BAR'; 13 | }, 14 | }, 15 | initial: 'a', 16 | states: { 17 | a: { 18 | on: { 19 | FOO: 'b', 20 | BAR: 'c', 21 | }, 22 | }, 23 | b: { 24 | entry: ['someAction'], 25 | }, 26 | c: { 27 | entry: ['otherAction'], 28 | }, 29 | }, 30 | }, 31 | { 32 | actions: { 33 | someAction: (context, event) => { 34 | ((accept: 'FOO') => {})(event.type); 35 | }, 36 | otherAction: (context, event) => { 37 | ((accept: 'BAR') => {})(event.type); 38 | }, 39 | }, 40 | }, 41 | ); 42 | -------------------------------------------------------------------------------- /apps/cli/src/__tests__/__examples__/bug-report-1.ts: -------------------------------------------------------------------------------- 1 | import { assign, createMachine } from 'xstate'; 2 | 3 | type Data = {}; 4 | 5 | type Context = { 6 | data: Data[]; 7 | }; 8 | 9 | const machine = createMachine( 10 | { 11 | initial: 'initialise', 12 | id: 'machine', 13 | tsTypes: {} as import('./bug-report-1.typegen').Typegen0, 14 | schema: { 15 | context: { 16 | data: [], 17 | } as Context, 18 | services: {} as { 19 | pollData: { 20 | data: Data[]; 21 | }; 22 | getData: { 23 | data: Data[]; 24 | }; 25 | }, 26 | }, 27 | invoke: { 28 | src: 'pollData', 29 | onDone: { 30 | actions: 'assignData', 31 | }, 32 | }, 33 | states: { 34 | initialise: { 35 | invoke: { 36 | src: 'getData', 37 | onError: { 38 | target: 'error', 39 | }, 40 | onDone: { 41 | target: 'ready', 42 | actions: ['assignData'], 43 | }, 44 | }, 45 | }, 46 | ready: { 47 | id: 'ready', 48 | initial: 'idle', 49 | states: { 50 | idle: {}, 51 | refresh: { 52 | invoke: { 53 | src: 'pollData', 54 | onDone: { 55 | target: '#ready.idle', 56 | actions: 'assignData', 57 | }, 58 | }, 59 | }, 60 | }, 61 | after: { 62 | 5000: { 63 | target: '#ready.refresh', 64 | }, 65 | }, 66 | }, 67 | error: {}, 68 | }, 69 | }, 70 | { 71 | services: { 72 | /** 73 | * This service will now type error if it 74 | * returns anything other than { id: string } 75 | */ 76 | pollData: async () => { 77 | return []; 78 | }, 79 | getData: async () => { 80 | return []; 81 | }, 82 | }, 83 | actions: { 84 | assignData: (context, event) => 85 | assign({ 86 | data: event.data, 87 | }), 88 | }, 89 | }, 90 | ).withConfig({ 91 | actions: {}, 92 | }); 93 | -------------------------------------------------------------------------------- /apps/cli/src/__tests__/__examples__/delay-backtick.ts: -------------------------------------------------------------------------------- 1 | import { createMachine } from 'xstate'; 2 | 3 | createMachine({ 4 | tsTypes: {} as import('./delay-backtick.typegen').Typegen0, 5 | initial: 'a', 6 | states: { 7 | a: { 8 | after: { 9 | '`tick`, `tick`, `tick`': 'b', 10 | }, 11 | }, 12 | b: {}, 13 | }, 14 | }); 15 | -------------------------------------------------------------------------------- /apps/cli/src/__tests__/__examples__/delay-double-quote.ts: -------------------------------------------------------------------------------- 1 | import { createMachine } from 'xstate'; 2 | 3 | createMachine({ 4 | tsTypes: {} as import('./delay-double-quote.typegen').Typegen0, 5 | initial: 'a', 6 | states: { 7 | a: { 8 | after: { 9 | 'wait "forever"': 'b', 10 | }, 11 | }, 12 | b: {}, 13 | }, 14 | }); 15 | -------------------------------------------------------------------------------- /apps/cli/src/__tests__/__examples__/delay-single-quote.ts: -------------------------------------------------------------------------------- 1 | import { createMachine } from 'xstate'; 2 | 3 | createMachine({ 4 | tsTypes: {} as import('./delay-single-quote.typegen').Typegen0, 5 | initial: 'a', 6 | states: { 7 | a: { 8 | after: { 9 | "just '2 minutes'": 'b', 10 | }, 11 | }, 12 | b: {}, 13 | }, 14 | }); 15 | -------------------------------------------------------------------------------- /apps/cli/src/__tests__/__examples__/event-backtick.ts: -------------------------------------------------------------------------------- 1 | import { createMachine } from 'xstate'; 2 | 3 | createMachine({ 4 | tsTypes: {} as import('./event-backtick.typegen').Typegen0, 5 | schema: { 6 | events: {} as { type: '`tick`, `tick`' }, 7 | }, 8 | on: { 9 | '`tick`, `tick`': { 10 | actions: 'testAction', 11 | }, 12 | }, 13 | }); 14 | -------------------------------------------------------------------------------- /apps/cli/src/__tests__/__examples__/event-double-quote.ts: -------------------------------------------------------------------------------- 1 | import { createMachine } from 'xstate'; 2 | 3 | createMachine({ 4 | tsTypes: {} as import('./event-double-quote.typegen').Typegen0, 5 | schema: { 6 | events: {} as { type: '"quote" it' }, 7 | }, 8 | on: { 9 | '"quote" it': { 10 | actions: 'wrap with quotes', 11 | }, 12 | }, 13 | }); 14 | -------------------------------------------------------------------------------- /apps/cli/src/__tests__/__examples__/event-single-quote.ts: -------------------------------------------------------------------------------- 1 | import { createMachine } from 'xstate'; 2 | 3 | createMachine({ 4 | tsTypes: {} as import('./event-single-quote.typegen').Typegen0, 5 | schema: { 6 | events: {} as { type: "let's freestyle" }, 7 | }, 8 | on: { 9 | "let's freestyle": { 10 | actions: 'rap like Eminem', 11 | }, 12 | }, 13 | }); 14 | -------------------------------------------------------------------------------- /apps/cli/src/__tests__/__examples__/functionsInOptions.ts: -------------------------------------------------------------------------------- 1 | import { createMachine } from 'xstate'; 2 | 3 | createMachine( 4 | { 5 | tsTypes: {} as import('./functionsInOptions.typegen').Typegen0, 6 | schema: { 7 | services: { 8 | service1: {} as { 9 | data: boolean; 10 | }, 11 | service2: {} as { 12 | data: boolean; 13 | }, 14 | }, 15 | }, 16 | on: { 17 | FOO: { 18 | cond: 'guard', 19 | actions: 'sayHello', 20 | }, 21 | }, 22 | invoke: [ 23 | { 24 | src: 'service1', 25 | }, 26 | { 27 | src: 'service2', 28 | }, 29 | ], 30 | }, 31 | { 32 | guards: { 33 | guard() { 34 | return true; 35 | }, 36 | }, 37 | actions: { 38 | sayHello() {}, 39 | }, 40 | services: { 41 | service1() { 42 | return Promise.resolve(true); 43 | }, 44 | service2() { 45 | return Promise.resolve(true); 46 | }, 47 | }, 48 | }, 49 | ).withConfig( 50 | // Should have no warning because all implementations 51 | // have been passed 52 | {}, 53 | ); 54 | -------------------------------------------------------------------------------- /apps/cli/src/__tests__/__examples__/guard-backtick.ts: -------------------------------------------------------------------------------- 1 | import { createMachine } from 'xstate'; 2 | 3 | createMachine({ 4 | tsTypes: {} as import('./guard-backtick.typegen').Typegen0, 5 | schema: { 6 | events: {} as { type: 'PING' }, 7 | }, 8 | on: { 9 | PING: { 10 | cond: 'is `event` valid', 11 | actions: () => {}, 12 | }, 13 | }, 14 | }); 15 | -------------------------------------------------------------------------------- /apps/cli/src/__tests__/__examples__/guard-double-quote.ts: -------------------------------------------------------------------------------- 1 | import { createMachine } from 'xstate'; 2 | 3 | createMachine({ 4 | tsTypes: {} as import('./guard-double-quote.typegen').Typegen0, 5 | schema: { 6 | events: {} as { type: 'PING' }, 7 | }, 8 | on: { 9 | PING: { 10 | cond: 'is this "safe"', 11 | actions: () => {}, 12 | }, 13 | }, 14 | }); 15 | -------------------------------------------------------------------------------- /apps/cli/src/__tests__/__examples__/guard-single-quote.ts: -------------------------------------------------------------------------------- 1 | import { createMachine } from 'xstate'; 2 | 3 | createMachine({ 4 | tsTypes: {} as import('./guard-single-quote.typegen').Typegen0, 5 | schema: { 6 | events: {} as { type: 'PING' }, 7 | }, 8 | on: { 9 | PING: { 10 | cond: "it's play time", 11 | actions: () => {}, 12 | }, 13 | }, 14 | }); 15 | -------------------------------------------------------------------------------- /apps/cli/src/__tests__/__examples__/multi.ts: -------------------------------------------------------------------------------- 1 | import { createMachine } from 'xstate'; 2 | 3 | const machine1 = createMachine( 4 | { 5 | tsTypes: {} as import('./multi.typegen').Typegen0, 6 | schema: { 7 | events: {} as { type: 'FOO' } | { type: 'BAR' }, 8 | }, 9 | initial: 'a', 10 | states: { 11 | a: { 12 | on: { 13 | FOO: 'b', 14 | }, 15 | }, 16 | b: { 17 | entry: ['entryB'], 18 | }, 19 | }, 20 | }, 21 | { 22 | actions: { 23 | entryB: (context, event) => { 24 | ((_accept: 'FOO') => {})(event.type); 25 | }, 26 | }, 27 | }, 28 | ); 29 | 30 | const machine2 = createMachine( 31 | { 32 | tsTypes: {} as import('./multi.typegen').Typegen1, 33 | schema: { 34 | events: {} as { type: 'BAZ' } | { type: 'BAZ2' }, 35 | }, 36 | initial: 'a', 37 | states: { 38 | a: { 39 | on: { 40 | BAZ: 'b', 41 | }, 42 | }, 43 | b: { 44 | entry: ['entryB'], 45 | }, 46 | }, 47 | }, 48 | { 49 | actions: { 50 | entryB: (context, event) => { 51 | ((_accept: 'BAZ') => {})(event.type); 52 | }, 53 | }, 54 | }, 55 | ); 56 | -------------------------------------------------------------------------------- /apps/cli/src/__tests__/__examples__/service-backtick.ts: -------------------------------------------------------------------------------- 1 | import { createMachine } from 'xstate'; 2 | 3 | createMachine({ 4 | tsTypes: {} as import('./service-backtick.typegen').Typegen0, 5 | initial: 'a', 6 | states: { 7 | a: { 8 | invoke: { 9 | id: 'prepare `ticker`', 10 | src: 'prepare `ticker`', 11 | onDone: 'finished', 12 | onError: 'failed', 13 | }, 14 | }, 15 | finished: {}, 16 | failed: {}, 17 | }, 18 | }); 19 | -------------------------------------------------------------------------------- /apps/cli/src/__tests__/__examples__/service-double-quote.ts: -------------------------------------------------------------------------------- 1 | import { createMachine } from 'xstate'; 2 | 3 | createMachine({ 4 | tsTypes: {} as import('./service-double-quote.typegen').Typegen0, 5 | initial: 'a', 6 | states: { 7 | a: { 8 | invoke: { 9 | id: 'trigger "fake" alarm', 10 | src: 'trigger "fake" alarm', 11 | onDone: 'finished', 12 | onError: 'failed', 13 | }, 14 | }, 15 | finished: {}, 16 | failed: {}, 17 | }, 18 | }); 19 | -------------------------------------------------------------------------------- /apps/cli/src/__tests__/__examples__/service-single-quote.ts: -------------------------------------------------------------------------------- 1 | import { createMachine } from 'xstate'; 2 | 3 | createMachine({ 4 | tsTypes: {} as import('./service-single-quote.typegen').Typegen0, 5 | initial: 'a', 6 | states: { 7 | a: { 8 | invoke: { 9 | id: "just tell me how I'm feeling", 10 | src: "just tell me how I'm feeling", 11 | onDone: 'finished', 12 | onError: 'failed', 13 | }, 14 | }, 15 | finished: {}, 16 | failed: {}, 17 | }, 18 | }); 19 | -------------------------------------------------------------------------------- /apps/cli/src/__tests__/__examples__/state-name-double-quote-compound.ts: -------------------------------------------------------------------------------- 1 | import { createMachine } from 'xstate'; 2 | 3 | createMachine({ 4 | tsTypes: {} as import('./state-name-double-quote-compound.typegen').Typegen0, 5 | schema: { 6 | events: {} as { type: 'NEXT' }, 7 | }, 8 | initial: 'oh, this is just "awesome"', 9 | states: { 10 | 'oh, this is just "awesome"': { 11 | initial: 'a', 12 | states: { 13 | a: {}, 14 | }, 15 | }, 16 | }, 17 | }); 18 | -------------------------------------------------------------------------------- /apps/cli/src/__tests__/__examples__/state-name-double-quote-leaf.ts: -------------------------------------------------------------------------------- 1 | import { createMachine } from 'xstate'; 2 | 3 | createMachine({ 4 | tsTypes: {} as import('./state-name-double-quote-leaf.typegen').Typegen0, 5 | schema: { 6 | events: {} as { type: 'NEXT' }, 7 | }, 8 | initial: 'a', 9 | states: { 10 | a: { 11 | initial: 'oh, this is just "awesome"', 12 | states: { 13 | 'oh, this is just "awesome"': {}, 14 | }, 15 | }, 16 | }, 17 | }); 18 | -------------------------------------------------------------------------------- /apps/cli/src/__tests__/__examples__/state-name-double-quote.ts: -------------------------------------------------------------------------------- 1 | import { createMachine } from 'xstate'; 2 | 3 | createMachine({ 4 | tsTypes: {} as import('./state-name-double-quote.typegen').Typegen0, 5 | schema: { 6 | events: {} as { type: 'NEXT' }, 7 | }, 8 | initial: 'oh, this is just "awesome"', 9 | states: { 10 | 'oh, this is just "awesome"': {}, 11 | }, 12 | }); 13 | -------------------------------------------------------------------------------- /apps/cli/src/__tests__/__examples__/state-name-single-quote-compound.ts: -------------------------------------------------------------------------------- 1 | import { createMachine } from 'xstate'; 2 | 3 | createMachine({ 4 | tsTypes: {} as import('./state-name-single-quote-compound.typegen').Typegen0, 5 | schema: { 6 | events: {} as { type: 'NEXT' }, 7 | }, 8 | initial: "oh, this is just 'awesome'", 9 | states: { 10 | "oh, this is just 'awesome'": { 11 | initial: 'a', 12 | states: { 13 | a: {}, 14 | }, 15 | }, 16 | }, 17 | }); 18 | -------------------------------------------------------------------------------- /apps/cli/src/__tests__/__examples__/state-name-single-quote-leaf.ts: -------------------------------------------------------------------------------- 1 | import { createMachine } from 'xstate'; 2 | 3 | createMachine({ 4 | tsTypes: {} as import('./state-name-single-quote-leaf.typegen').Typegen0, 5 | schema: { 6 | events: {} as { type: 'NEXT' }, 7 | }, 8 | initial: 'a', 9 | states: { 10 | a: { 11 | initial: "oh, this is just 'awesome'", 12 | states: { 13 | "oh, this is just 'awesome'": {}, 14 | }, 15 | }, 16 | }, 17 | }); 18 | -------------------------------------------------------------------------------- /apps/cli/src/__tests__/__examples__/state-name-single-quote.ts: -------------------------------------------------------------------------------- 1 | import { createMachine } from 'xstate'; 2 | 3 | createMachine({ 4 | tsTypes: {} as import('./state-name-single-quote.typegen').Typegen0, 5 | schema: { 6 | events: {} as { type: 'NEXT' }, 7 | }, 8 | initial: "oh, this is just 'awesome'", 9 | states: { 10 | "oh, this is just 'awesome'": {}, 11 | }, 12 | }); 13 | -------------------------------------------------------------------------------- /apps/cli/src/__tests__/__examples__/tag-backtick.ts: -------------------------------------------------------------------------------- 1 | import { createMachine } from 'xstate'; 2 | 3 | createMachine({ 4 | tsTypes: {} as import('./tag-backtick.typegen').Typegen0, 5 | tags: 'Say `hello`', 6 | }); 7 | -------------------------------------------------------------------------------- /apps/cli/src/__tests__/__examples__/tag-double-quote.ts: -------------------------------------------------------------------------------- 1 | import { createMachine } from 'xstate'; 2 | 3 | createMachine({ 4 | tsTypes: {} as import('./tag-double-quote.typegen').Typegen0, 5 | tags: 'Say "hello"', 6 | }); 7 | -------------------------------------------------------------------------------- /apps/cli/src/__tests__/__examples__/tag-single-quote.ts: -------------------------------------------------------------------------------- 1 | import { createMachine } from 'xstate'; 2 | 3 | createMachine({ 4 | tsTypes: {} as import('./tag-single-quote.typegen').Typegen0, 5 | tags: "Display user's profile", 6 | }); 7 | -------------------------------------------------------------------------------- /apps/cli/src/__tests__/__examples__/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2019", 4 | "lib": ["ES2019"], 5 | "module": "commonjs", 6 | "moduleResolution": "node", 7 | "sourceMap": true, 8 | "skipLibCheck": true, 9 | "strict": true, 10 | "noEmit": true 11 | }, 12 | "include": ["*.ts"] 13 | } 14 | -------------------------------------------------------------------------------- /apps/cli/src/__tests__/__examples__/type-safe-services.ts: -------------------------------------------------------------------------------- 1 | import { createMachine } from 'xstate'; 2 | 3 | const machine = createMachine( 4 | { 5 | tsTypes: {} as import('./type-safe-services.typegen').Typegen0, 6 | schema: {} as { 7 | services: { 8 | makeFetch: { 9 | data: string; 10 | }; 11 | }; 12 | }, 13 | invoke: { 14 | src: 'makeFetch', 15 | onDone: { 16 | actions: 'sayHello', 17 | }, 18 | }, 19 | }, 20 | { 21 | services: { 22 | makeFetch: (context, event) => { 23 | return Promise.resolve('string'); 24 | }, 25 | }, 26 | actions: { 27 | sayHello: (context, event) => { 28 | ((accept: string) => {})(event.data); 29 | }, 30 | }, 31 | }, 32 | ); 33 | -------------------------------------------------------------------------------- /apps/cli/src/__tests__/__sky__/.gitignore: -------------------------------------------------------------------------------- 1 | *.typegen.ts 2 | -------------------------------------------------------------------------------- /apps/cli/src/__tests__/__sky__/actorFromStately.sky.ts: -------------------------------------------------------------------------------- 1 | // This file was generated by the XState CLI, please do not edit it manually. 2 | import { createMachine } from 'xstate-beta'; 3 | 4 | const machine = createMachine( 5 | { 6 | id: 'bananaDispensingMachine', 7 | initial: 'idle', 8 | states: { 9 | idle: { 10 | description: 11 | 'The machine is idle and waiting for a user to insert coins or a request to dispense a banana.', 12 | on: { 13 | INSERT_COINS: { 14 | target: 'waitingForSelection', 15 | reenter: false, 16 | }, 17 | DISPENSE_BANANA: { 18 | reenter: true, 19 | }, 20 | }, 21 | }, 22 | waitingForSelection: { 23 | description: 24 | 'The machine is waiting for the user to select the number of bananas to dispense.', 25 | on: { 26 | SELECT_ONE: { 27 | target: 'dispensingOneBanana', 28 | reenter: false, 29 | }, 30 | SELECT_THREE: { 31 | target: 'dispensingThreeBananas', 32 | reenter: false, 33 | }, 34 | CANCEL: { 35 | target: 'idle', 36 | reenter: false, 37 | }, 38 | SELECT_TWO: { 39 | target: 'dispensingTwoBananas', 40 | reenter: false, 41 | }, 42 | }, 43 | }, 44 | dispensingOneBanana: { 45 | description: 'The machine is dispensing one banana to the user.', 46 | on: { 47 | DISPENSE_COMPLETE: { 48 | target: 'idle', 49 | reenter: false, 50 | }, 51 | }, 52 | }, 53 | dispensingThreeBananas: { 54 | description: 'The machine is dispensing three bananas to the user.', 55 | on: { 56 | DISPENSE_COMPLETE: { 57 | target: 'idle', 58 | reenter: false, 59 | }, 60 | }, 61 | }, 62 | dispensingTwoBananas: { 63 | description: 'The machine is dispensing two bananas to the user.', 64 | on: { 65 | DISPENSE_COMPLETE: { 66 | target: 'idle', 67 | reenter: false, 68 | }, 69 | }, 70 | }, 71 | }, 72 | types: { 73 | events: {} as 74 | | { type: 'CANCEL' } 75 | | { type: 'SELECT_ONE' } 76 | | { type: 'SELECT_TWO' } 77 | | { type: 'INSERT_COINS' } 78 | | { type: 'SELECT_THREE' } 79 | | { type: 'DISPENSE_BANANA' } 80 | | { type: 'DISPENSE_COMPLETE' }, 81 | }, 82 | }, 83 | { 84 | actions: {}, 85 | actors: {}, 86 | guards: {}, 87 | delays: {}, 88 | }, 89 | ); 90 | export const skyConfig = { 91 | actorId: 'bd8947a6-d615-465a-bad7-cb1e73dbb151', 92 | machine, 93 | }; 94 | -------------------------------------------------------------------------------- /apps/cli/src/__tests__/__sky__/actorFromStately.ts: -------------------------------------------------------------------------------- 1 | import { actorFromStately } from '@statelyai/sky'; 2 | 3 | // TODO: The Sky SDK is using xstate v5 so we can't really create meaningful test until we upgrade to v5 in this repo 4 | async function testActor() { 5 | const actor = await actorFromStately({ 6 | apiKey: 'my api key', 7 | url: 'https://sky.dev.stately.ai/th413d', 8 | sessionId: 'my session', 9 | }); 10 | actor.send({ type: 'INSERT_COINS' }); 11 | } 12 | -------------------------------------------------------------------------------- /apps/cli/src/__tests__/__sky__/machineFromStately.sky.ts: -------------------------------------------------------------------------------- 1 | // This file was generated by the XState CLI, please do not edit it manually. 2 | import { createMachine } from 'xstate-beta'; 3 | 4 | const machine = createMachine( 5 | { 6 | id: 'bananaDispensingMachine', 7 | initial: 'idle', 8 | states: { 9 | idle: { 10 | description: 11 | 'The machine is idle and waiting for a user to insert coins or a request to dispense a banana.', 12 | on: { 13 | INSERT_COINS: { 14 | target: 'waitingForSelection', 15 | reenter: true, 16 | }, 17 | DISPENSE_BANANA: { 18 | target: 'idle', 19 | reenter: true, 20 | }, 21 | }, 22 | }, 23 | waitingForSelection: { 24 | description: 25 | 'The machine is waiting for the user to select the number of bananas to dispense.', 26 | on: { 27 | SELECT_ONE: { 28 | target: 'dispensingOneBanana', 29 | reenter: true, 30 | }, 31 | SELECT_THREE: { 32 | target: 'dispensingThreeBananas', 33 | reenter: true, 34 | }, 35 | CANCEL: { 36 | target: 'idle', 37 | reenter: true, 38 | }, 39 | SELECT_TWO: { 40 | target: 'dispensingTwoBananas', 41 | reenter: true, 42 | }, 43 | }, 44 | }, 45 | dispensingOneBanana: { 46 | description: 'The machine is dispensing one banana to the user.', 47 | on: { 48 | DISPENSE_COMPLETE: { 49 | target: 'idle', 50 | reenter: true, 51 | }, 52 | }, 53 | }, 54 | dispensingThreeBananas: { 55 | description: 'The machine is dispensing three bananas to the user.', 56 | on: { 57 | DISPENSE_COMPLETE: { 58 | target: 'idle', 59 | reenter: true, 60 | }, 61 | }, 62 | }, 63 | dispensingTwoBananas: { 64 | description: 'The machine is dispensing two bananas to the user.', 65 | on: { 66 | DISPENSE_COMPLETE: { 67 | target: 'idle', 68 | reenter: true, 69 | }, 70 | }, 71 | }, 72 | }, 73 | types: { 74 | events: {} as 75 | | { type: 'CANCEL' } 76 | | { type: 'SELECT_ONE' } 77 | | { type: 'SELECT_TWO' } 78 | | { type: 'INSERT_COINS' } 79 | | { type: 'SELECT_THREE' } 80 | | { type: 'DISPENSE_BANANA' } 81 | | { type: 'DISPENSE_COMPLETE' }, 82 | }, 83 | }, 84 | { 85 | actions: {}, 86 | actors: {}, 87 | guards: {}, 88 | delays: {}, 89 | }, 90 | ); 91 | 92 | export const skyConfig = { 93 | actorId: 'bd8947a6-d615-465a-bad7-cb1e73dbb151', 94 | machine, 95 | }; 96 | -------------------------------------------------------------------------------- /apps/cli/src/__tests__/__sky__/machineFromStately.ts: -------------------------------------------------------------------------------- 1 | import { machineFromStately } from '@statelyai/sky'; 2 | import { createActor } from 'xstate-beta'; 3 | import { skyConfig } from './machineFromStately.sky'; 4 | 5 | // TODO: The Sky SDK is using xstate v5 so we can't really create meaningful test until we upgrade to v5 in this repo 6 | const machine = machineFromStately( 7 | { url: 'https://sky.dev.stately.ai/th413d' }, 8 | skyConfig as any, 9 | ); 10 | 11 | const actor = createActor(machine as any).start(); 12 | actor.send({ type: 'INSERT_COINS' }); 13 | -------------------------------------------------------------------------------- /apps/cli/src/__tests__/__sky__/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2019", 4 | "lib": ["ES2019"], 5 | "module": "commonjs", 6 | "moduleResolution": "node", 7 | "sourceMap": true, 8 | "skipLibCheck": true, 9 | "strict": true, 10 | "noEmit": true 11 | }, 12 | "include": ["*.ts"] 13 | } 14 | -------------------------------------------------------------------------------- /apps/cli/src/__tests__/examples.test.ts: -------------------------------------------------------------------------------- 1 | import { execSync } from 'child_process'; 2 | import * as fs from 'fs'; 3 | import * as minimatch from 'minimatch'; 4 | import * as path from 'path'; 5 | 6 | describe('typegen', () => { 7 | const examplesPath = path.resolve(__dirname, '__examples__'); 8 | const examplesFiles = fs.readdirSync(examplesPath); 9 | 10 | minimatch 11 | .match(examplesFiles, '*.typegen.ts') 12 | .map((file) => fs.unlinkSync(path.join(examplesPath, file))); 13 | 14 | execSync('yarn build', { 15 | cwd: __dirname, 16 | stdio: 'ignore', 17 | }); 18 | 19 | execSync('node ../../bin/bin.js typegen "./__examples__/*.ts"', { 20 | cwd: __dirname, 21 | }); 22 | 23 | it('Should pass tsc', async () => { 24 | try { 25 | execSync(`tsc`, { cwd: examplesPath }); 26 | } catch (e: any) { 27 | throw new Error(e.stdout.toString()); 28 | } 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /apps/cli/src/__tests__/sky.test.ts: -------------------------------------------------------------------------------- 1 | import { execSync } from 'child_process'; 2 | import * as path from 'path'; 3 | 4 | describe('sky', () => { 5 | // TODO: The Sky SDK is using xstate v5 so we can't really create meaningful test until we upgrade to v5 in this repo 6 | const examplesPath = path.resolve(__dirname, '__sky__'); 7 | // const examplesFiles = fs.readdirSync(examplesPath); 8 | 9 | // minimatch 10 | // .match(examplesFiles, '*.sky.ts') 11 | // .map((file) => fs.unlinkSync(path.join(examplesPath, file))); 12 | 13 | // execSync('yarn build', { 14 | // cwd: __dirname, 15 | // stdio: 'ignore', 16 | // }); 17 | 18 | // execSync('node ../../bin/bin.js sky "./__sky__/*.ts"', { 19 | // cwd: __dirname, 20 | // }); 21 | 22 | it('Should pass tsc', async () => { 23 | try { 24 | execSync(`tsc`, { cwd: examplesPath }); 25 | } catch (e: any) { 26 | throw new Error(e.stdout.toString()); 27 | } 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /apps/cli/src/sky/urlUtils.ts: -------------------------------------------------------------------------------- 1 | import fetch from 'isomorphic-fetch'; 2 | 3 | /* 4 | * This function is used to expand a skyUrl to the final config API URL 5 | */ 6 | async function skyUrlExpander( 7 | skyUrl: string | undefined | null, 8 | ): Promise { 9 | if (skyUrl && skyUrl.length > 0) { 10 | try { 11 | // Fetch the skyUrl, but don't automatically follow redirects 12 | const skyResponse = await fetch(skyUrl, { redirect: 'manual' }); 13 | 14 | // If there is a potential redirect follow it 15 | if (skyResponse.status === 307) { 16 | return await skyUrlExpander(skyResponse.headers.get('Location')); 17 | } else if (skyResponse.status === 200) { 18 | // If there is no redirect, we have the final config API URL 19 | return skyUrl; 20 | } 21 | } catch (error) { 22 | console.error('Error while expanding short Sky URL', error); 23 | } 24 | } 25 | } 26 | 27 | export async function fetchSkyConfig(skyUrl: string | undefined | null) { 28 | const skyConfigUrl = await skyUrlExpander(skyUrl); 29 | if (skyConfigUrl) { 30 | const url = new URL(skyConfigUrl); 31 | const actorId = url.searchParams.get('actorId'); 32 | if (actorId) { 33 | return { 34 | actorId, 35 | origin: url.origin, 36 | }; 37 | } 38 | } 39 | console.error( 40 | `Error: URL does not point to a valid workflow, please contact support@stately.ai with the URL ${skyUrl}`, 41 | ); 42 | } 43 | -------------------------------------------------------------------------------- /apps/cli/src/sky/writeToFetchedMachineFile.ts: -------------------------------------------------------------------------------- 1 | import { SkyConfig } from '@statelyai/sky'; 2 | import { existsSync } from 'fs'; 3 | import * as fs from 'fs/promises'; 4 | import * as path from 'path'; 5 | import * as prettier from 'prettier'; 6 | 7 | const getPathToSave = (filePath: string) => 8 | filePath.slice(0, -path.extname(filePath).length) + '.sky.ts'; 9 | 10 | export const doesSkyConfigExist = (filePath: string) => { 11 | const pathToSave = getPathToSave(filePath); 12 | return existsSync(pathToSave); 13 | }; 14 | 15 | // The exported code from the sky config file has an initial export statement that we don't want 16 | function removeLeadingExport(code: string) { 17 | const regex = /export (const machine = createMachine)/; 18 | return code.replace(regex, '$1'); 19 | } 20 | 21 | export const writeSkyConfig = async (opts: { 22 | filePath: string; 23 | skyConfig: SkyConfig; 24 | createTypeGenFile: 25 | | ((uriArray: string[], { cwd }: { cwd: string }) => Promise) 26 | | undefined; 27 | }) => { 28 | const { filePath, skyConfig } = opts; 29 | if ('error' in skyConfig) { 30 | console.error(skyConfig.error); 31 | return; 32 | } 33 | const prettierConfig = await prettier.resolveConfig(filePath); 34 | const pathToSave = getPathToSave(filePath); 35 | const code = removeLeadingExport(skyConfig.configString); 36 | 37 | const machineFile = `// This file was generated by the XState CLI, please do not edit it manually. 38 | ${code}; 39 | 40 | export const skyConfig = { actorId:'${skyConfig.actorId}', machine };`; 41 | 42 | await fs.writeFile( 43 | pathToSave, 44 | prettier.format(machineFile, { 45 | ...prettierConfig, 46 | parser: 'typescript', 47 | }), 48 | ); 49 | 50 | // Run typegen if it's provided 51 | if (opts.createTypeGenFile) 52 | await opts.createTypeGenFile([pathToSave], { cwd: __dirname }); 53 | }; 54 | -------------------------------------------------------------------------------- /apps/cli/src/typegen/writeToFiles.ts: -------------------------------------------------------------------------------- 1 | import { extractMachinesFromFile } from '@xstate/machine-extractor'; 2 | import { 3 | TypegenData, 4 | getTsTypesEdits, 5 | getTypegenData, 6 | getTypegenOutput, 7 | processFileEdits, 8 | } from '@xstate/tools-shared'; 9 | import 'dotenv/config'; 10 | import * as fs from 'fs/promises'; 11 | import * as path from 'path'; 12 | import { getPrettierInstance } from '../utils'; 13 | 14 | async function removeFile(filePath: string) { 15 | try { 16 | await fs.unlink(filePath); 17 | } catch (e: any) { 18 | if (e?.code === 'ENOENT') { 19 | return; 20 | } 21 | throw e; 22 | } 23 | } 24 | 25 | const writeToTypegenFile = async ( 26 | typegenUri: string, 27 | types: TypegenData[], 28 | { cwd }: { cwd: string }, 29 | ) => { 30 | const prettierInstance = getPrettierInstance(cwd); 31 | await fs.writeFile( 32 | typegenUri, 33 | // // Prettier v3 returns a promise 34 | await prettierInstance.format(getTypegenOutput(types), { 35 | ...(await prettierInstance.resolveConfig(typegenUri)), 36 | parser: 'typescript', 37 | }), 38 | ); 39 | }; 40 | 41 | export const writeToFiles = async ( 42 | uriArray: string[], 43 | { cwd }: { cwd: string }, 44 | ) => { 45 | /** 46 | * TODO - implement pretty readout 47 | */ 48 | await Promise.all( 49 | uriArray.map(async (uri) => { 50 | try { 51 | const fileContents = await fs.readFile(uri, 'utf8'); 52 | 53 | const extracted = extractMachinesFromFile(fileContents); 54 | 55 | if (!extracted) { 56 | return; 57 | } 58 | 59 | const typegenUri = 60 | uri.slice(0, -path.extname(uri).length) + '.typegen.ts'; 61 | 62 | const types = extracted.machines 63 | .filter( 64 | ( 65 | machineResult, 66 | ): machineResult is NonNullable => 67 | !!machineResult?.machineCallResult.definition?.tsTypes?.node, 68 | ) 69 | .map((machineResult, index) => 70 | getTypegenData(path.basename(uri), index, machineResult), 71 | ); 72 | 73 | if (types.length) { 74 | await writeToTypegenFile(typegenUri, types, { cwd }); 75 | } else { 76 | await removeFile(typegenUri); 77 | } 78 | 79 | const edits = getTsTypesEdits(types); 80 | if (edits.length > 0) { 81 | const newFile = processFileEdits(fileContents, edits); 82 | await fs.writeFile(uri, newFile); 83 | } 84 | console.log(`${uri} - success`); 85 | } catch (e: any) { 86 | if (e?.code === 'BABEL_PARSER_SYNTAX_ERROR') { 87 | console.error(`${uri} - syntax error, skipping`); 88 | } else { 89 | console.error(`${uri} - error, `, e); 90 | } 91 | throw e; 92 | } 93 | }), 94 | ); 95 | }; 96 | -------------------------------------------------------------------------------- /apps/cli/src/utils.ts: -------------------------------------------------------------------------------- 1 | export // TODO: just use the native one when support for node 12 gets dropped 2 | const allSettled: typeof Promise.allSettled = (promises: Promise[]) => 3 | Promise.all( 4 | promises.map((promise) => 5 | promise.then( 6 | (value) => ({ status: 'fulfilled' as const, value }), 7 | (reason) => ({ status: 'rejected' as const, reason }), 8 | ), 9 | ), 10 | ); 11 | 12 | let prettier: typeof import('prettier') | undefined; 13 | export function getPrettierInstance(cwd: string): typeof import('prettier') { 14 | if (prettier) { 15 | return prettier; 16 | } 17 | try { 18 | return require(require.resolve('prettier', { paths: [cwd] })); 19 | } catch (err) { 20 | if (!err || (err as any).code !== 'MODULE_NOT_FOUND') { 21 | throw err; 22 | } 23 | // we load our own prettier instance lazily on purpose to speed up the init time 24 | return (prettier = require('prettier')); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /apps/cli/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strict": true, 4 | "noEmit": true, 5 | "moduleResolution": "node", 6 | "allowSyntheticDefaultImports": true, 7 | "resolveJsonModule": true, 8 | "skipLibCheck": true, 9 | "target": "es2015", 10 | "exactOptionalPropertyTypes": true, 11 | "module": "CommonJS", 12 | "types": ["jest"] 13 | }, 14 | "include": ["src"], 15 | "exclude": ["src/__tests__/__examples__", "src/__tests__/__sky__"] 16 | } 17 | -------------------------------------------------------------------------------- /apps/extension/client/.vscodeignore: -------------------------------------------------------------------------------- 1 | .vscode/** 2 | **/*.ts 3 | **/*.map 4 | .gitignore 5 | **/tsconfig.json 6 | **/tsconfig.base.json 7 | contributing.md 8 | .travis.yml 9 | node_modules/** 10 | assets -------------------------------------------------------------------------------- /apps/extension/client/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2022 David Khourshid 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /apps/extension/client/bundled-editor/Inter-italic.var.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/statelyai/xstate-tools/fc7a85d780cd8ea4ea21fb423f2477e01e2f1dc3/apps/extension/client/bundled-editor/Inter-italic.var.woff2 -------------------------------------------------------------------------------- /apps/extension/client/bundled-editor/Inter-roman.var.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/statelyai/xstate-tools/fc7a85d780cd8ea4ea21fb423f2477e01e2f1dc3/apps/extension/client/bundled-editor/Inter-roman.var.woff2 -------------------------------------------------------------------------------- /apps/extension/client/bundled-editor/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Stately Studio 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /apps/extension/client/jest.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('@jest/types').Config.InitialOptions} */ 2 | module.exports = { 3 | testMatch: ['**/?(*.)+(spec|test).[jt]s?(x)'], 4 | watchPlugins: [ 5 | 'jest-watch-typeahead/filename', 6 | 'jest-watch-typeahead/testname', 7 | ], 8 | snapshotFormat: { 9 | printBasicPrototype: false, 10 | escapeString: false, 11 | }, 12 | transform: { 13 | '^.+\\.tsx?$': ['esbuild-jest', { sourcemap: true }], 14 | }, 15 | }; 16 | -------------------------------------------------------------------------------- /apps/extension/client/media/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/statelyai/xstate-tools/fc7a85d780cd8ea4ea21fb423f2477e01e2f1dc3/apps/extension/client/media/icon.png -------------------------------------------------------------------------------- /apps/extension/client/snippets/xstate.code-snippets: -------------------------------------------------------------------------------- 1 | { 2 | "XState Machine": { 3 | "scope": "javascript,javascriptreact,typescript,typescriptreact,vue", 4 | "prefix": "xsm", 5 | "body": [ 6 | "import { createMachine } from 'xstate';", 7 | "const ${1:nameOf}Machine = createMachine({\n\tid: '${1:nameOf}',\n\tinitial: '${2:initialState}',\n\tstates: {\n\t\t${2:initialState}: {$0},\n\t}\n});" 8 | ], 9 | "description": "Outline for XState Machine" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /apps/extension/client/src/constants.ts: -------------------------------------------------------------------------------- 1 | export const EXTENSION_ID = 'statelyai.stately-vscode'; 2 | -------------------------------------------------------------------------------- /apps/extension/client/src/extension.ts: -------------------------------------------------------------------------------- 1 | /* -------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | * ------------------------------------------------------------------------------------------ */ 5 | 6 | import * as path from 'path'; 7 | import * as vscode from 'vscode'; 8 | import { handleTypegenNestingConfig } from './checkTypegenNesting'; 9 | import { initiateEditor } from './initiateEditor'; 10 | import { initiateTypegen } from './initiateTypegen'; 11 | import { TypeSafeLanguageClient } from './typeSafeLanguageClient'; 12 | 13 | let languageClient: TypeSafeLanguageClient | undefined; 14 | 15 | export async function activate(context: vscode.ExtensionContext) { 16 | const serverModule = context.asAbsolutePath(path.join('dist', 'server.js')); 17 | 18 | languageClient = new TypeSafeLanguageClient(serverModule); 19 | languageClient.start(); 20 | await languageClient.onReady(); 21 | 22 | handleTypegenNestingConfig(); 23 | initiateEditor(context, languageClient); 24 | initiateTypegen(context, languageClient); 25 | } 26 | 27 | export function deactivate(): Thenable | undefined { 28 | return languageClient?.stop(); 29 | } 30 | -------------------------------------------------------------------------------- /apps/extension/client/src/getWebviewContent.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | 3 | export const getWebviewContent = (scriptsSrc: vscode.Uri, title: string) => { 4 | return ` 5 | 6 | 7 | 8 | 9 | 10 | ${title} 11 | 37 | 38 | 39 | 40 |
41 | 42 |
43 |