@decohere/devkit 0.0.1-dev.126 → 0.0.1-dev.5
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/package.json +3 -12
- package/src/cli/action.ts +77 -0
- package/src/cli/command.ts +173 -0
- package/src/cli/index.ts +30 -0
- package/src/cli/module.type.ts +48 -0
- package/src/cli/options.ts +81 -0
- package/src/diagnostics/trace.ts +92 -0
- package/src/interop/docker/docker.ts +57 -0
- package/src/interop/errors/interop.error.ts +1 -0
- package/src/interop/git/git.ts +98 -0
- package/{types/src/interop/git/git.types.d.ts → src/interop/git/git.types.ts} +9 -6
- package/{es6/src/interop/index.js → src/interop/index.ts} +1 -0
- package/src/interop/node/node.ts +38 -0
- package/src/interop/npm/npm.ts +168 -0
- package/src/interop/npm/npm.types.ts +28 -0
- package/src/interop/pnpm/pnpm.ts +22 -0
- package/{types/src/interop/pnpm/pnpm.types.d.ts → src/interop/pnpm/pnpm.types.ts} +1 -1
- package/src/interop/shell/shell.argv.ts +31 -0
- package/src/interop/shell/shell.ts +100 -0
- package/src/interop/shell/shell.types.ts +9 -0
- package/src/interop/types/detect-package-manager.ts +45 -0
- package/src/interop/types/package-manager.ts +7 -0
- package/src/interop/yarn/yarn.ts +28 -0
- package/{types/src/interop/yarn/yarn.types.d.ts → src/interop/yarn/yarn.types.ts} +1 -1
- package/src/io/find-directories.ts +13 -0
- package/src/io/find-file.ts +37 -0
- package/src/io/watch.ts +290 -0
- package/tests/cli/command.model.spec.ts +91 -0
- package/tests/diagnostics/trace.spec.ts +319 -0
- package/tests/interop/detect-package-manager.spec.ts +70 -0
- package/tests/interop/npm.spec.ts +129 -0
- package/tests/interop/pnpm.spec.ts +30 -0
- package/tests/interop/shell.spec.ts +47 -0
- package/tests/interop/yarn.spec.ts +18 -0
- package/tests/io/find-file.spec.ts +188 -0
- package/tests/io/watch.real-fs.spec.ts +76 -0
- package/tests/io/watch.spec.ts +291 -0
- package/LICENSE +0 -15
- package/commonjs/src/cli/action.js +0 -65
- package/commonjs/src/cli/command.js +0 -127
- package/commonjs/src/cli/index.js +0 -61
- package/commonjs/src/cli/module.type.js +0 -18
- package/commonjs/src/cli/options.js +0 -115
- package/commonjs/src/diagnostics/index.js +0 -22
- package/commonjs/src/diagnostics/trace.js +0 -79
- package/commonjs/src/interop/docker/docker.js +0 -54
- package/commonjs/src/interop/errors/interop.error.js +0 -12
- package/commonjs/src/interop/git/git.js +0 -134
- package/commonjs/src/interop/git/git.types.js +0 -4
- package/commonjs/src/interop/index.js +0 -97
- package/commonjs/src/interop/node/index.js +0 -18
- package/commonjs/src/interop/node/node.js +0 -45
- package/commonjs/src/interop/npm/npm.js +0 -231
- package/commonjs/src/interop/npm/npm.types.js +0 -16
- package/commonjs/src/interop/pnpm/pnpm.js +0 -34
- package/commonjs/src/interop/pnpm/pnpm.types.js +0 -4
- package/commonjs/src/interop/shell/shell.argv.js +0 -45
- package/commonjs/src/interop/shell/shell.js +0 -105
- package/commonjs/src/interop/shell/shell.types.js +0 -4
- package/commonjs/src/interop/types/detect-package-manager.js +0 -71
- package/commonjs/src/interop/types/package-manager.js +0 -4
- package/commonjs/src/interop/yarn/yarn.js +0 -34
- package/commonjs/src/interop/yarn/yarn.types.js +0 -4
- package/commonjs/src/io/find-directories.js +0 -21
- package/commonjs/src/io/find-file.js +0 -45
- package/commonjs/src/io/index.js +0 -20
- package/commonjs/src/io/watch.js +0 -261
- package/es6/src/cli/action.js +0 -47
- package/es6/src/cli/command.js +0 -106
- package/es6/src/cli/index.js +0 -4
- package/es6/src/cli/module.type.js +0 -11
- package/es6/src/cli/options.js +0 -76
- package/es6/src/diagnostics/trace.js +0 -61
- package/es6/src/interop/docker/docker.js +0 -44
- package/es6/src/interop/errors/interop.error.js +0 -2
- package/es6/src/interop/git/git.js +0 -124
- package/es6/src/interop/git/git.types.js +0 -1
- package/es6/src/interop/node/node.js +0 -35
- package/es6/src/interop/npm/npm.js +0 -180
- package/es6/src/interop/npm/npm.types.js +0 -6
- package/es6/src/interop/pnpm/pnpm.js +0 -24
- package/es6/src/interop/pnpm/pnpm.types.js +0 -1
- package/es6/src/interop/shell/shell.argv.js +0 -35
- package/es6/src/interop/shell/shell.js +0 -95
- package/es6/src/interop/shell/shell.types.js +0 -1
- package/es6/src/interop/types/detect-package-manager.js +0 -50
- package/es6/src/interop/types/package-manager.js +0 -1
- package/es6/src/interop/yarn/yarn.js +0 -24
- package/es6/src/interop/yarn/yarn.types.js +0 -1
- package/es6/src/io/find-directories.js +0 -11
- package/es6/src/io/find-file.js +0 -27
- package/es6/src/io/watch.js +0 -238
- package/types/src/cli/action.d.ts +0 -36
- package/types/src/cli/command.d.ts +0 -34
- package/types/src/cli/index.d.ts +0 -7
- package/types/src/cli/module.type.d.ts +0 -36
- package/types/src/cli/options.d.ts +0 -28
- package/types/src/diagnostics/index.d.ts +0 -1
- package/types/src/diagnostics/trace.d.ts +0 -10
- package/types/src/interop/docker/docker.d.ts +0 -19
- package/types/src/interop/errors/interop.error.d.ts +0 -2
- package/types/src/interop/git/git.d.ts +0 -16
- package/types/src/interop/index.d.ts +0 -14
- package/types/src/interop/node/index.d.ts +0 -1
- package/types/src/interop/node/node.d.ts +0 -5
- package/types/src/interop/npm/npm.d.ts +0 -29
- package/types/src/interop/npm/npm.types.d.ts +0 -24
- package/types/src/interop/pnpm/pnpm.d.ts +0 -9
- package/types/src/interop/shell/shell.argv.d.ts +0 -5
- package/types/src/interop/shell/shell.d.ts +0 -6
- package/types/src/interop/shell/shell.types.d.ts +0 -9
- package/types/src/interop/types/detect-package-manager.d.ts +0 -4
- package/types/src/interop/types/package-manager.d.ts +0 -10
- package/types/src/interop/yarn/yarn.d.ts +0 -9
- package/types/src/io/find-directories.d.ts +0 -2
- package/types/src/io/find-file.d.ts +0 -9
- package/types/src/io/index.d.ts +0 -3
- package/types/src/io/watch.d.ts +0 -16
- /package/{es6/src/diagnostics/index.js → src/diagnostics/index.ts} +0 -0
- /package/{es6/src/interop/node/index.js → src/interop/node/index.ts} +0 -0
- /package/{es6/src/io/index.js → src/io/index.ts} +0 -0
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
export type GitOptions = {
|
|
2
|
-
|
|
2
|
+
cwd?: string;
|
|
3
3
|
};
|
|
4
|
+
|
|
4
5
|
export type CommitOptions = {
|
|
5
|
-
|
|
6
|
-
|
|
6
|
+
files?: string[];
|
|
7
|
+
all?: boolean;
|
|
7
8
|
} & GitOptions;
|
|
9
|
+
|
|
8
10
|
export type PushOptions = {
|
|
9
|
-
|
|
11
|
+
setUpstream: string;
|
|
10
12
|
} & GitOptions;
|
|
13
|
+
|
|
11
14
|
export type StageOptions = {
|
|
12
|
-
|
|
13
|
-
|
|
15
|
+
files?: string[];
|
|
16
|
+
all?: string;
|
|
14
17
|
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { type SpawnOptions, spawn } from "node:child_process";
|
|
2
|
+
import { Shell } from "../shell/shell.js";
|
|
3
|
+
|
|
4
|
+
export class Node extends Shell {
|
|
5
|
+
protected program: string = "node";
|
|
6
|
+
|
|
7
|
+
public async runScript(script: string, ...args: string[]) {
|
|
8
|
+
const spawnOpt: SpawnOptions = {
|
|
9
|
+
cwd: ".",
|
|
10
|
+
stdio: "inherit",
|
|
11
|
+
env: {
|
|
12
|
+
...process.env,
|
|
13
|
+
},
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
return new Promise((resolve, reject) => {
|
|
17
|
+
const proc = spawn("node", [script, ...args], spawnOpt);
|
|
18
|
+
|
|
19
|
+
proc.on("exit", (code) => {
|
|
20
|
+
if (code !== 0) {
|
|
21
|
+
reject();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
resolve(null);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
proc.on("close", (code) => {
|
|
28
|
+
resolve(null);
|
|
29
|
+
if (code === 0) {
|
|
30
|
+
resolve({
|
|
31
|
+
message: `${script} completed`,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
reject(`Running '${script}' failed with code ${code}`);
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { InteropError } from "../errors/interop.error.js";
|
|
2
|
+
import { Shell } from "../shell/shell.js";
|
|
3
|
+
import type { PackageManager } from "../types/package-manager.js";
|
|
4
|
+
import type { NpmOptions, PublishOptions, VersionOptions, VersionType } from "./npm.types.js";
|
|
5
|
+
|
|
6
|
+
const Commands = {
|
|
7
|
+
publish: "publish",
|
|
8
|
+
version: "version",
|
|
9
|
+
link: "link",
|
|
10
|
+
install: "install",
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export class Npm extends Shell implements PackageManager {
|
|
14
|
+
public readonly name = "npm" as const;
|
|
15
|
+
protected program: string = "npm";
|
|
16
|
+
|
|
17
|
+
public async publish(directory: string, options: PublishOptions = { channel: "beta" }) {
|
|
18
|
+
const { cwd, dryRun, skipIfPublished, channel, authToken, otp, provenance } = options;
|
|
19
|
+
if (directory == null) {
|
|
20
|
+
throw new InteropError("Directory is required");
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (skipIfPublished && !dryRun) {
|
|
24
|
+
const alreadyPublished = await this.isPublished(directory, { cwd });
|
|
25
|
+
if (alreadyPublished) {
|
|
26
|
+
return { message: `Skipping publish — version already exists on registry`, data: [] };
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const args = [Commands.publish, directory];
|
|
31
|
+
let tmpUserConfigPath: string | undefined;
|
|
32
|
+
|
|
33
|
+
args.push("--tag", channel ?? "beta");
|
|
34
|
+
|
|
35
|
+
if (provenance) {
|
|
36
|
+
args.push("--provenance");
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Explicit --token (local bootstrap). OIDC publish must not inherit cwd .npmrc / NODE_AUTH_TOKEN.
|
|
40
|
+
if (authToken?.trim()) {
|
|
41
|
+
tmpUserConfigPath = await this.createTempUserConfig(authToken.trim());
|
|
42
|
+
args.push("--userconfig", tmpUserConfigPath);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (dryRun) {
|
|
46
|
+
args.push("--dry-run");
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const env = { ...process.env };
|
|
50
|
+
if (!authToken?.trim()) {
|
|
51
|
+
delete env.NODE_AUTH_TOKEN;
|
|
52
|
+
delete env.NPM_TOKEN;
|
|
53
|
+
env.NPM_CONFIG_USERCONFIG = "/dev/null";
|
|
54
|
+
}
|
|
55
|
+
if (otp) {
|
|
56
|
+
env.NPM_CONFIG_OTP = otp;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
try {
|
|
60
|
+
return await this.run({ cwd, env }, ...args);
|
|
61
|
+
} catch (error) {
|
|
62
|
+
if (skipIfPublished && !dryRun && isAlreadyPublishedError(error)) {
|
|
63
|
+
return { message: `Skipping publish — version already exists on registry`, data: [] };
|
|
64
|
+
}
|
|
65
|
+
throw error;
|
|
66
|
+
} finally {
|
|
67
|
+
if (tmpUserConfigPath) {
|
|
68
|
+
await this.removeTempUserConfig(tmpUserConfigPath);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* @description Runs in the directory of the package
|
|
75
|
+
* @param options
|
|
76
|
+
* @returns
|
|
77
|
+
*/
|
|
78
|
+
public async version(versionType: VersionType, options: VersionOptions) {
|
|
79
|
+
const { cwd, noTag, preid } = options;
|
|
80
|
+
const args = [Commands.version, versionType];
|
|
81
|
+
if (noTag) {
|
|
82
|
+
args.push("--no-git-tag-version");
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (preid) {
|
|
86
|
+
args.push("--preid", preid);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return this.run({ cwd }, ...args);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
public async link(directory: string, options: NpmOptions = { cwd: "./" }) {
|
|
93
|
+
const { cwd } = options;
|
|
94
|
+
const args = [Commands.link, directory];
|
|
95
|
+
return this.run({ cwd }, ...args);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
public async install(options: NpmOptions = { cwd: "./" }) {
|
|
99
|
+
const { cwd } = options;
|
|
100
|
+
return this.run({ cwd }, Commands.install);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Install only `dependencies` as real folders: `--omit=dev` skips devDependencies and
|
|
105
|
+
* `--no-bin-links` skips the `.bin` symlinks, so the result holds no symlinks at all.
|
|
106
|
+
*/
|
|
107
|
+
public async installProduction(options: NpmOptions = { cwd: "./" }) {
|
|
108
|
+
const { cwd } = options;
|
|
109
|
+
return this.run(
|
|
110
|
+
{ cwd },
|
|
111
|
+
Commands.install,
|
|
112
|
+
"--omit=dev",
|
|
113
|
+
"--no-bin-links",
|
|
114
|
+
"--no-audit",
|
|
115
|
+
"--no-fund",
|
|
116
|
+
"--loglevel=error",
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
public async syncLockFile(options: NpmOptions = { cwd: "./" }) {
|
|
121
|
+
const { cwd } = options;
|
|
122
|
+
return this.run({ cwd }, Commands.install, "--package-lock-only", "--ignore-scripts");
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* True when package@version exists on the registry.
|
|
127
|
+
* Uses ambient npm config (cwd `.npmrc`); do not pass tokens on argv.
|
|
128
|
+
*/
|
|
129
|
+
private async isPublished(directory: string, options: NpmOptions): Promise<boolean> {
|
|
130
|
+
const { readFileSync } = await import("node:fs");
|
|
131
|
+
const { join } = await import("node:path");
|
|
132
|
+
try {
|
|
133
|
+
const pkgJson = JSON.parse(readFileSync(join(directory, "package.json"), "utf8"));
|
|
134
|
+
const spec = `${pkgJson.name}@${pkgJson.version}`;
|
|
135
|
+
await this.run(options, "view", spec, "version");
|
|
136
|
+
return true;
|
|
137
|
+
} catch {
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
private async createTempUserConfig(authToken: string): Promise<string> {
|
|
143
|
+
const { mkdtemp, writeFile } = await import("node:fs/promises");
|
|
144
|
+
const { tmpdir } = await import("node:os");
|
|
145
|
+
const { join } = await import("node:path");
|
|
146
|
+
const tempDir = await mkdtemp(join(tmpdir(), "decohere-npmrc-"));
|
|
147
|
+
const npmrcPath = join(tempDir, ".npmrc");
|
|
148
|
+
const npmrc = [
|
|
149
|
+
"registry=https://registry.npmjs.org/",
|
|
150
|
+
"always-auth=true",
|
|
151
|
+
`//registry.npmjs.org/:_authToken=${authToken}`,
|
|
152
|
+
].join("\n");
|
|
153
|
+
await writeFile(npmrcPath, `${npmrc}\n`, { mode: 0o600 });
|
|
154
|
+
return npmrcPath;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
private async removeTempUserConfig(npmrcPath: string): Promise<void> {
|
|
158
|
+
const { rm } = await import("node:fs/promises");
|
|
159
|
+
const { dirname } = await import("node:path");
|
|
160
|
+
const dir = dirname(npmrcPath);
|
|
161
|
+
await rm(dir, { recursive: true, force: true });
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function isAlreadyPublishedError(error: unknown): boolean {
|
|
166
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
167
|
+
return /cannot publish over the previously published versions/i.test(message);
|
|
168
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export type NpmOptions = {
|
|
2
|
+
cwd?: string;
|
|
3
|
+
};
|
|
4
|
+
|
|
5
|
+
export type PublishOptions = {
|
|
6
|
+
dryRun?: boolean;
|
|
7
|
+
skipIfPublished?: boolean;
|
|
8
|
+
authToken?: string;
|
|
9
|
+
otp?: string;
|
|
10
|
+
provenance?: boolean;
|
|
11
|
+
/** @deprecated use channel */
|
|
12
|
+
versionType?: VersionType;
|
|
13
|
+
channel?: string;
|
|
14
|
+
} & NpmOptions;
|
|
15
|
+
|
|
16
|
+
export type VersionType = "major" | "minor" | "patch" | "prerelease";
|
|
17
|
+
|
|
18
|
+
export const VersionTypes = {
|
|
19
|
+
major: "major",
|
|
20
|
+
minor: "minor",
|
|
21
|
+
patch: "patch",
|
|
22
|
+
prerelease: "prerelease",
|
|
23
|
+
} satisfies Record<VersionType, VersionType>;
|
|
24
|
+
|
|
25
|
+
export type VersionOptions = {
|
|
26
|
+
preid?: string;
|
|
27
|
+
noTag?: boolean;
|
|
28
|
+
} & NpmOptions;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Shell } from "../shell/shell.js";
|
|
2
|
+
import type { PackageManager } from "../types/package-manager.js";
|
|
3
|
+
import type { PnpmOptions } from "./pnpm.types.js";
|
|
4
|
+
|
|
5
|
+
const Commands = {
|
|
6
|
+
install: "install",
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export class Pnpm extends Shell implements PackageManager {
|
|
10
|
+
public readonly name = "pnpm" as const;
|
|
11
|
+
protected program: string = "pnpm";
|
|
12
|
+
|
|
13
|
+
public async install(options: PnpmOptions = { cwd: "./" }) {
|
|
14
|
+
const { cwd } = options;
|
|
15
|
+
return this.run({ cwd }, Commands.install);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
public async syncLockFile(options: PnpmOptions = { cwd: "./" }) {
|
|
19
|
+
const { cwd } = options;
|
|
20
|
+
return this.run({ cwd }, Commands.install, "--lockfile-only", "--ignore-scripts");
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
function fields(args: string[]): Record<string, string | boolean | string[]> {
|
|
2
|
+
const positional: string[] = [];
|
|
3
|
+
const flags: Record<string, string | boolean> = {};
|
|
4
|
+
for (let i = 0; i < args.length; i++) {
|
|
5
|
+
const arg = args[i];
|
|
6
|
+
if (arg === "--") {
|
|
7
|
+
positional.push(...args.slice(i + 1));
|
|
8
|
+
break;
|
|
9
|
+
}
|
|
10
|
+
if (arg.startsWith("--")) {
|
|
11
|
+
const body = arg.slice(2);
|
|
12
|
+
const eq = body.indexOf("=");
|
|
13
|
+
if (eq !== -1) {
|
|
14
|
+
flags[body.slice(0, eq)] = body.slice(eq + 1);
|
|
15
|
+
continue;
|
|
16
|
+
}
|
|
17
|
+
const next = args[i + 1];
|
|
18
|
+
if (next != null && !next.startsWith("-")) {
|
|
19
|
+
flags[body] = next;
|
|
20
|
+
i += 1;
|
|
21
|
+
} else {
|
|
22
|
+
flags[body] = true;
|
|
23
|
+
}
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
positional.push(arg);
|
|
27
|
+
}
|
|
28
|
+
return { _: positional, ...flags };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export const shellArgv = { fields };
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { type SpawnOptions, spawn } from "node:child_process";
|
|
2
|
+
import { scribe } from "@decohere/scribe";
|
|
3
|
+
import { InteropError } from "../errors/interop.error.js";
|
|
4
|
+
import { shellArgv } from "./shell.argv.js";
|
|
5
|
+
import type { ShellOptions, ShellResponse } from "./shell.types.js";
|
|
6
|
+
|
|
7
|
+
export abstract class Shell {
|
|
8
|
+
protected abstract program: string;
|
|
9
|
+
protected async run(options: ShellOptions, ...args: string[]) {
|
|
10
|
+
const logger = scribe.logger(Shell.name);
|
|
11
|
+
const argv = args ?? [];
|
|
12
|
+
const argvFields = shellArgv.fields(argv);
|
|
13
|
+
const positionals = Array.isArray(argvFields._) ? argvFields._ : [];
|
|
14
|
+
const commandName = [this.program, ...positionals].join(" ").trim();
|
|
15
|
+
logger.info("Running", { program: this.program, args: argvFields });
|
|
16
|
+
return new Promise<ShellResponse>((resolve, reject) => {
|
|
17
|
+
let settled = false;
|
|
18
|
+
const resolveOnce = (response: ShellResponse) => {
|
|
19
|
+
if (settled) {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
settled = true;
|
|
23
|
+
resolve(response);
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const rejectOnce = (error: unknown) => {
|
|
27
|
+
if (settled) {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
settled = true;
|
|
31
|
+
reject(error);
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const { cwd, env } = options;
|
|
35
|
+
const spawnOpt: SpawnOptions = {
|
|
36
|
+
cwd: cwd ?? ".",
|
|
37
|
+
stdio: ["inherit", "pipe", "pipe"],
|
|
38
|
+
env: env ?? { ...process.env },
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const proc = spawn(this.program, argv, spawnOpt);
|
|
42
|
+
const responseData: string[] = [];
|
|
43
|
+
const errorData: string[] = [];
|
|
44
|
+
|
|
45
|
+
proc.on("close", (code, signal) => {
|
|
46
|
+
if (code === 0) {
|
|
47
|
+
resolveOnce({
|
|
48
|
+
message: `${commandName} completed`,
|
|
49
|
+
data: responseData,
|
|
50
|
+
});
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const stderr = errorData.join("\n").trim();
|
|
55
|
+
const stdout = responseData.join("\n").trim();
|
|
56
|
+
const exitCodeText = code == null ? "unknown" : String(code);
|
|
57
|
+
const signalText = signal ? ` signal ${signal}` : "";
|
|
58
|
+
const base = `Running '${commandName}' failed with code ${exitCodeText}${signalText}`;
|
|
59
|
+
const details = [
|
|
60
|
+
stderr ? `stderr:\n${stderr}` : "",
|
|
61
|
+
!stderr && stdout ? `stdout:\n${stdout}` : "",
|
|
62
|
+
]
|
|
63
|
+
.filter(Boolean)
|
|
64
|
+
.join("\n");
|
|
65
|
+
|
|
66
|
+
rejectOnce(new InteropError(details ? `${base}\n${details}` : base));
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
proc.on("error", (err) => {
|
|
70
|
+
rejectOnce(err);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
proc.stdout.on("error", (err) => {
|
|
74
|
+
rejectOnce(err);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
proc.stdout.on("data", (data) => {
|
|
78
|
+
const lines = this.parseStream(data);
|
|
79
|
+
console.log(data.toString());
|
|
80
|
+
responseData.push(...lines);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
proc.stderr?.on("error", (err) => {
|
|
84
|
+
rejectOnce(err);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
proc.stderr?.on("data", (data) => {
|
|
88
|
+
const lines = this.parseStream(data);
|
|
89
|
+
console.error(data.toString());
|
|
90
|
+
errorData.push(...lines);
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
private parseStream(data: Buffer | string) {
|
|
96
|
+
const dataStr: string = data.toString();
|
|
97
|
+
const lines = dataStr.split("\n").filter((l) => l !== "");
|
|
98
|
+
return lines;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { createInterface } from "node:readline/promises";
|
|
4
|
+
import { Npm } from "../npm/npm.js";
|
|
5
|
+
import { Pnpm } from "../pnpm/pnpm.js";
|
|
6
|
+
import { Yarn } from "../yarn/yarn.js";
|
|
7
|
+
import type { PackageManager } from "./package-manager.js";
|
|
8
|
+
|
|
9
|
+
const LOCKFILES: Array<[string, () => PackageManager]> = [
|
|
10
|
+
["pnpm-lock.yaml", () => new Pnpm()],
|
|
11
|
+
["yarn.lock", () => new Yarn()],
|
|
12
|
+
["package-lock.json", () => new Npm()],
|
|
13
|
+
];
|
|
14
|
+
|
|
15
|
+
function fromLockfile(cwd: string): PackageManager | undefined {
|
|
16
|
+
for (const [file, factory] of LOCKFILES) {
|
|
17
|
+
if (existsSync(join(cwd, file))) return factory();
|
|
18
|
+
}
|
|
19
|
+
return undefined;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function fromUserAgent(): PackageManager | undefined {
|
|
23
|
+
const agent = process.env.npm_config_user_agent ?? "";
|
|
24
|
+
if (agent.startsWith("pnpm")) return new Pnpm();
|
|
25
|
+
if (agent.startsWith("yarn")) return new Yarn();
|
|
26
|
+
if (agent.startsWith("npm")) return new Npm();
|
|
27
|
+
return undefined;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function detectPackageManager(cwd: string): PackageManager | undefined {
|
|
31
|
+
return fromLockfile(cwd) ?? fromUserAgent();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function packageManagerFromString(name: string): PackageManager {
|
|
35
|
+
if (name === "yarn") return new Yarn();
|
|
36
|
+
if (name === "npm") return new Npm();
|
|
37
|
+
return new Pnpm();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export async function promptForPackageManager(): Promise<PackageManager> {
|
|
41
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
42
|
+
const answer = await rl.question("Package manager? [pnpm/yarn/npm] (default: pnpm): ");
|
|
43
|
+
rl.close();
|
|
44
|
+
return packageManagerFromString(answer.trim().toLowerCase());
|
|
45
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ShellResponse } from "../shell/shell.types.js";
|
|
2
|
+
|
|
3
|
+
export interface PackageManager {
|
|
4
|
+
readonly name: "npm" | "pnpm" | "yarn";
|
|
5
|
+
install(options?: { cwd?: string }): Promise<ShellResponse>;
|
|
6
|
+
syncLockFile(options?: { cwd?: string }): Promise<ShellResponse>;
|
|
7
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Shell } from "../shell/shell.js";
|
|
2
|
+
import type { PackageManager } from "../types/package-manager.js";
|
|
3
|
+
import type { YarnOptions } from "./yarn.types.js";
|
|
4
|
+
|
|
5
|
+
const Commands = {
|
|
6
|
+
install: "install",
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export class Yarn extends Shell implements PackageManager {
|
|
10
|
+
public readonly name = "yarn" as const;
|
|
11
|
+
protected program: string = "yarn";
|
|
12
|
+
|
|
13
|
+
public async install(options: YarnOptions = { cwd: "./" }) {
|
|
14
|
+
const { cwd } = options;
|
|
15
|
+
return this.run({ cwd }, Commands.install);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
public async syncLockFile(options: YarnOptions = { cwd: "./" }) {
|
|
19
|
+
const { cwd } = options;
|
|
20
|
+
return this.run(
|
|
21
|
+
{ cwd },
|
|
22
|
+
Commands.install,
|
|
23
|
+
"--mode",
|
|
24
|
+
"update-lockfile",
|
|
25
|
+
"--ignore-scripts",
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { glob } from "glob";
|
|
2
|
+
import type { Directory } from "@decohere/core/io";
|
|
3
|
+
|
|
4
|
+
export async function findDirectories(...pattern: string[]): Promise<Directory[]> {
|
|
5
|
+
const paths = await glob(pattern, { withFileTypes: true });
|
|
6
|
+
const dirs = paths
|
|
7
|
+
.filter((p) => p.isDirectory())
|
|
8
|
+
.map((p) => ({
|
|
9
|
+
name: p.name,
|
|
10
|
+
parentPath: p.parentPath,
|
|
11
|
+
}));
|
|
12
|
+
return dirs;
|
|
13
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { glob } from "glob";
|
|
2
|
+
import { FileEx } from "@decohere/core/io";
|
|
3
|
+
|
|
4
|
+
export type FindFileOptions = {
|
|
5
|
+
cwd: string;
|
|
6
|
+
ignore?: string | string[];
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export async function findFile(pattern: string) {
|
|
10
|
+
const files = await glob(pattern, { includeChildMatches: false });
|
|
11
|
+
if (files.length === 0) {
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
const file = {
|
|
15
|
+
relativePath: files.at(0),
|
|
16
|
+
};
|
|
17
|
+
return file;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export async function findFiles(
|
|
21
|
+
patterns: string[],
|
|
22
|
+
options?: FindFileOptions,
|
|
23
|
+
): Promise<FileEx[]> {
|
|
24
|
+
const paths = await glob(patterns, {
|
|
25
|
+
...options,
|
|
26
|
+
withFileTypes: true,
|
|
27
|
+
});
|
|
28
|
+
const files = paths
|
|
29
|
+
.filter((p) => p.isFile())
|
|
30
|
+
.map((p) => {
|
|
31
|
+
return new FileEx({
|
|
32
|
+
relativePath: p.relative(),
|
|
33
|
+
fullPath: p.fullpath(),
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
return files;
|
|
37
|
+
}
|