@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,3 @@
1
+ import base from '@stack-dev/eslint-config/base.mjs';
2
+
3
+ export default [...base, { ignores: ['**/dist/**'] }];
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "@stack-dev/cli",
3
+ "version": "0.1.0",
4
+ "bin": {
5
+ "stack": "./dist/index.js"
6
+ },
7
+ "dependencies": {
8
+ "commander": "^11.1.0",
9
+ "enquirer": "^2.4.1",
10
+ "fast-glob": "^3.3.3",
11
+ "glob": "^11.0.3",
12
+ "json5": "^2.2.3",
13
+ "lodash": "^4.17.21",
14
+ "yaml": "^2.8.0",
15
+ "@stack-dev/core": "0.1.0"
16
+ },
17
+ "devDependencies": {
18
+ "@types/lodash": "^4.17.20",
19
+ "@types/node": "^24.0.15",
20
+ "eslint": "^9.32.0",
21
+ "prettier": "^3.6.2",
22
+ "prettier-plugin-organize-imports": "^4.2.0",
23
+ "tsup": "^7.3.0",
24
+ "@stack-dev/prettier-config": "0.1.0",
25
+ "@stack-dev/eslint-config": "0.1.0",
26
+ "@stack-dev/typescript-config": "0.1.0"
27
+ },
28
+ "scripts": {
29
+ "build": "tsup src/index.ts --dts --format esm,cjs",
30
+ "start": "node dist/index.js",
31
+ "check-types": "tsc --noEmit",
32
+ "lint": "eslint .",
33
+ "format": "prettier . --write"
34
+ }
35
+ }
@@ -0,0 +1,3 @@
1
+ import base from '@stack-dev/prettier-config/base.mjs';
2
+
3
+ export default base;
@@ -0,0 +1,20 @@
1
+ import { FileGenerator } from './file-generator';
2
+
3
+ export class FileGeneratorImp implements FileGenerator {
4
+ private readonly _filepath: string;
5
+
6
+ private readonly _contents: string;
7
+
8
+ public constructor(filepath: string, contents: string) {
9
+ this._filepath = filepath;
10
+ this._contents = contents;
11
+ }
12
+
13
+ public get filepath(): string {
14
+ return this._filepath;
15
+ }
16
+
17
+ public async generate(): Promise<string> {
18
+ return this._contents;
19
+ }
20
+ }
@@ -0,0 +1,5 @@
1
+ export interface FileGenerator {
2
+ readonly filepath: string;
3
+
4
+ generate(): Promise<string>;
5
+ }
@@ -0,0 +1,3 @@
1
+ export * from './file-generator';
2
+
3
+ export * from './package-json-generator';
@@ -0,0 +1,23 @@
1
+ import { FileGenerator } from './file-generator';
2
+ import { PackageJSON } from '../package-json';
3
+
4
+ export class PackageJsonGenerator implements FileGenerator {
5
+ private readonly _packageJson: PackageJSON;
6
+ private readonly _namespace: string;
7
+
8
+ public constructor(
9
+ packageJson: PackageJSON,
10
+ namespace: string = ''
11
+ ) {
12
+ this._packageJson = packageJson;
13
+ this._namespace = namespace;
14
+ }
15
+
16
+ public get filepath(): string {
17
+ return 'package.json';
18
+ }
19
+
20
+ public async generate(): Promise<string> {
21
+ return this._packageJson.format(this._namespace);
22
+ }
23
+ }
package/src/index.ts ADDED
@@ -0,0 +1,185 @@
1
+ import {
2
+ createConfigPackage,
3
+ createLibraryPackage,
4
+ createReactPackage,
5
+ } from './packages';
6
+ import {
7
+ comparePackages,
8
+ getAllPackages,
9
+ getCurrentPackage,
10
+ getPackageByName,
11
+ } from './utils/package';
12
+ import { packageTypes, pickPackageType } from './utils/package-type';
13
+ import { pickStyleType, styleTypes } from './utils/style-type';
14
+
15
+ import { Command } from 'commander';
16
+ import { prompt } from 'enquirer';
17
+ import { linkPackages } from './link-packages';
18
+ import { createViteReactApp } from './packages/vite-react-app/create-vite-react-app';
19
+ import { unlinkPackages } from './unlink-packages';
20
+ import { getDirectoryPackageJson } from './utils/utils';
21
+ import { getNamespace } from './utils/workspace';
22
+ import { createWorkspace } from './workspace';
23
+
24
+ const program = new Command();
25
+
26
+ program
27
+ .name('stack')
28
+ .description('Opinionated TypeScript workspace manager')
29
+ .version('0.1.0');
30
+
31
+ program
32
+ .command('create <name>')
33
+ .description('Create a new workspace')
34
+ .option(
35
+ '-o, --output <dir>',
36
+ 'Target directory to create the workspace in',
37
+ '.',
38
+ )
39
+ .action(async (name, options) => {
40
+ const output = options.output ?? process.cwd();
41
+
42
+ await createWorkspace(name, output);
43
+ });
44
+
45
+ program
46
+ .command('g <name>')
47
+ .description('Generate a new package or app')
48
+ .option(
49
+ '-t, --type <type>',
50
+ `Type of package to generate (${packageTypes.join(', ')})`,
51
+ )
52
+ .option('--style <style>', `Styling system to use (${styleTypes.join(', ')})`)
53
+ .action(async (name, options) => {
54
+ const type = await pickPackageType(options);
55
+
56
+ switch (type) {
57
+ case 'library':
58
+ await createLibraryPackage(name);
59
+ break;
60
+ case 'config':
61
+ await createConfigPackage(name);
62
+ break;
63
+ case 'react':
64
+ await createReactPackage(name, await pickStyleType(options));
65
+ break;
66
+ case 'vite':
67
+ await createViteReactApp(name);
68
+ break;
69
+ case 'cli':
70
+ // await createCliPackage(name)
71
+ break;
72
+ case 'fastify':
73
+ // await createFastifyApp(name)
74
+ break;
75
+ case 'next':
76
+ // await createNextPackage(name)
77
+ break;
78
+ }
79
+
80
+ console.log('');
81
+ console.log('Run pnpm install to finish linking.');
82
+ });
83
+
84
+ program
85
+ .command('link [name]')
86
+ .alias('l')
87
+ .option('-D, --dev', 'Whether to link as a devDependency.', false)
88
+ .description('Link to the specified package')
89
+ .action(async (name, options) => {
90
+ name = name ?? (await promptForPackageToLinkTo());
91
+
92
+ const development = options.dev ?? false;
93
+
94
+ if (!isValidPackageName(name)) {
95
+ throw new Error(`Package name "${name}" is not a valid option.`);
96
+ }
97
+
98
+ const current = await getCurrentPackage();
99
+ const target = await getPackageByName(name);
100
+
101
+ await linkPackages(current, target, development);
102
+
103
+ console.log('');
104
+ console.log('Run pnpm install to finish linking.');
105
+ });
106
+
107
+ program
108
+ .command('unlink [name]')
109
+ .alias('u')
110
+ .description('Unlink the specified package')
111
+ .action(async (name) => {
112
+ name = name ?? (await promptForPackageToUnlinkFrom());
113
+
114
+ if (!(await isValidPackageName(name))) {
115
+ throw new Error(`Package name "${name}" is not a valid option.`);
116
+ }
117
+
118
+ const current = await getCurrentPackage();
119
+ const target = await getPackageByName(name);
120
+
121
+ await unlinkPackages(current, target);
122
+ });
123
+
124
+ async function promptForPackageToLinkTo(): Promise<string> {
125
+ const options = await getAllPackages();
126
+ const currentPackage = await getCurrentPackage();
127
+
128
+ const linked = await getLinkedPackageNames();
129
+
130
+ const validOptions = options
131
+ .filter((o) => o.name !== currentPackage.name)
132
+ .filter((o) => !linked.has(o.name))
133
+ .toSorted(comparePackages);
134
+
135
+ const response = await prompt<{ packageName: string }>({
136
+ type: 'select',
137
+ name: 'packageName',
138
+ message: 'What package do you want to link to?',
139
+ choices: [...validOptions],
140
+ });
141
+
142
+ return response.packageName;
143
+ }
144
+
145
+ async function promptForPackageToUnlinkFrom(): Promise<string> {
146
+ const validOptions = await getLinkedPackageNames();
147
+
148
+ const response = await prompt<{ packageName: string }>({
149
+ type: 'select',
150
+ name: 'packageName',
151
+ message: 'What package do you want to unlink from?',
152
+ choices: [...validOptions],
153
+ });
154
+
155
+ return response.packageName;
156
+ }
157
+
158
+ async function getLinkedPackageNames(): Promise<Set<string>> {
159
+ const currentPackage = await getCurrentPackage();
160
+ const packageJSON = await getDirectoryPackageJson(currentPackage.directory);
161
+ const namespace = await getNamespace();
162
+
163
+ const names = [...packageJSON.dependencies, ...packageJSON.devDependencies]
164
+ .map((d) => d.name)
165
+ .filter((n) => n.startsWith(namespace));
166
+
167
+ return new Set(names);
168
+ }
169
+
170
+ program.parse();
171
+
172
+ async function isValidPackageName(packageName: string): Promise<boolean> {
173
+ const options = await getValidPackageNames();
174
+
175
+ return options.some((o) => o === packageName);
176
+ }
177
+
178
+ async function getValidPackageNames(): Promise<ReadonlyArray<string>> {
179
+ const options = await getAllPackages();
180
+ const currentPackage = await getCurrentPackage();
181
+
182
+ return options
183
+ .filter((o) => o.name !== currentPackage.name)
184
+ .map((o) => o.name);
185
+ }
@@ -0,0 +1,65 @@
1
+ import { Dependency, PackageJSON } from './package-json';
2
+ import { getDirectoryPackageJson, getPackageJSONPath } from './utils/utils';
3
+
4
+ import fs from 'node:fs/promises';
5
+ import path from 'node:path';
6
+ import { TSConfig } from './tsconfig';
7
+ import { Package } from './utils/package';
8
+ import { getNamespace } from './utils/workspace';
9
+
10
+ export async function linkPackages(
11
+ current: Package,
12
+ target: Package,
13
+ development: boolean,
14
+ ): Promise<void> {
15
+ await updatePackageJSON(current, target, development);
16
+ await updateTSConfig(current, target);
17
+ }
18
+
19
+ async function updatePackageJSON(
20
+ current: Package,
21
+ target: Package,
22
+ development: boolean,
23
+ ) {
24
+ const namespace = await getNamespace();
25
+
26
+ const packageJSON = await getDirectoryPackageJson(current.directory);
27
+ const updated = addDependency(packageJSON, target, development);
28
+
29
+ const packageJSONPath = getPackageJSONPath(current.directory);
30
+
31
+ await fs.writeFile(packageJSONPath, updated.format(namespace));
32
+ }
33
+
34
+ function addDependency(
35
+ packageJSON: PackageJSON,
36
+ target: Package,
37
+ development: boolean,
38
+ ): PackageJSON {
39
+ const dependency = new Dependency(target.name, 'workspace:*');
40
+
41
+ if (development) {
42
+ return packageJSON.addDevDependency(dependency);
43
+ } else {
44
+ return packageJSON.addDependency(dependency);
45
+ }
46
+ }
47
+
48
+ async function updateTSConfig(current: Package, target: Package) {
49
+ const tsconfigPath = path.join(current.directory, 'tsconfig.json');
50
+ const tsconfigContents = await fs.readFile(tsconfigPath, 'utf8');
51
+ const tsconfig = TSConfig.parse(tsconfigContents);
52
+
53
+ const targetDirectory = path.join(target.directory, 'src', 'index.ts');
54
+
55
+ const updatedPaths = {
56
+ ...tsconfig.compilerOptions.paths,
57
+ [target.name]: [path.relative(current.directory, targetDirectory)],
58
+ };
59
+ const updatedCompilerOptions =
60
+ tsconfig.compilerOptions.setPaths(updatedPaths);
61
+
62
+ const updated = tsconfig.setCompilerOptions(updatedCompilerOptions);
63
+
64
+ await fs.writeFile(tsconfigPath, updated.format());
65
+ }
@@ -0,0 +1,28 @@
1
+ import { Equalable } from '@stack-dev/core';
2
+
3
+ export class Dependency implements Equalable {
4
+ private readonly _name: string;
5
+
6
+ private readonly _version: string;
7
+
8
+ public constructor(name: string, version: string) {
9
+ this._name = name;
10
+ this._version = version;
11
+ }
12
+
13
+ public get name(): string {
14
+ return this._name;
15
+ }
16
+
17
+ public get version(): string {
18
+ return this._version;
19
+ }
20
+
21
+ public equals(other: unknown): boolean {
22
+ if (other instanceof Dependency) {
23
+ return this._name === other._name && this._version === other._version;
24
+ } else {
25
+ return false;
26
+ }
27
+ }
28
+ }
@@ -0,0 +1,3 @@
1
+ export * from './package-json';
2
+
3
+ export * from './dependency';
@@ -0,0 +1,269 @@
1
+ import { Equalable, haveSameItems, sortKeys } from '@stack-dev/core';
2
+
3
+ import { Snapshot } from '@stack-dev/core';
4
+ import JSON5 from 'json5';
5
+ import { isEqual } from 'lodash';
6
+ import { Dependency } from './dependency';
7
+
8
+ export type ConstructorArgs = {
9
+ name: string;
10
+ dependencies?: ReadonlyArray<Dependency>;
11
+ devDependencies?: ReadonlyArray<Dependency>;
12
+ peerDependencies?: ReadonlyArray<Dependency>;
13
+ additionalData?: Snapshot;
14
+ };
15
+
16
+ export class PackageJSON implements Equalable {
17
+ private readonly _name: string;
18
+
19
+ private readonly _dependencies: ReadonlyArray<Dependency>;
20
+
21
+ private readonly _devDependencies: ReadonlyArray<Dependency>;
22
+
23
+ private readonly _peerDependencies: ReadonlyArray<Dependency>;
24
+
25
+ private readonly _additionalData: Snapshot;
26
+
27
+ public constructor(args: ConstructorArgs) {
28
+ this._name = args.name;
29
+ this._dependencies = args.dependencies ?? [];
30
+ this._devDependencies = args.devDependencies ?? [];
31
+ this._peerDependencies = args.peerDependencies ?? [];
32
+ this._additionalData = args.additionalData ?? {};
33
+ }
34
+
35
+ public get name(): string {
36
+ return this._name;
37
+ }
38
+
39
+ public get dependencies(): ReadonlyArray<Dependency> {
40
+ return this._dependencies;
41
+ }
42
+
43
+ public get devDependencies(): ReadonlyArray<Dependency> {
44
+ return this._devDependencies;
45
+ }
46
+
47
+ public get peerDependencies(): ReadonlyArray<Dependency> {
48
+ return this._peerDependencies;
49
+ }
50
+
51
+ public addDependency(dependency: Dependency): PackageJSON {
52
+ return new PackageJSON({
53
+ name: this.name,
54
+ dependencies: [...this.dependencies, dependency],
55
+ devDependencies: this.devDependencies,
56
+ additionalData: this._additionalData,
57
+ });
58
+ }
59
+
60
+ public addDevDependency(dependency: Dependency): PackageJSON {
61
+ return new PackageJSON({
62
+ name: this.name,
63
+ dependencies: this.dependencies,
64
+ devDependencies: [...this.devDependencies, dependency],
65
+ additionalData: this._additionalData,
66
+ });
67
+ }
68
+
69
+ public addPeerDependency(dependency: Dependency): PackageJSON {
70
+ return new PackageJSON({
71
+ name: this.name,
72
+ dependencies: this.dependencies,
73
+ devDependencies: this.devDependencies,
74
+ peerDependencies: [...this.peerDependencies, dependency],
75
+ additionalData: this._additionalData,
76
+ })
77
+ }
78
+
79
+ public removeDependency(name: string): PackageJSON {
80
+ return new PackageJSON({
81
+ name: this.name,
82
+ dependencies: this.dependencies.filter((d) => d.name !== name),
83
+ devDependencies: this.devDependencies,
84
+ additionalData: this._additionalData,
85
+ });
86
+ }
87
+
88
+ public removeDevDependency(name: string): PackageJSON {
89
+ return new PackageJSON({
90
+ name: this.name,
91
+ dependencies: this.dependencies,
92
+ devDependencies: this.devDependencies.filter((d) => d.name !== name),
93
+ additionalData: this._additionalData,
94
+ });
95
+ }
96
+
97
+ public removePeerDependency(name: string): PackageJSON {
98
+ return new PackageJSON({
99
+ name: this.name,
100
+ dependencies: this.dependencies,
101
+ devDependencies: this.devDependencies,
102
+ peerDependencies: this.peerDependencies.filter((d) => d.name !== name),
103
+ additionalData: this._additionalData,
104
+ });
105
+ }
106
+
107
+ public static parse(s: string): PackageJSON {
108
+ const json = JSON5.parse(s);
109
+
110
+ const name = json.name;
111
+ const dependencies = PackageJSON.parseDependencies(json);
112
+ const devDependencies = PackageJSON.parseDevDependencies(json);
113
+ const peerDependencies = PackageJSON.parsePeerDependencies(json);
114
+
115
+ const additionalData = { ...json };
116
+ delete additionalData['name'];
117
+ delete additionalData['dependencies'];
118
+ delete additionalData['devDependencies'];
119
+ delete additionalData['peerDependencies'];
120
+
121
+ return new PackageJSON({
122
+ name,
123
+ dependencies,
124
+ devDependencies,
125
+ peerDependencies,
126
+ additionalData,
127
+ });
128
+ }
129
+
130
+ private static parseDependencies(json: Snapshot) {
131
+ if ('dependencies' in json && typeof json.dependencies === 'object') {
132
+ return Object.entries(json.dependencies).map(
133
+ ([name, version]) => new Dependency(name, version as string),
134
+ );
135
+ } else {
136
+ return [];
137
+ }
138
+ }
139
+
140
+ private static parseDevDependencies(json: Snapshot) {
141
+ if ('devDependencies' in json && typeof json.devDependencies === 'object') {
142
+ return Object.entries(json.devDependencies).map(
143
+ ([name, version]) => new Dependency(name, version as string),
144
+ );
145
+ } else {
146
+ return [];
147
+ }
148
+ }
149
+
150
+ private static parsePeerDependencies(json: Snapshot) {
151
+ if ('peerDependencies' in json && typeof json.peerDependencies === 'object') {
152
+ return Object.entries(json.peerDependencies).map(
153
+ ([name, version]) => new Dependency(name, version as string),
154
+ );
155
+ } else {
156
+ return [];
157
+ }
158
+ }
159
+
160
+ public format(namespace: string): string {
161
+ const json = {
162
+ name: this._name,
163
+ dependencies: makeDependencyObject(this._dependencies, namespace),
164
+ devDependencies: makeDependencyObject(this._devDependencies, namespace),
165
+ peerDependencies: makeDependencyObject(this._peerDependencies, namespace),
166
+ ...this._additionalData,
167
+ };
168
+
169
+ const ordered = sortKeys(json, comparePackageJSONKeys);
170
+
171
+ return JSON.stringify(ordered, null, 2);
172
+ }
173
+
174
+ public equals(other: unknown): boolean {
175
+ if (other instanceof PackageJSON) {
176
+ const sameDependencies = haveSameItems(
177
+ this._dependencies,
178
+ other._dependencies,
179
+ (d1, d2) => d1.equals(d2),
180
+ );
181
+
182
+ const sameDevDependencies = haveSameItems(
183
+ this._devDependencies,
184
+ other._devDependencies,
185
+ (d1, d2) => d1.equals(d2),
186
+ );
187
+
188
+ const samePeerDependencies = haveSameItems(
189
+ this._peerDependencies,
190
+ other._peerDependencies,
191
+ (d1, d2) => d1.equals(d2),
192
+ )
193
+
194
+ return (
195
+ this._name === other._name &&
196
+ sameDependencies &&
197
+ sameDevDependencies &&
198
+ samePeerDependencies &&
199
+ isEqual(this._additionalData, other._additionalData)
200
+ );
201
+ } else {
202
+ return false;
203
+ }
204
+ }
205
+ }
206
+
207
+ function makeDependencyObject(
208
+ dependencies: ReadonlyArray<Dependency>,
209
+ namespace: string,
210
+ ): Record<string, string> | undefined {
211
+ if (dependencies.length === 0) {
212
+ return undefined;
213
+ }
214
+
215
+ const result: Record<string, string> = {};
216
+
217
+ dependencies
218
+ .toSorted((a, b) => comparePackageNames(a.name, b.name, namespace))
219
+ .forEach((d) => (result[d.name] = d.version));
220
+
221
+ return result;
222
+ }
223
+
224
+ function comparePackageNames(a: string, b: string, namespace: string): number {
225
+ if (a.startsWith(namespace) && b.startsWith(namespace)) {
226
+ return a.localeCompare(b);
227
+ } else if (a.startsWith(namespace)) {
228
+ return -1;
229
+ } else if (b.startsWith(namespace)) {
230
+ return 1;
231
+ } else {
232
+ return a.localeCompare(b);
233
+ }
234
+ }
235
+
236
+ function comparePackageJSONKeys(a: string, b: string): number {
237
+ return getKeyIndex(a) - getKeyIndex(b);
238
+ }
239
+
240
+ function getKeyIndex(s: string): number {
241
+ switch (s.toLowerCase()) {
242
+ case 'name':
243
+ return 0;
244
+ case 'version':
245
+ return 1;
246
+ case 'private':
247
+ return 2;
248
+ case 'bin':
249
+ return 3;
250
+ case 'main':
251
+ return 4;
252
+ case 'module':
253
+ return 5;
254
+ case 'types':
255
+ return 6;
256
+ case 'exports':
257
+ return 7;
258
+ case 'scripts':
259
+ return 8;
260
+ case 'dependencies':
261
+ return 9;
262
+ case 'devDependencies':
263
+ return 10;
264
+ case 'peerdependencies':
265
+ return 11;
266
+ default:
267
+ return Number.MAX_VALUE;
268
+ }
269
+ }
@@ -0,0 +1,29 @@
1
+ import path from 'node:path';
2
+ import { PackageJSON } from '../package-json';
3
+ import { PackageJsonGenerator } from '../file-generator';
4
+ import { PackageGenerator } from '../utils/package-generator';
5
+ import { getNamespace, getWorkspaceRoot } from '../utils/workspace';
6
+
7
+ export async function createConfigPackage(name: string): Promise<void> {
8
+ const rootDir = await getWorkspaceRoot();
9
+ const directory = path.join(rootDir, 'configs', name);
10
+
11
+ const namespace = await getNamespace(rootDir);
12
+ const packageName = `${namespace}/${name}`;
13
+
14
+ const packageJsonModel = new PackageJSON({
15
+ name: packageName,
16
+ additionalData: {
17
+ version: '0.1.0',
18
+ private: true,
19
+ },
20
+ });
21
+
22
+ const generator = new PackageGenerator(
23
+ directory,
24
+ new PackageJsonGenerator(packageJsonModel, namespace),
25
+ [],
26
+ );
27
+
28
+ await generator.generate();
29
+ }
@@ -0,0 +1,4 @@
1
+ export * from './library-package';
2
+ export * from './react-package';
3
+
4
+ export * from './create-config-package';