@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.
- package/.turbo/daemon/6fa76abe2aa470d0-turbo.log.2025-08-02 +5 -0
- package/.turbo/daemon/6fa76abe2aa470d0-turbo.log.2025-12-29 +1 -0
- package/.turbo/daemon/6fa76abe2aa470d0-turbo.log.2025-12-30 +0 -0
- package/.turbo/turbo-build.log +21 -0
- package/.turbo/turbo-check-types.log +5 -0
- package/dist/index.d.mts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2097 -0
- package/dist/index.mjs +2073 -0
- package/eslint.config.mjs +3 -0
- package/package.json +35 -0
- package/prettier.config.mjs +3 -0
- package/src/file-generator/file-generator-imp.ts +20 -0
- package/src/file-generator/file-generator.ts +5 -0
- package/src/file-generator/index.ts +3 -0
- package/src/file-generator/package-json-generator.ts +23 -0
- package/src/index.ts +185 -0
- package/src/link-packages.ts +65 -0
- package/src/package-json/dependency.ts +28 -0
- package/src/package-json/index.ts +3 -0
- package/src/package-json/package-json.ts +269 -0
- package/src/packages/create-config-package.ts +29 -0
- package/src/packages/index.ts +4 -0
- package/src/packages/library-package/create-library-package.ts +85 -0
- package/src/packages/library-package/files/add-file-generator.ts +8 -0
- package/src/packages/library-package/files/add-spec-file-generator.ts +17 -0
- package/src/packages/library-package/files/eslint-config-file-generator.ts +11 -0
- package/src/packages/library-package/files/index-file-generator.ts +9 -0
- package/src/packages/library-package/files/prettier-config-file-generator.ts +11 -0
- package/src/packages/library-package/files/tsconfig-file-generator.ts +15 -0
- package/src/packages/library-package/files/tsup-config-file-generator.ts +23 -0
- package/src/packages/library-package/files/vitest-config-file-generator.ts +19 -0
- package/src/packages/library-package/index.ts +1 -0
- package/src/packages/react-package/create-react-package.ts +25 -0
- package/src/packages/react-package/create-tailwind-react-package.ts +30 -0
- package/src/packages/react-package/create-unstyled-react-package.ts +3 -0
- package/src/packages/react-package/css-react-package/create-css-react-package.ts +103 -0
- package/src/packages/react-package/css-react-package/files/button-css-module-file-generator.ts +16 -0
- package/src/packages/react-package/css-react-package/files/button-file-generator.ts +14 -0
- package/src/packages/react-package/css-react-package/files/button-spec-file-generator.ts +33 -0
- package/src/packages/react-package/css-react-package/files/eslint-config-file-generator.ts +18 -0
- package/src/packages/react-package/css-react-package/files/index-file-generator.ts +9 -0
- package/src/packages/react-package/css-react-package/files/prettier-config-file-generator.ts +11 -0
- package/src/packages/react-package/css-react-package/files/tsconfig-file-generator.ts +15 -0
- package/src/packages/react-package/css-react-package/files/tsup-config-file-generator.ts +24 -0
- package/src/packages/react-package/css-react-package/files/vitest-config-file-generator.ts +23 -0
- package/src/packages/react-package/index.ts +1 -0
- package/src/packages/react-package/styled-components-react-package/create-styled-components-react-package.ts +112 -0
- package/src/packages/react-package/styled-components-react-package/files/button-file-generator.ts +30 -0
- package/src/packages/react-package/styled-components-react-package/files/button-spec-file-generator.ts +33 -0
- package/src/packages/react-package/styled-components-react-package/files/eslint-config-file-generator.ts +18 -0
- package/src/packages/react-package/styled-components-react-package/files/index-file-generator.ts +9 -0
- package/src/packages/react-package/styled-components-react-package/files/prettier-config-file-generator.ts +11 -0
- package/src/packages/react-package/styled-components-react-package/files/tsconfig-file-generator.ts +15 -0
- package/src/packages/react-package/styled-components-react-package/files/tsup-config-file-generator.ts +21 -0
- package/src/packages/react-package/styled-components-react-package/files/vitest-config-file-generator.ts +23 -0
- package/src/packages/vite-react-app/create-vite-react-app.ts +79 -0
- package/src/packages/vite-react-app/files/app-file-generator.ts +28 -0
- package/src/packages/vite-react-app/files/eslint-config-file-generator.ts +11 -0
- package/src/packages/vite-react-app/files/index-html-file-generator.ts +20 -0
- package/src/packages/vite-react-app/files/main-file-generator.ts +14 -0
- package/src/packages/vite-react-app/files/prettier-config-file-generator.ts +11 -0
- package/src/packages/vite-react-app/files/tsconfig-file-generator.ts +15 -0
- package/src/packages/vite-react-app/files/vite-config-file-generator.ts +17 -0
- package/src/packages/vite-react-app/files/vitest-config-file-generator.ts +19 -0
- package/src/tsconfig/compiler-options.ts +83 -0
- package/src/tsconfig/index.ts +4 -0
- package/src/tsconfig/reference.ts +21 -0
- package/src/tsconfig/tsconfig.ts +137 -0
- package/src/unlink-packages.ts +47 -0
- package/src/utils/package-generator.ts +41 -0
- package/src/utils/package-type.ts +44 -0
- package/src/utils/package.ts +126 -0
- package/src/utils/style-type.ts +41 -0
- package/src/utils/utils.ts +28 -0
- package/src/utils/workspace.ts +78 -0
- package/src/workspace/create-workspace.ts +39 -0
- package/src/workspace/index.ts +1 -0
- package/src/workspace/root-package.ts +195 -0
- package/src/workspace/typescript-config.ts +84 -0
- package/tsconfig.json +14 -0
- package/tsup.config.ts +16 -0
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { getDirectoryPackageJson, getPackageJSONPath } from './utils/utils';
|
|
2
|
+
|
|
3
|
+
import fs from 'node:fs/promises';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { TSConfig } from './tsconfig';
|
|
6
|
+
import { Package } from './utils/package';
|
|
7
|
+
import { getNamespace } from './utils/workspace';
|
|
8
|
+
|
|
9
|
+
export async function unlinkPackages(
|
|
10
|
+
current: Package,
|
|
11
|
+
target: Package,
|
|
12
|
+
): Promise<void> {
|
|
13
|
+
await updatePackageJSON(current, target);
|
|
14
|
+
await updateTSConfig(current, target);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async function updatePackageJSON(current: Package, target: Package) {
|
|
18
|
+
const namespace = await getNamespace();
|
|
19
|
+
|
|
20
|
+
const packageJSON = await getDirectoryPackageJson(current.directory);
|
|
21
|
+
const updated = packageJSON
|
|
22
|
+
.removeDependency(target.name)
|
|
23
|
+
.removeDevDependency(target.name);
|
|
24
|
+
|
|
25
|
+
const packageJSONPath = getPackageJSONPath(current.directory);
|
|
26
|
+
|
|
27
|
+
await fs.writeFile(packageJSONPath, updated.format(namespace));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async function updateTSConfig(current: Package, target: Package) {
|
|
31
|
+
const tsconfigPath = path.join(current.directory, 'tsconfig.json');
|
|
32
|
+
const tsconfigContents = await fs.readFile(tsconfigPath, 'utf8');
|
|
33
|
+
const tsconfig = TSConfig.parse(tsconfigContents);
|
|
34
|
+
|
|
35
|
+
const updatedPaths = Object.fromEntries(
|
|
36
|
+
Object.entries(tsconfig.compilerOptions.paths).filter(
|
|
37
|
+
([key]) => key !== target.name,
|
|
38
|
+
),
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
const updatedCompilerOptions =
|
|
42
|
+
tsconfig.compilerOptions.setPaths(updatedPaths);
|
|
43
|
+
|
|
44
|
+
const updated = tsconfig.setCompilerOptions(updatedCompilerOptions);
|
|
45
|
+
|
|
46
|
+
await fs.writeFile(tsconfigPath, updated.format());
|
|
47
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { FileGenerator, PackageJsonGenerator } from '../file-generator';
|
|
4
|
+
|
|
5
|
+
export class PackageGenerator {
|
|
6
|
+
private readonly _root: string;
|
|
7
|
+
|
|
8
|
+
private readonly _packageJson: PackageJsonGenerator;
|
|
9
|
+
|
|
10
|
+
private readonly _fileGenerators: ReadonlyArray<FileGenerator>;
|
|
11
|
+
|
|
12
|
+
public constructor(
|
|
13
|
+
root: string,
|
|
14
|
+
packageJson: PackageJsonGenerator,
|
|
15
|
+
fileGenerators: ReadonlyArray<FileGenerator> = [],
|
|
16
|
+
) {
|
|
17
|
+
this._root = root;
|
|
18
|
+
this._packageJson = packageJson;
|
|
19
|
+
this._fileGenerators = fileGenerators;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
public async generate(): Promise<void> {
|
|
23
|
+
const all = [this._packageJson, ...this._fileGenerators];
|
|
24
|
+
|
|
25
|
+
await fs.mkdir(this._root, { recursive: true });
|
|
26
|
+
|
|
27
|
+
await Promise.all(all.map((gen) => this.write(gen)));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
private async write(fileGenerator: FileGenerator): Promise<void> {
|
|
31
|
+
const contents = await fileGenerator.generate();
|
|
32
|
+
|
|
33
|
+
const filepath = path.join(this._root, fileGenerator.filepath);
|
|
34
|
+
|
|
35
|
+
const directory = path.dirname(filepath);
|
|
36
|
+
|
|
37
|
+
await fs.mkdir(directory, { recursive: true });
|
|
38
|
+
|
|
39
|
+
await fs.writeFile(filepath, contents);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { prompt } from 'enquirer';
|
|
2
|
+
|
|
3
|
+
export const packageTypes = [
|
|
4
|
+
'library',
|
|
5
|
+
'config',
|
|
6
|
+
'react',
|
|
7
|
+
'vite',
|
|
8
|
+
'fastify',
|
|
9
|
+
'next',
|
|
10
|
+
'cli',
|
|
11
|
+
] as const;
|
|
12
|
+
|
|
13
|
+
export type PackageType = (typeof packageTypes)[number];
|
|
14
|
+
|
|
15
|
+
export async function pickPackageType(
|
|
16
|
+
options?: Record<string, string | undefined>,
|
|
17
|
+
): Promise<PackageType> {
|
|
18
|
+
if (options?.type && isPackageType(options.type)) {
|
|
19
|
+
return options.type;
|
|
20
|
+
} else if (options?.type && !isPackageType(options.type)) {
|
|
21
|
+
throw new Error(
|
|
22
|
+
`--type setting "${options.type}" is invalid, must be one of ${packageTypes.join(', ')}.`,
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const response = await prompt<{ type: string }>({
|
|
27
|
+
type: 'select',
|
|
28
|
+
name: 'type',
|
|
29
|
+
message: 'What kind of package do you want?',
|
|
30
|
+
choices: [...packageTypes],
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
if (!isPackageType(response.type)) {
|
|
34
|
+
throw new Error(
|
|
35
|
+
`Type "${response.type}" is invalid, must be one of ${packageTypes.join(', ')}.`,
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return response.type;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function isPackageType(s: string): s is PackageType {
|
|
43
|
+
return packageTypes.some((p) => p === s);
|
|
44
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { fileExists, getDirectoryPackageJson } from './utils';
|
|
2
|
+
import { getDirectoryWorkspaceFile, getWorkspaceRoot } from './workspace';
|
|
3
|
+
|
|
4
|
+
import { glob } from 'fast-glob';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
|
|
7
|
+
// TODO: make into class with PackageJSON.
|
|
8
|
+
// TODO: Create Workspace/context...
|
|
9
|
+
export type Package = {
|
|
10
|
+
name: string;
|
|
11
|
+
|
|
12
|
+
directory: string;
|
|
13
|
+
|
|
14
|
+
type: 'Config' | 'App' | 'Library' | 'Unknown';
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export async function getCurrentPackage(
|
|
18
|
+
directory: string = process.cwd(),
|
|
19
|
+
): Promise<Package> {
|
|
20
|
+
const packageRoot = await getPackageRoot(directory);
|
|
21
|
+
const packageJson = await getDirectoryPackageJson(packageRoot);
|
|
22
|
+
|
|
23
|
+
return getPackageByName(packageJson.name);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export async function getPackageRoot(
|
|
27
|
+
directory: string = process.cwd(),
|
|
28
|
+
): Promise<string> {
|
|
29
|
+
const parent = path.dirname(directory);
|
|
30
|
+
|
|
31
|
+
if (parent === directory) {
|
|
32
|
+
throw new Error('Not a package.');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (await isPackageRoot(directory)) {
|
|
36
|
+
return directory;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return getPackageRoot(parent);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export async function getPackageByName(name: string): Promise<Package> {
|
|
43
|
+
const all = await getAllPackages();
|
|
44
|
+
|
|
45
|
+
const match = all.find((p) => p.name === name);
|
|
46
|
+
|
|
47
|
+
if (match === undefined) {
|
|
48
|
+
throw new Error(`No package with name "${name}".`);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return match;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export async function getAllPackages(
|
|
55
|
+
directory: string = process.cwd(),
|
|
56
|
+
): Promise<ReadonlyArray<Package>> {
|
|
57
|
+
const workspaceRoot = await getWorkspaceRoot(directory);
|
|
58
|
+
const workspaceFile = (await getDirectoryWorkspaceFile(workspaceRoot)) as {
|
|
59
|
+
packages: ReadonlyArray<string>;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const results: Array<Package> = [];
|
|
63
|
+
|
|
64
|
+
for (const seg of workspaceFile.packages) {
|
|
65
|
+
const packageType = getPackageType(seg);
|
|
66
|
+
|
|
67
|
+
const packageJsonPaths = await glob(`${workspaceRoot}/${seg}/package.json`);
|
|
68
|
+
|
|
69
|
+
const packageDirectories = packageJsonPaths.map((p) => path.dirname(p));
|
|
70
|
+
|
|
71
|
+
for (const directory of packageDirectories) {
|
|
72
|
+
const name = (await getDirectoryPackageJson(directory)).name;
|
|
73
|
+
|
|
74
|
+
results.push({
|
|
75
|
+
name,
|
|
76
|
+
directory,
|
|
77
|
+
type: packageType,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return results;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function getPackageType(segment: string): Package['type'] {
|
|
86
|
+
if (segment.startsWith('app')) {
|
|
87
|
+
return 'App';
|
|
88
|
+
} else if (segment.startsWith('config')) {
|
|
89
|
+
return 'Config';
|
|
90
|
+
} else if (segment.startsWith('package') || segment.startsWith('lib')) {
|
|
91
|
+
return 'Library';
|
|
92
|
+
} else {
|
|
93
|
+
return 'Unknown';
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function comparePackages(a: Package, b: Package): number {
|
|
98
|
+
const packageTypeDifference = comparePackageTypes(a.type, b.type);
|
|
99
|
+
|
|
100
|
+
if (packageTypeDifference !== 0) {
|
|
101
|
+
return packageTypeDifference;
|
|
102
|
+
} else {
|
|
103
|
+
return a.name.localeCompare(b.name);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function comparePackageTypes(a: Package['type'], b: Package['type']): number {
|
|
108
|
+
return getPackageTypeIndex(a) - getPackageTypeIndex(b);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function getPackageTypeIndex(packageType: Package['type']): number {
|
|
112
|
+
switch (packageType) {
|
|
113
|
+
case 'Library':
|
|
114
|
+
return 0;
|
|
115
|
+
case 'Config':
|
|
116
|
+
return 1;
|
|
117
|
+
case 'App':
|
|
118
|
+
return 2;
|
|
119
|
+
case 'Unknown':
|
|
120
|
+
return 3;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
async function isPackageRoot(directory: string): Promise<boolean> {
|
|
125
|
+
return await fileExists(path.join(directory, 'package.json'));
|
|
126
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { prompt } from 'enquirer';
|
|
2
|
+
|
|
3
|
+
export const styleTypes = [
|
|
4
|
+
'tailwind',
|
|
5
|
+
'css-modules',
|
|
6
|
+
'styled-components',
|
|
7
|
+
'none',
|
|
8
|
+
] as const;
|
|
9
|
+
|
|
10
|
+
export type StyleType = (typeof styleTypes)[number];
|
|
11
|
+
|
|
12
|
+
export async function pickStyleType(
|
|
13
|
+
options?: Record<string, string | undefined>,
|
|
14
|
+
): Promise<StyleType> {
|
|
15
|
+
if (options?.style && isStyleType(options?.style)) {
|
|
16
|
+
return options?.style;
|
|
17
|
+
} else if (options?.style && !isStyleType(options?.style)) {
|
|
18
|
+
throw new Error(
|
|
19
|
+
`--style setting "${options.style}" is invalid, must be one of ${styleTypes.join(', ')}.`,
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const response = await prompt<{ type: string }>({
|
|
24
|
+
type: 'select',
|
|
25
|
+
name: 'type',
|
|
26
|
+
message: 'What kind of style do you want?',
|
|
27
|
+
choices: [...styleTypes],
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
if (!isStyleType(response.type)) {
|
|
31
|
+
throw new Error(
|
|
32
|
+
`Type "${response.type}" is invalid, must be one of ${styleTypes.join(', ')}.`,
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return response.type;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function isStyleType(s: string): s is StyleType {
|
|
40
|
+
return styleTypes.some((p) => p === s);
|
|
41
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { PackageJSON } from '../package-json/package-json';
|
|
4
|
+
|
|
5
|
+
export async function getDirectoryPackageJson(
|
|
6
|
+
directory: string,
|
|
7
|
+
): Promise<PackageJSON> {
|
|
8
|
+
const packageJsonPath = getPackageJSONPath(directory);
|
|
9
|
+
const packageJsonText = await fs.readFile(packageJsonPath, {
|
|
10
|
+
encoding: 'utf-8',
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
return PackageJSON.parse(packageJsonText);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function getPackageJSONPath(directory: string) {
|
|
17
|
+
return path.join(directory, 'package.json');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export async function fileExists(filepath: string): Promise<boolean> {
|
|
21
|
+
try {
|
|
22
|
+
await fs.access(filepath, fs.constants.F_OK);
|
|
23
|
+
|
|
24
|
+
return true;
|
|
25
|
+
} catch {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { fileExists, getDirectoryPackageJson } from './utils';
|
|
2
|
+
|
|
3
|
+
import { Snapshot } from '@stack-dev/core';
|
|
4
|
+
import fs from 'node:fs/promises';
|
|
5
|
+
import path from 'path';
|
|
6
|
+
import yaml from 'yaml';
|
|
7
|
+
|
|
8
|
+
export type WorkspaceYaml = {
|
|
9
|
+
packages: ReadonlyArray<string>;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export async function getDirectoryWorkspaceFile(
|
|
13
|
+
directory: string,
|
|
14
|
+
): Promise<WorkspaceYaml> {
|
|
15
|
+
const raw = await getRawDirectoryWorkspaceFile(directory);
|
|
16
|
+
|
|
17
|
+
if ('packages' in raw && raw.packages instanceof Array) {
|
|
18
|
+
return {
|
|
19
|
+
packages: raw.packages.filter((p): p is string => typeof p === 'string'),
|
|
20
|
+
};
|
|
21
|
+
} else {
|
|
22
|
+
return { packages: [] };
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async function getRawDirectoryWorkspaceFile(
|
|
27
|
+
directory: string,
|
|
28
|
+
): Promise<Snapshot> {
|
|
29
|
+
const case1 = path.join(directory, 'pnpm-workspace.yaml');
|
|
30
|
+
const case2 = path.join(directory, 'pnpm-workspace.yml');
|
|
31
|
+
|
|
32
|
+
if (await fileExists(case1)) {
|
|
33
|
+
return yaml.parse(await fs.readFile(case1, { encoding: 'utf-8' }));
|
|
34
|
+
} else if (await fileExists(case2)) {
|
|
35
|
+
return yaml.parse(await fs.readFile(case2, { encoding: 'utf-8' }));
|
|
36
|
+
} else {
|
|
37
|
+
throw new Error(`Directory "${directory}" is not a workspace.`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export async function isWorkspaceRoot(directory: string): Promise<boolean> {
|
|
42
|
+
return (
|
|
43
|
+
(await fileExists(path.join(directory, 'pnpm-workspace.yaml'))) ||
|
|
44
|
+
(await fileExists(path.join(directory, 'pnpm-workspace.yml')))
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export async function getWorkspaceRoot(
|
|
49
|
+
directory: string = process.cwd(),
|
|
50
|
+
): Promise<string> {
|
|
51
|
+
const parent = path.dirname(directory);
|
|
52
|
+
|
|
53
|
+
if (parent === directory) {
|
|
54
|
+
throw new Error('Not a workspace.');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (await isWorkspaceRoot(directory)) {
|
|
58
|
+
return directory;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return getWorkspaceRoot(parent);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export async function getNamespace(
|
|
65
|
+
directory: string = process.cwd(),
|
|
66
|
+
): Promise<string> {
|
|
67
|
+
const root = await getWorkspaceRoot(directory);
|
|
68
|
+
|
|
69
|
+
const packageJson = await getDirectoryPackageJson(root);
|
|
70
|
+
|
|
71
|
+
const result = packageJson.name;
|
|
72
|
+
|
|
73
|
+
if (!result) {
|
|
74
|
+
throw new Error('Missing name.');
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return `@${result}`;
|
|
78
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { getNamespace } from '../utils/workspace';
|
|
4
|
+
import { makeRootPackage } from './root-package';
|
|
5
|
+
import { makeTypescriptConfig } from './typescript-config';
|
|
6
|
+
|
|
7
|
+
export async function createWorkspace(name: string, directory: string) {
|
|
8
|
+
await validateNotInWorkspace(directory);
|
|
9
|
+
|
|
10
|
+
console.log(`✨ Creating workspace: @${name}`);
|
|
11
|
+
|
|
12
|
+
const fullPath = path.join(directory, name);
|
|
13
|
+
|
|
14
|
+
await fs.mkdir(fullPath, { recursive: true });
|
|
15
|
+
|
|
16
|
+
const namespace = `@${name}`;
|
|
17
|
+
const PACKAGES = [
|
|
18
|
+
await makeRootPackage(fullPath, name),
|
|
19
|
+
await makeTypescriptConfig(fullPath, namespace),
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
await Promise.all(PACKAGES.map((p) => p.generate()));
|
|
23
|
+
|
|
24
|
+
await fs.mkdir(path.join(fullPath, 'apps'));
|
|
25
|
+
await fs.mkdir(path.join(fullPath, 'configs'));
|
|
26
|
+
await fs.mkdir(path.join(fullPath, 'packages'));
|
|
27
|
+
|
|
28
|
+
console.log(`✅ Workspace created at: ${fullPath}`);
|
|
29
|
+
console.log('');
|
|
30
|
+
console.log(`Run "cd ${fullPath}" followed by "pnpm install"`);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function validateNotInWorkspace(directory: string): Promise<void> {
|
|
34
|
+
const namespace = await getNamespace(directory);
|
|
35
|
+
|
|
36
|
+
if (namespace !== undefined) {
|
|
37
|
+
throw new Error(`Currently in workspace "${namespace}".`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './create-workspace';
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { PackageJsonGenerator } from '../file-generator';
|
|
2
|
+
import { FileGeneratorImp } from '../file-generator/file-generator-imp';
|
|
3
|
+
import { Dependency, PackageJSON } from '../package-json';
|
|
4
|
+
import { PackageGenerator } from '../utils/package-generator';
|
|
5
|
+
|
|
6
|
+
export async function makeRootPackage(
|
|
7
|
+
directory: string,
|
|
8
|
+
name: string,
|
|
9
|
+
): Promise<PackageGenerator> {
|
|
10
|
+
const packageJsonModel = new PackageJSON({
|
|
11
|
+
name: name,
|
|
12
|
+
devDependencies: [new Dependency('turbo', '^2.5.4')],
|
|
13
|
+
additionalData: {
|
|
14
|
+
description: '',
|
|
15
|
+
keywords: [],
|
|
16
|
+
author: '',
|
|
17
|
+
license: 'ISC',
|
|
18
|
+
packageManager: 'pnpm@10.13.1',
|
|
19
|
+
},
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
const PNPM_WORKSPACE = new FileGeneratorImp(
|
|
23
|
+
'pnpm-workspace.yaml',
|
|
24
|
+
['packages:', ' - apps/*', ' - packages/*', ' - configs/*'].join('\n'),
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
const GITIGNORE = new FileGeneratorImp('.gitignore', GITIGNORE_CONTENT);
|
|
28
|
+
|
|
29
|
+
const TURBO_JSON = new FileGeneratorImp(
|
|
30
|
+
'turbo.json',
|
|
31
|
+
JSON.stringify(
|
|
32
|
+
{
|
|
33
|
+
tasks: {
|
|
34
|
+
build: {
|
|
35
|
+
dependsOn: ['^build'],
|
|
36
|
+
outputs: ['dist/**'],
|
|
37
|
+
},
|
|
38
|
+
lint: {},
|
|
39
|
+
test: {},
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
null,
|
|
43
|
+
2,
|
|
44
|
+
),
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
return new PackageGenerator(
|
|
48
|
+
directory,
|
|
49
|
+
new PackageJsonGenerator(packageJsonModel, ''),
|
|
50
|
+
[PNPM_WORKSPACE, GITIGNORE, TURBO_JSON],
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const GITIGNORE_CONTENT = `# Logs
|
|
55
|
+
logs
|
|
56
|
+
*.log
|
|
57
|
+
npm-debug.log*
|
|
58
|
+
yarn-debug.log*
|
|
59
|
+
yarn-error.log*
|
|
60
|
+
lerna-debug.log*
|
|
61
|
+
|
|
62
|
+
# Diagnostic reports (https://nodejs.org/api/report.html)
|
|
63
|
+
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
|
64
|
+
|
|
65
|
+
# Runtime data
|
|
66
|
+
pids
|
|
67
|
+
*.pid
|
|
68
|
+
*.seed
|
|
69
|
+
*.pid.lock
|
|
70
|
+
|
|
71
|
+
# Directory for instrumented libs generated by jscoverage/JSCover
|
|
72
|
+
lib-cov
|
|
73
|
+
|
|
74
|
+
# Coverage directory used by tools like istanbul
|
|
75
|
+
coverage
|
|
76
|
+
*.lcov
|
|
77
|
+
|
|
78
|
+
# nyc test coverage
|
|
79
|
+
.nyc_output
|
|
80
|
+
|
|
81
|
+
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
|
82
|
+
.grunt
|
|
83
|
+
|
|
84
|
+
# Bower dependency directory (https://bower.io/)
|
|
85
|
+
bower_components
|
|
86
|
+
|
|
87
|
+
# node-waf configuration
|
|
88
|
+
.lock-wscript
|
|
89
|
+
|
|
90
|
+
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
|
91
|
+
build/Release
|
|
92
|
+
|
|
93
|
+
# Dependency directories
|
|
94
|
+
node_modules/
|
|
95
|
+
jspm_packages/
|
|
96
|
+
|
|
97
|
+
# Snowpack dependency directory (https://snowpack.dev/)
|
|
98
|
+
web_modules/
|
|
99
|
+
|
|
100
|
+
# TypeScript cache
|
|
101
|
+
*.tsbuildinfo
|
|
102
|
+
|
|
103
|
+
# Optional npm cache directory
|
|
104
|
+
.npm
|
|
105
|
+
|
|
106
|
+
# Optional eslint cache
|
|
107
|
+
.eslintcache
|
|
108
|
+
|
|
109
|
+
# Optional stylelint cache
|
|
110
|
+
.stylelintcache
|
|
111
|
+
|
|
112
|
+
# Optional REPL history
|
|
113
|
+
.node_repl_history
|
|
114
|
+
|
|
115
|
+
# Output of 'npm pack'
|
|
116
|
+
*.tgz
|
|
117
|
+
|
|
118
|
+
# Yarn Integrity file
|
|
119
|
+
.yarn-integrity
|
|
120
|
+
|
|
121
|
+
# dotenv environment variable files
|
|
122
|
+
.env
|
|
123
|
+
.env.*
|
|
124
|
+
!.env.example
|
|
125
|
+
|
|
126
|
+
# parcel-bundler cache (https://parceljs.org/)
|
|
127
|
+
.cache
|
|
128
|
+
.parcel-cache
|
|
129
|
+
|
|
130
|
+
# Next.js build output
|
|
131
|
+
.next
|
|
132
|
+
out
|
|
133
|
+
|
|
134
|
+
# Nuxt.js build / generate output
|
|
135
|
+
.nuxt
|
|
136
|
+
dist
|
|
137
|
+
|
|
138
|
+
# Gatsby files
|
|
139
|
+
.cache/
|
|
140
|
+
# Comment in the public line in if your project uses Gatsby and not Next.js
|
|
141
|
+
# https://nextjs.org/blog/next-9-1#public-directory-support
|
|
142
|
+
# public
|
|
143
|
+
|
|
144
|
+
# vuepress build output
|
|
145
|
+
.vuepress/dist
|
|
146
|
+
|
|
147
|
+
# vuepress v2.x temp and cache directory
|
|
148
|
+
.temp
|
|
149
|
+
.cache
|
|
150
|
+
|
|
151
|
+
# Sveltekit cache directory
|
|
152
|
+
.svelte-kit/
|
|
153
|
+
|
|
154
|
+
# vitepress build output
|
|
155
|
+
**/.vitepress/dist
|
|
156
|
+
|
|
157
|
+
# vitepress cache directory
|
|
158
|
+
**/.vitepress/cache
|
|
159
|
+
|
|
160
|
+
# Docusaurus cache and generated files
|
|
161
|
+
.docusaurus
|
|
162
|
+
|
|
163
|
+
# Serverless directories
|
|
164
|
+
.serverless/
|
|
165
|
+
|
|
166
|
+
# FuseBox cache
|
|
167
|
+
.fusebox/
|
|
168
|
+
|
|
169
|
+
# DynamoDB Local files
|
|
170
|
+
.dynamodb/
|
|
171
|
+
|
|
172
|
+
# Firebase cache directory
|
|
173
|
+
.firebase/
|
|
174
|
+
|
|
175
|
+
# TernJS port file
|
|
176
|
+
.tern-port
|
|
177
|
+
|
|
178
|
+
# Stores VSCode versions used for testing VSCode extensions
|
|
179
|
+
.vscode-test
|
|
180
|
+
|
|
181
|
+
# yarn v3
|
|
182
|
+
.pnp.*
|
|
183
|
+
.yarn/*
|
|
184
|
+
!.yarn/patches
|
|
185
|
+
!.yarn/plugins
|
|
186
|
+
!.yarn/releases
|
|
187
|
+
!.yarn/sdks
|
|
188
|
+
!.yarn/versions
|
|
189
|
+
|
|
190
|
+
# Vite logs files
|
|
191
|
+
vite.config.js.timestamp-*
|
|
192
|
+
vite.config.ts.timestamp-*
|
|
193
|
+
|
|
194
|
+
.turbo
|
|
195
|
+
`;
|