@stack-dev/cli 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/.turbo/daemon/6fa76abe2aa470d0-turbo.log.2025-08-02 +5 -0
  2. package/.turbo/daemon/6fa76abe2aa470d0-turbo.log.2025-12-29 +1 -0
  3. package/.turbo/daemon/6fa76abe2aa470d0-turbo.log.2025-12-30 +0 -0
  4. package/.turbo/turbo-build.log +21 -0
  5. package/.turbo/turbo-check-types.log +5 -0
  6. package/dist/index.d.mts +2 -0
  7. package/dist/index.d.ts +2 -0
  8. package/dist/index.js +2097 -0
  9. package/dist/index.mjs +2073 -0
  10. package/eslint.config.mjs +3 -0
  11. package/package.json +35 -0
  12. package/prettier.config.mjs +3 -0
  13. package/src/file-generator/file-generator-imp.ts +20 -0
  14. package/src/file-generator/file-generator.ts +5 -0
  15. package/src/file-generator/index.ts +3 -0
  16. package/src/file-generator/package-json-generator.ts +23 -0
  17. package/src/index.ts +185 -0
  18. package/src/link-packages.ts +65 -0
  19. package/src/package-json/dependency.ts +28 -0
  20. package/src/package-json/index.ts +3 -0
  21. package/src/package-json/package-json.ts +269 -0
  22. package/src/packages/create-config-package.ts +29 -0
  23. package/src/packages/index.ts +4 -0
  24. package/src/packages/library-package/create-library-package.ts +85 -0
  25. package/src/packages/library-package/files/add-file-generator.ts +8 -0
  26. package/src/packages/library-package/files/add-spec-file-generator.ts +17 -0
  27. package/src/packages/library-package/files/eslint-config-file-generator.ts +11 -0
  28. package/src/packages/library-package/files/index-file-generator.ts +9 -0
  29. package/src/packages/library-package/files/prettier-config-file-generator.ts +11 -0
  30. package/src/packages/library-package/files/tsconfig-file-generator.ts +15 -0
  31. package/src/packages/library-package/files/tsup-config-file-generator.ts +23 -0
  32. package/src/packages/library-package/files/vitest-config-file-generator.ts +19 -0
  33. package/src/packages/library-package/index.ts +1 -0
  34. package/src/packages/react-package/create-react-package.ts +25 -0
  35. package/src/packages/react-package/create-tailwind-react-package.ts +30 -0
  36. package/src/packages/react-package/create-unstyled-react-package.ts +3 -0
  37. package/src/packages/react-package/css-react-package/create-css-react-package.ts +103 -0
  38. package/src/packages/react-package/css-react-package/files/button-css-module-file-generator.ts +16 -0
  39. package/src/packages/react-package/css-react-package/files/button-file-generator.ts +14 -0
  40. package/src/packages/react-package/css-react-package/files/button-spec-file-generator.ts +33 -0
  41. package/src/packages/react-package/css-react-package/files/eslint-config-file-generator.ts +18 -0
  42. package/src/packages/react-package/css-react-package/files/index-file-generator.ts +9 -0
  43. package/src/packages/react-package/css-react-package/files/prettier-config-file-generator.ts +11 -0
  44. package/src/packages/react-package/css-react-package/files/tsconfig-file-generator.ts +15 -0
  45. package/src/packages/react-package/css-react-package/files/tsup-config-file-generator.ts +24 -0
  46. package/src/packages/react-package/css-react-package/files/vitest-config-file-generator.ts +23 -0
  47. package/src/packages/react-package/index.ts +1 -0
  48. package/src/packages/react-package/styled-components-react-package/create-styled-components-react-package.ts +112 -0
  49. package/src/packages/react-package/styled-components-react-package/files/button-file-generator.ts +30 -0
  50. package/src/packages/react-package/styled-components-react-package/files/button-spec-file-generator.ts +33 -0
  51. package/src/packages/react-package/styled-components-react-package/files/eslint-config-file-generator.ts +18 -0
  52. package/src/packages/react-package/styled-components-react-package/files/index-file-generator.ts +9 -0
  53. package/src/packages/react-package/styled-components-react-package/files/prettier-config-file-generator.ts +11 -0
  54. package/src/packages/react-package/styled-components-react-package/files/tsconfig-file-generator.ts +15 -0
  55. package/src/packages/react-package/styled-components-react-package/files/tsup-config-file-generator.ts +21 -0
  56. package/src/packages/react-package/styled-components-react-package/files/vitest-config-file-generator.ts +23 -0
  57. package/src/packages/vite-react-app/create-vite-react-app.ts +79 -0
  58. package/src/packages/vite-react-app/files/app-file-generator.ts +28 -0
  59. package/src/packages/vite-react-app/files/eslint-config-file-generator.ts +11 -0
  60. package/src/packages/vite-react-app/files/index-html-file-generator.ts +20 -0
  61. package/src/packages/vite-react-app/files/main-file-generator.ts +14 -0
  62. package/src/packages/vite-react-app/files/prettier-config-file-generator.ts +11 -0
  63. package/src/packages/vite-react-app/files/tsconfig-file-generator.ts +15 -0
  64. package/src/packages/vite-react-app/files/vite-config-file-generator.ts +17 -0
  65. package/src/packages/vite-react-app/files/vitest-config-file-generator.ts +19 -0
  66. package/src/tsconfig/compiler-options.ts +83 -0
  67. package/src/tsconfig/index.ts +4 -0
  68. package/src/tsconfig/reference.ts +21 -0
  69. package/src/tsconfig/tsconfig.ts +137 -0
  70. package/src/unlink-packages.ts +47 -0
  71. package/src/utils/package-generator.ts +41 -0
  72. package/src/utils/package-type.ts +44 -0
  73. package/src/utils/package.ts +126 -0
  74. package/src/utils/style-type.ts +41 -0
  75. package/src/utils/utils.ts +28 -0
  76. package/src/utils/workspace.ts +78 -0
  77. package/src/workspace/create-workspace.ts +39 -0
  78. package/src/workspace/index.ts +1 -0
  79. package/src/workspace/root-package.ts +195 -0
  80. package/src/workspace/typescript-config.ts +84 -0
  81. package/tsconfig.json +14 -0
  82. package/tsup.config.ts +16 -0
@@ -0,0 +1,30 @@
1
+ import { FileGeneratorImp } from '../../../../file-generator/file-generator-imp';
2
+
3
+ const BUTTON = `import React, { HTMLAttributes } from 'react';
4
+ import styled from 'styled-components';
5
+
6
+ // This is your "Styled" version of the button
7
+ // No more imports, no more "empty objects"
8
+ const StyledButton = styled.button\`
9
+ background-color: #007bff;
10
+ color: white;
11
+ padding: 10px 20px;
12
+ border: none;
13
+ border-radius: 4px;
14
+ cursor: pointer;
15
+ font-size: 16px;
16
+
17
+ &:hover {
18
+ background-color: #0056b3;
19
+ }
20
+ \`;
21
+
22
+ export function Button(props: HTMLAttributes<HTMLButtonElement>) {
23
+ return <StyledButton {...props} />;
24
+ }
25
+ `;
26
+
27
+ export const BUTTON_FILE_GENERATOR = new FileGeneratorImp(
28
+ 'src/button.tsx',
29
+ BUTTON,
30
+ );
@@ -0,0 +1,33 @@
1
+ import { FileGeneratorImp } from '../../../../file-generator/file-generator-imp';
2
+
3
+ const BUTTON_SPEC = `import { render, screen, fireEvent } from '@testing-library/react';
4
+ import { Button } from './button';
5
+
6
+ describe('Button', () => {
7
+ it('renders the label correctly', () => {
8
+ render(<Button>Click Me</Button>);
9
+ expect(screen.getByText('Click Me')).toBeDefined();
10
+ });
11
+
12
+ it('is a button element', () => {
13
+ render(<Button>Submit</Button>);
14
+ const buttonElement = screen.getByRole('button');
15
+ expect(buttonElement.tagName).toBe('BUTTON');
16
+ });
17
+
18
+ /* Note: Testing for specific CSS classes with CSS Modules is tricky
19
+ because class names are mangled (e.g., _styledButton_123).
20
+ Usually, we just test that the class attribute exists.
21
+ */
22
+ it('applies a class name', () => {
23
+ render(<Button>Styled</Button>);
24
+ const buttonElement = screen.getByRole('button');
25
+ expect(buttonElement.className).toBeTruthy();
26
+ });
27
+ });
28
+ `;
29
+
30
+ export const BUTTON_SPEC_FILE_GENERATOR = new FileGeneratorImp(
31
+ 'src/button.spec.tsx',
32
+ BUTTON_SPEC,
33
+ );
@@ -0,0 +1,18 @@
1
+ import { FileGeneratorImp } from '../../../../file-generator/file-generator-imp';
2
+
3
+ const ESLINT_CONFIG = `import base from '@stack-dev/eslint-config/base.mjs';
4
+ import react from '@stack-dev/eslint-config/react.mjs';
5
+
6
+ export default [
7
+ ...base,
8
+ ...react,
9
+ {
10
+ ignores: ['**/dist/**', '**/coverage/**']
11
+ }
12
+ ];
13
+ `;
14
+
15
+ export const ESLINT_CONFIG_FILE_GENERATOR = new FileGeneratorImp(
16
+ 'eslint.config.mjs',
17
+ ESLINT_CONFIG,
18
+ );
@@ -0,0 +1,9 @@
1
+ import { FileGeneratorImp } from '../../../../file-generator/file-generator-imp';
2
+
3
+ const INDEX_TS = `export * from './button';
4
+ `;
5
+
6
+ export const INDEX_FILE_GENERATOR = new FileGeneratorImp(
7
+ 'src/index.ts',
8
+ INDEX_TS,
9
+ );
@@ -0,0 +1,11 @@
1
+ import { FileGeneratorImp } from '../../../../file-generator/file-generator-imp';
2
+
3
+ const PRETTIER_CONFIG = `import base from '@stack-dev/prettier-config/base.mjs';
4
+
5
+ export default base;
6
+ `;
7
+
8
+ export const PRETTIER_CONFIG_FILE_GENERATOR = new FileGeneratorImp(
9
+ 'prettier.config.mjs',
10
+ PRETTIER_CONFIG,
11
+ );
@@ -0,0 +1,15 @@
1
+ import { FileGeneratorImp } from '../../../../file-generator/file-generator-imp';
2
+
3
+ const TSCONFIG = `{
4
+ "extends": "@stack-dev/typescript-config/tsconfig.react.json",
5
+ "compilerOptions": {
6
+ "outDir": "dist"
7
+ },
8
+ "include": ["src"]
9
+ }
10
+ `;
11
+
12
+ export const TSCONFIG_FILE_GENERATOR = new FileGeneratorImp(
13
+ 'tsconfig.json',
14
+ TSCONFIG,
15
+ );
@@ -0,0 +1,21 @@
1
+ import { FileGeneratorImp } from '../../../../file-generator/file-generator-imp';
2
+
3
+ const TSUP_CONFIG = `import { defineConfig } from 'tsup';
4
+
5
+ export default defineConfig({
6
+ entry: ['src/index.ts'],
7
+ format: ['esm', 'cjs'],
8
+ dts: true,
9
+ clean: true,
10
+ external: ['react', 'react-dom', 'styled-components'],
11
+ outExtension({ format }) {
12
+ return {
13
+ js: format === 'esm' ? '.mjs' : '.js',
14
+ };
15
+ },
16
+ });`;
17
+
18
+ export const TSUP_CONFIG_FILE_GENERATOR = new FileGeneratorImp(
19
+ 'tsup.config.ts',
20
+ TSUP_CONFIG,
21
+ );
@@ -0,0 +1,23 @@
1
+ import { FileGeneratorImp } from '../../../../file-generator/file-generator-imp';
2
+
3
+ const VITEST_CONFIG = `import { defineConfig } from 'vitest/config';
4
+ import react from '@vitejs/plugin-react';
5
+
6
+ export default defineConfig({
7
+ plugins: [react()],
8
+ test: {
9
+ globals: true,
10
+ environment: 'jsdom',
11
+ coverage: {
12
+ provider: 'v8',
13
+ reporter: ['text', 'json', 'html'],
14
+ },
15
+ css: true,
16
+ },
17
+ });
18
+ `;
19
+
20
+ export const VITEST_CONFIG_FILE_GENERATOR = new FileGeneratorImp(
21
+ 'vitest.config.ts',
22
+ VITEST_CONFIG,
23
+ );
@@ -0,0 +1,79 @@
1
+ import path from 'path';
2
+ import { PackageJsonGenerator } from '../../file-generator';
3
+ import { Dependency, PackageJSON } from '../../package-json';
4
+ import { PackageGenerator } from '../../utils/package-generator';
5
+ import { getNamespace, getWorkspaceRoot } from '../../utils/workspace';
6
+
7
+ import { INDEX_HTML_FILE_GENERATOR } from './files/index-html-file-generator';
8
+ import { MAIN_FILE_GENERATOR } from './files/main-file-generator';
9
+ import { VITE_CONFIG_FILE_GENERATOR } from './files/vite-config-file-generator';
10
+
11
+ import { APP_FILE_GENERATOR } from './files/app-file-generator';
12
+ import { ESLINT_CONFIG_FILE_GENERATOR } from './files/eslint-config-file-generator';
13
+ import { PRETTIER_CONFIG_FILE_GENERATOR } from './files/prettier-config-file-generator';
14
+ import { TSCONFIG_FILE_GENERATOR } from './files/tsconfig-file-generator';
15
+ import { VITEST_CONFIG_FILE_GENERATOR } from './files/vitest-config-file-generator';
16
+
17
+ export async function createViteReactApp(name: string): Promise<void> {
18
+ const rootDir = await getWorkspaceRoot();
19
+ const directory = path.join(rootDir, 'apps', name);
20
+
21
+ const namespace = await getNamespace(rootDir);
22
+ const packageName = `${namespace}/${name}`;
23
+
24
+ console.log(`🚀 Creating Vite React App: ${packageName}`);
25
+
26
+ const generator = new PackageGenerator(
27
+ directory,
28
+ makeAppPackageGenerator(packageName, namespace),
29
+ [
30
+ VITE_CONFIG_FILE_GENERATOR,
31
+ INDEX_HTML_FILE_GENERATOR,
32
+ MAIN_FILE_GENERATOR,
33
+ APP_FILE_GENERATOR,
34
+ TSCONFIG_FILE_GENERATOR,
35
+ PRETTIER_CONFIG_FILE_GENERATOR,
36
+ ESLINT_CONFIG_FILE_GENERATOR,
37
+ VITEST_CONFIG_FILE_GENERATOR,
38
+ ],
39
+ );
40
+
41
+ await generator.generate();
42
+ }
43
+
44
+ function makeAppPackageGenerator(packageName: string, namespace: string) {
45
+ const packageJsonModel = new PackageJSON({
46
+ name: packageName,
47
+ dependencies: [
48
+ new Dependency('react', '^18.3.1'),
49
+ new Dependency('react-dom', '^18.3.1'),
50
+ ],
51
+ devDependencies: [
52
+ new Dependency(`${namespace}/eslint-config`, 'workspace:*'),
53
+ new Dependency(`${namespace}/prettier-config`, 'workspace:*'),
54
+ new Dependency(`${namespace}/typescript-config`, 'workspace:*'),
55
+ new Dependency('@types/react', '^18.3.1'),
56
+ new Dependency('@types/react-dom', '^18.3.1'),
57
+ new Dependency('@vitejs/plugin-react', '^4.3.1'),
58
+ new Dependency('vite', '^5.4.2'),
59
+ new Dependency('typescript', '^5.5.4'),
60
+ new Dependency('eslint', '^9.32.0'),
61
+ new Dependency('prettier', '^3.6.2'),
62
+ ],
63
+ additionalData: {
64
+ version: '0.1.0',
65
+ private: true,
66
+ type: 'module',
67
+ scripts: {
68
+ dev: 'vite',
69
+ build: 'tsc && vite build',
70
+ preview: 'vite preview',
71
+ start: 'pnpm run preview',
72
+ lint: 'eslint .',
73
+ format: 'prettier . --write',
74
+ },
75
+ },
76
+ });
77
+
78
+ return new PackageJsonGenerator(packageJsonModel, namespace);
79
+ }
@@ -0,0 +1,28 @@
1
+ import { FileGeneratorImp } from '../../../file-generator/file-generator-imp';
2
+
3
+ const APP = `import React, { useState } from 'react';
4
+
5
+ export function App() {
6
+ const [count, setCount] = useState(0);
7
+
8
+ return (
9
+ <div style={{
10
+ padding: '2rem',
11
+ fontFamily: 'system-ui, sans-serif',
12
+ textAlign: 'center'
13
+ }}>
14
+ <h1>Stack-Dev App</h1>
15
+ <div className="card">
16
+ <button onClick={function() { setCount(count + 1) }}>
17
+ Count is {count}
18
+ </button>
19
+ </div>
20
+ <p>
21
+ Edit <code>src/App.tsx</code> and save to test HMR
22
+ </p>
23
+ </div>
24
+ );
25
+ }
26
+ `;
27
+
28
+ export const APP_FILE_GENERATOR = new FileGeneratorImp('src/App.tsx', APP);
@@ -0,0 +1,11 @@
1
+ import { FileGeneratorImp } from '../../../file-generator/file-generator-imp';
2
+
3
+ const ESLINT_CONFIG = `import base from '@stack-dev/eslint-config/base.mjs';
4
+
5
+ export default [...base, { ignores: ['**/dist/**'] }];
6
+ `;
7
+
8
+ export const ESLINT_CONFIG_FILE_GENERATOR = new FileGeneratorImp(
9
+ 'eslint.config.mjs',
10
+ ESLINT_CONFIG,
11
+ );
@@ -0,0 +1,20 @@
1
+ import { FileGeneratorImp } from '../../../file-generator/file-generator-imp';
2
+
3
+ const INDEX_HTML = `<!doctype html>
4
+ <html lang="en">
5
+ <head>
6
+ <meta charset="UTF-8" />
7
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
8
+ <title>Vite + React + Stack-Dev</title>
9
+ </head>
10
+ <body>
11
+ <div id="root"></div>
12
+ <script type="module" src="/src/main.tsx"></script>
13
+ </body>
14
+ </html>
15
+ `;
16
+
17
+ export const INDEX_HTML_FILE_GENERATOR = new FileGeneratorImp(
18
+ 'index.html',
19
+ INDEX_HTML,
20
+ );
@@ -0,0 +1,14 @@
1
+ import { FileGeneratorImp } from '../../../file-generator/file-generator-imp';
2
+
3
+ const MAIN = `import React from 'react';
4
+ import ReactDOM from 'react-dom/client';
5
+ import { App } from './App';
6
+
7
+ ReactDOM.createRoot(document.getElementById('root')!).render(
8
+ <React.StrictMode>
9
+ <App />
10
+ </React.StrictMode>,
11
+ );
12
+ `;
13
+
14
+ export const MAIN_FILE_GENERATOR = new FileGeneratorImp('src/main.tsx', MAIN);
@@ -0,0 +1,11 @@
1
+ import { FileGeneratorImp } from '../../../file-generator/file-generator-imp';
2
+
3
+ const PRETTIER_CONFIG = `import base from '@stack-dev/prettier-config/base.mjs';
4
+
5
+ export default base;
6
+ `;
7
+
8
+ export const PRETTIER_CONFIG_FILE_GENERATOR = new FileGeneratorImp(
9
+ 'prettier.config.mjs',
10
+ PRETTIER_CONFIG,
11
+ );
@@ -0,0 +1,15 @@
1
+ import { FileGeneratorImp } from '../../../file-generator/file-generator-imp';
2
+
3
+ const TSCONFIG = `{
4
+ "extends": "@stack-dev/typescript-config/tsconfig.react.json",
5
+ "compilerOptions": {
6
+ "outDir": "dist"
7
+ },
8
+ "include": ["src"]
9
+ }
10
+ `;
11
+
12
+ export const TSCONFIG_FILE_GENERATOR = new FileGeneratorImp(
13
+ 'tsconfig.json',
14
+ TSCONFIG,
15
+ );
@@ -0,0 +1,17 @@
1
+ import { FileGeneratorImp } from '../../../file-generator/file-generator-imp';
2
+
3
+ const VITE_CONFIG = `import { defineConfig } from 'vite';
4
+ import react from '@vitejs/plugin-react';
5
+
6
+ export default defineConfig({
7
+ plugins: [react()],
8
+ server: {
9
+ port: 3000,
10
+ },
11
+ });
12
+ `;
13
+
14
+ export const VITE_CONFIG_FILE_GENERATOR = new FileGeneratorImp(
15
+ 'vite.config.ts',
16
+ VITE_CONFIG,
17
+ );
@@ -0,0 +1,19 @@
1
+ import { FileGeneratorImp } from '../../../file-generator/file-generator-imp';
2
+
3
+ const VITEST_CONFIG = `import { defineConfig } from 'vitest/config';
4
+
5
+ export default defineConfig({
6
+ test: {
7
+ globals: true,
8
+ coverage: {
9
+ provider: 'v8',
10
+ },
11
+ environment: 'node',
12
+ },
13
+ });
14
+ `;
15
+
16
+ export const VITEST_CONFIG_FILE_GENERATOR = new FileGeneratorImp(
17
+ 'vitest.config.ts',
18
+ VITEST_CONFIG,
19
+ );
@@ -0,0 +1,83 @@
1
+ import { Equalable, Snapshot, sortKeys } from '@stack-dev/core';
2
+
3
+ import { isEqual } from 'lodash';
4
+
5
+ export type ConstructorArgs = {
6
+ paths?: Record<string, ReadonlyArray<string>>;
7
+ additionalData?: Snapshot;
8
+ };
9
+
10
+ export class CompilerOptions implements Equalable {
11
+ private readonly _additionalData: Snapshot;
12
+
13
+ private readonly _paths: Record<string, ReadonlyArray<string>>;
14
+
15
+ public constructor(args?: ConstructorArgs) {
16
+ this._paths = args?.paths ?? {};
17
+ this._additionalData = args?.additionalData ?? {};
18
+ }
19
+
20
+ public get paths(): Record<string, ReadonlyArray<string>> {
21
+ return this._paths;
22
+ }
23
+
24
+ public get additionalData(): Snapshot {
25
+ return this._additionalData;
26
+ }
27
+
28
+ public setPaths(
29
+ paths: Record<string, ReadonlyArray<string>>,
30
+ ): CompilerOptions {
31
+ return new CompilerOptions({
32
+ paths,
33
+ additionalData: this._additionalData,
34
+ });
35
+ }
36
+
37
+ public format(): string {
38
+ const json = {
39
+ paths: this._paths,
40
+ ...this._additionalData,
41
+ };
42
+
43
+ const ordered = sortKeys(json, compareKeys);
44
+
45
+ return JSON.stringify(ordered, null, 2);
46
+ }
47
+
48
+ public equals(other: unknown): boolean {
49
+ // TODO: Get rid of lodash.
50
+
51
+ if (other instanceof CompilerOptions) {
52
+ return (
53
+ isEqual(this._paths, other._paths) &&
54
+ isEqual(this._additionalData, other._additionalData)
55
+ );
56
+ } else {
57
+ return false;
58
+ }
59
+ }
60
+ }
61
+
62
+ function compareKeys(a: string, b: string): number {
63
+ return getKeyIndex(a) - getKeyIndex(b);
64
+ }
65
+
66
+ function getKeyIndex(s: string): number {
67
+ const order = [
68
+ 'target',
69
+ 'module',
70
+ 'moduleResolution',
71
+ 'esModuleInterop',
72
+ 'lib',
73
+ 'types',
74
+ 'strict',
75
+ 'allowJs',
76
+ ];
77
+
78
+ if (order.every((key) => key !== s)) {
79
+ return Number.MAX_VALUE;
80
+ } else {
81
+ return order.indexOf(s);
82
+ }
83
+ }
@@ -0,0 +1,4 @@
1
+ export * from './tsconfig';
2
+
3
+ export * from './compiler-options';
4
+ export * from './reference';
@@ -0,0 +1,21 @@
1
+ import { Equalable } from '@stack-dev/core';
2
+
3
+ export class Reference implements Equalable {
4
+ private readonly _path: string;
5
+
6
+ public constructor(path: string) {
7
+ this._path = path;
8
+ }
9
+
10
+ public get path(): string {
11
+ return this._path;
12
+ }
13
+
14
+ equals(other: unknown): boolean {
15
+ if (other instanceof Reference) {
16
+ return this._path === other._path;
17
+ } else {
18
+ return false;
19
+ }
20
+ }
21
+ }
@@ -0,0 +1,137 @@
1
+ import * as JSON5 from 'json5';
2
+
3
+ import { Equalable, haveSameItems, sortKeys } from '@stack-dev/core';
4
+
5
+ import { Snapshot } from '@stack-dev/core';
6
+ import { isEqual } from 'lodash';
7
+ import { CompilerOptions } from './compiler-options';
8
+ import { Reference } from './reference';
9
+
10
+ type ConstructorArgs = {
11
+ compilerOptions?: CompilerOptions;
12
+ references?: ReadonlyArray<Reference>;
13
+ additionalData?: Snapshot;
14
+ };
15
+
16
+ export class TSConfig implements Equalable {
17
+ private readonly _compilerOptions: CompilerOptions;
18
+
19
+ private readonly _references: ReadonlyArray<Reference>;
20
+
21
+ private readonly _additionalData: Snapshot;
22
+
23
+ public constructor(args?: ConstructorArgs) {
24
+ this._compilerOptions = args?.compilerOptions ?? new CompilerOptions();
25
+ this._references = args?.references ?? [];
26
+ this._additionalData = args?.additionalData ?? {};
27
+ }
28
+
29
+ public get compilerOptions(): CompilerOptions {
30
+ return this._compilerOptions;
31
+ }
32
+
33
+ public addReference(reference: Reference): TSConfig {
34
+ return new TSConfig({
35
+ compilerOptions: this._compilerOptions,
36
+ references: [...this._references, reference],
37
+ additionalData: this._additionalData,
38
+ });
39
+ }
40
+
41
+ public setCompilerOptions(compilerOptions: CompilerOptions): TSConfig {
42
+ return new TSConfig({
43
+ compilerOptions,
44
+ references: this._references,
45
+ additionalData: this._additionalData,
46
+ });
47
+ }
48
+
49
+ public static parse(s: string): TSConfig {
50
+ const json = JSON5.parse(s);
51
+
52
+ const references = TSConfig.parseReferences(json);
53
+
54
+ const compilerOptions = new CompilerOptions({
55
+ paths: json.compilerOptions?.paths,
56
+ ...json.compilerOptions,
57
+ });
58
+
59
+ const additionalData = { ...json };
60
+ delete additionalData['compilerOptions'];
61
+ delete additionalData['references'];
62
+
63
+ return new TSConfig({
64
+ compilerOptions,
65
+ references,
66
+ additionalData,
67
+ });
68
+ }
69
+
70
+ private static parseReferences(json: unknown): ReadonlyArray<Reference> {
71
+ if (
72
+ typeof json === 'object' &&
73
+ json !== null &&
74
+ 'references' in json &&
75
+ json.references instanceof Array
76
+ ) {
77
+ return json.references.map(
78
+ (r: Record<string, string>) => new Reference(r.path),
79
+ );
80
+ } else {
81
+ return [];
82
+ }
83
+ }
84
+
85
+ public format(): string {
86
+ const compilerOptions = JSON5.parse(this.compilerOptions.format());
87
+
88
+ const json = {
89
+ compilerOptions,
90
+ references: this._references.map((r) => ({ path: r.path })),
91
+ ...this._additionalData,
92
+ };
93
+
94
+ const ordered = sortKeys(json, compareKeys);
95
+
96
+ return JSON.stringify(ordered, null, 2);
97
+ }
98
+
99
+ public equals(other: unknown): boolean {
100
+ if (other instanceof TSConfig) {
101
+ const sameReferences = haveSameItems(
102
+ this._references,
103
+ other._references,
104
+ (r1, r2) => r1.equals(r2),
105
+ );
106
+
107
+ return (
108
+ this._compilerOptions.equals(other._compilerOptions) &&
109
+ sameReferences &&
110
+ isEqual(this._additionalData, other._additionalData)
111
+ );
112
+ } else {
113
+ return false;
114
+ }
115
+ }
116
+ }
117
+
118
+ // TODO: orderKeys (by array);
119
+ function compareKeys(a: string, b: string): number {
120
+ return getKeyIndex(a) - getKeyIndex(b);
121
+ }
122
+
123
+ function getKeyIndex(s: string): number {
124
+ const order = [
125
+ 'extends',
126
+ 'compilerOptions',
127
+ 'include',
128
+ 'exclude',
129
+ 'references',
130
+ ];
131
+
132
+ if (order.every((key) => key !== s)) {
133
+ return Number.MAX_VALUE;
134
+ } else {
135
+ return order.indexOf(s);
136
+ }
137
+ }