@decohere/devkit 0.0.1-dev.119
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/README.md +14 -0
- package/commonjs/src/cli/action.js +65 -0
- package/commonjs/src/cli/command.js +127 -0
- package/commonjs/src/cli/index.js +61 -0
- package/commonjs/src/cli/module.type.js +18 -0
- package/commonjs/src/cli/options.js +115 -0
- package/commonjs/src/diagnostics/index.js +22 -0
- package/commonjs/src/diagnostics/trace.js +79 -0
- package/commonjs/src/interop/docker/docker.js +54 -0
- package/commonjs/src/interop/errors/interop.error.js +12 -0
- package/commonjs/src/interop/git/git.js +134 -0
- package/commonjs/src/interop/git/git.types.js +4 -0
- package/commonjs/src/interop/index.js +97 -0
- package/commonjs/src/interop/node/index.js +18 -0
- package/commonjs/src/interop/node/node.js +45 -0
- package/commonjs/src/interop/npm/npm.js +231 -0
- package/commonjs/src/interop/npm/npm.types.js +16 -0
- package/commonjs/src/interop/pnpm/pnpm.js +34 -0
- package/commonjs/src/interop/pnpm/pnpm.types.js +4 -0
- package/commonjs/src/interop/shell/shell.argv.js +45 -0
- package/commonjs/src/interop/shell/shell.js +105 -0
- package/commonjs/src/interop/shell/shell.types.js +4 -0
- package/commonjs/src/interop/types/detect-package-manager.js +71 -0
- package/commonjs/src/interop/types/package-manager.js +4 -0
- package/commonjs/src/interop/yarn/yarn.js +34 -0
- package/commonjs/src/interop/yarn/yarn.types.js +4 -0
- package/commonjs/src/io/find-directories.js +21 -0
- package/commonjs/src/io/find-file.js +45 -0
- package/commonjs/src/io/index.js +20 -0
- package/commonjs/src/io/watch.js +261 -0
- package/es6/src/cli/action.js +47 -0
- package/es6/src/cli/command.js +106 -0
- package/es6/src/cli/index.js +4 -0
- package/es6/src/cli/module.type.js +11 -0
- package/es6/src/cli/options.js +76 -0
- package/es6/src/diagnostics/index.js +1 -0
- package/es6/src/diagnostics/trace.js +61 -0
- package/es6/src/interop/docker/docker.js +44 -0
- package/es6/src/interop/errors/interop.error.js +2 -0
- package/es6/src/interop/git/git.js +124 -0
- package/es6/src/interop/git/git.types.js +1 -0
- package/es6/src/interop/index.js +21 -0
- package/es6/src/interop/node/index.js +1 -0
- package/es6/src/interop/node/node.js +35 -0
- package/es6/src/interop/npm/npm.js +180 -0
- package/es6/src/interop/npm/npm.types.js +6 -0
- package/es6/src/interop/pnpm/pnpm.js +24 -0
- package/es6/src/interop/pnpm/pnpm.types.js +1 -0
- package/es6/src/interop/shell/shell.argv.js +35 -0
- package/es6/src/interop/shell/shell.js +95 -0
- package/es6/src/interop/shell/shell.types.js +1 -0
- package/es6/src/interop/types/detect-package-manager.js +50 -0
- package/es6/src/interop/types/package-manager.js +1 -0
- package/es6/src/interop/yarn/yarn.js +24 -0
- package/es6/src/interop/yarn/yarn.types.js +1 -0
- package/es6/src/io/find-directories.js +11 -0
- package/es6/src/io/find-file.js +27 -0
- package/es6/src/io/index.js +3 -0
- package/es6/src/io/watch.js +238 -0
- package/package.json +49 -0
- package/types/src/cli/action.d.ts +36 -0
- package/types/src/cli/command.d.ts +34 -0
- package/types/src/cli/index.d.ts +7 -0
- package/types/src/cli/module.type.d.ts +36 -0
- package/types/src/cli/options.d.ts +28 -0
- package/types/src/diagnostics/index.d.ts +1 -0
- package/types/src/diagnostics/trace.d.ts +10 -0
- package/types/src/interop/docker/docker.d.ts +19 -0
- package/types/src/interop/errors/interop.error.d.ts +2 -0
- package/types/src/interop/git/git.d.ts +16 -0
- package/types/src/interop/git/git.types.d.ts +14 -0
- package/types/src/interop/index.d.ts +14 -0
- package/types/src/interop/node/index.d.ts +1 -0
- package/types/src/interop/node/node.d.ts +5 -0
- package/types/src/interop/npm/npm.d.ts +29 -0
- package/types/src/interop/npm/npm.types.d.ts +24 -0
- package/types/src/interop/pnpm/pnpm.d.ts +9 -0
- package/types/src/interop/pnpm/pnpm.types.d.ts +3 -0
- package/types/src/interop/shell/shell.argv.d.ts +5 -0
- package/types/src/interop/shell/shell.d.ts +6 -0
- package/types/src/interop/shell/shell.types.d.ts +9 -0
- package/types/src/interop/types/detect-package-manager.d.ts +4 -0
- package/types/src/interop/types/package-manager.d.ts +10 -0
- package/types/src/interop/yarn/yarn.d.ts +9 -0
- package/types/src/interop/yarn/yarn.types.d.ts +3 -0
- package/types/src/io/find-directories.d.ts +2 -0
- package/types/src/io/find-file.d.ts +9 -0
- package/types/src/io/index.d.ts +3 -0
- package/types/src/io/watch.d.ts +16 -0
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { Command as CommanderType } from "commander";
|
|
2
|
+
export const CommandResponse = {
|
|
3
|
+
success (_ctx, responses) {
|
|
4
|
+
return responses;
|
|
5
|
+
},
|
|
6
|
+
help () {
|
|
7
|
+
return {};
|
|
8
|
+
},
|
|
9
|
+
fromAction (name, response) {
|
|
10
|
+
return {
|
|
11
|
+
[name]: response
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
/** Thrown when a command execution fails due to action errors. */ export class CommandError extends Error {
|
|
16
|
+
}
|
|
17
|
+
export class DecohereCommand extends CommanderType {
|
|
18
|
+
get actions() {
|
|
19
|
+
return [];
|
|
20
|
+
}
|
|
21
|
+
constructor(name, handler){
|
|
22
|
+
super();
|
|
23
|
+
if (typeof name === "string" || typeof name === "function") {
|
|
24
|
+
this.name(this.parseCommandName(name));
|
|
25
|
+
} else {
|
|
26
|
+
this.name(this.parseCommandName(this.constructor));
|
|
27
|
+
if (name) {
|
|
28
|
+
this.registerSubcommand(name, handler);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
if (handler && !this.commands.length) {
|
|
32
|
+
this.action(handler);
|
|
33
|
+
}
|
|
34
|
+
this.exitOverride();
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Whether the command, with these options, runs until it is stopped (`flux serve`,
|
|
38
|
+
* `forge build --watch`). The CLI then listens for SIGINT and SIGTERM from the moment it
|
|
39
|
+
* starts, so a stop during start-up still shuts down cleanly; other commands keep Node's
|
|
40
|
+
* default, where Ctrl+C ends them at once.
|
|
41
|
+
*/ runsUntilStopped(_options) {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
async run(actions, options) {
|
|
45
|
+
const responses = await this.runActions(actions, options);
|
|
46
|
+
this.throwOnErrors(responses);
|
|
47
|
+
return CommandResponse.success(options, responses);
|
|
48
|
+
}
|
|
49
|
+
registerSubcommand(commands, onAction) {
|
|
50
|
+
if (typeof commands === "function") {
|
|
51
|
+
const instance = new commands(commands, onAction);
|
|
52
|
+
this.addCommand(instance);
|
|
53
|
+
if (onAction && !instance.commands.length) {
|
|
54
|
+
instance.action(onAction);
|
|
55
|
+
}
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
for (const [key, command] of Object.entries(commands)){
|
|
59
|
+
const instance = command instanceof DecohereCommand ? command : new command(key, onAction);
|
|
60
|
+
this.addCommand(instance);
|
|
61
|
+
if (onAction && !instance.commands.length) {
|
|
62
|
+
instance.action(onAction);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
async runActions(actions, options) {
|
|
67
|
+
const selectedActions = this.getSelectedActions(actions);
|
|
68
|
+
const responses = {};
|
|
69
|
+
for (const [actionKey, action] of selectedActions){
|
|
70
|
+
responses[actionKey] = await action.run(options);
|
|
71
|
+
}
|
|
72
|
+
return responses;
|
|
73
|
+
}
|
|
74
|
+
getSelectedActions(actions) {
|
|
75
|
+
if (!this.actions.length) {
|
|
76
|
+
return [];
|
|
77
|
+
}
|
|
78
|
+
const actionEntries = Object.entries(actions);
|
|
79
|
+
return this.actions.map((ActionClass)=>{
|
|
80
|
+
const selectedAction = actionEntries.find(([, action])=>action instanceof ActionClass);
|
|
81
|
+
if (!selectedAction) {
|
|
82
|
+
throw new CommandError(`${this.name()} command missing action instance for ${ActionClass.name}`);
|
|
83
|
+
}
|
|
84
|
+
return selectedAction;
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
throwOnErrors(responses) {
|
|
88
|
+
const failures = Object.entries(responses).filter(([, response])=>{
|
|
89
|
+
if (!response) {
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
return !response.succeeded && !response.skipped;
|
|
93
|
+
});
|
|
94
|
+
if (!failures.length) {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
throw new CommandError(`${this.name()} command failed. ${failures.map(([actionName, response])=>`${actionName}: ${response?.message ?? "Unknown error"}`).join("\n")}`);
|
|
98
|
+
}
|
|
99
|
+
parseCommandName(stringLike) {
|
|
100
|
+
const command = typeof stringLike === "string" ? stringLike : stringLike.name?.toLowerCase().replace("command", "") ?? "";
|
|
101
|
+
if (command === "") {
|
|
102
|
+
throw new CommandError(`Could not parse command name from ${stringLike.toString()}`);
|
|
103
|
+
}
|
|
104
|
+
return command;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { ActionMap, ActionResponse } from "./action.js";
|
|
2
|
+
export { CommandError, CommandResponse, DecohereCommand } from "./command.js";
|
|
3
|
+
export { ModuleManifest } from "./module.type.js";
|
|
4
|
+
export { ConfigFileOption, ConfigurationOption, DryRunOption, EnvFileIfExistsOption, EnvFileOption, LogLevelOption, NoBuildOptions, NoCleanOptions, ProjectOption } from "./options.js";
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Simple declaration interface for a CLI module.
|
|
3
|
+
* Extend this and implement the four abstract members.
|
|
4
|
+
*/ export class ModuleManifest {
|
|
5
|
+
/** Sub-command classes this module contributes. */ commands = {};
|
|
6
|
+
/** Action classes this module contributes. */ actions = {};
|
|
7
|
+
/** Register infrastructure services into the DI container. */ registerServices(_builder) {}
|
|
8
|
+
/** Lazily create a workspace instance. Return null if this module does not use a workspace. */ createWorkspace(_options) {
|
|
9
|
+
return Promise.resolve(null);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { Option } from "commander";
|
|
2
|
+
function parseOptionalBoolean(input) {
|
|
3
|
+
if (typeof input === "boolean") {
|
|
4
|
+
return input;
|
|
5
|
+
}
|
|
6
|
+
const value = input.trim().toLowerCase();
|
|
7
|
+
if ([
|
|
8
|
+
"true",
|
|
9
|
+
"1",
|
|
10
|
+
"yes",
|
|
11
|
+
"on"
|
|
12
|
+
].includes(value)) {
|
|
13
|
+
return true;
|
|
14
|
+
}
|
|
15
|
+
if ([
|
|
16
|
+
"false",
|
|
17
|
+
"0",
|
|
18
|
+
"no",
|
|
19
|
+
"off"
|
|
20
|
+
].includes(value)) {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
throw new Error(`Invalid boolean value: "${input}". Use true/false.`);
|
|
24
|
+
}
|
|
25
|
+
export class ConfigurationOption extends Option {
|
|
26
|
+
constructor(defaultValue){
|
|
27
|
+
super("--configuration <configuration>", "Configuration name");
|
|
28
|
+
this.default(defaultValue);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
export class ConfigFileOption extends Option {
|
|
32
|
+
constructor(defaultValue = ".decohere.js"){
|
|
33
|
+
super("--config-file <file>", "Path to Decohere config file");
|
|
34
|
+
this.default(defaultValue);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
export class LogLevelOption extends Option {
|
|
38
|
+
constructor(){
|
|
39
|
+
super("--log-level <level>", "Log level (trace, debug, info, warn, error)");
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
export class EnvFileOption extends Option {
|
|
43
|
+
constructor(){
|
|
44
|
+
super("--env-file <file>", "Load environment variables from this file before running");
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
export class EnvFileIfExistsOption extends Option {
|
|
48
|
+
constructor(){
|
|
49
|
+
super("--env-file-if-exists <file>", "Like --env-file, but skip it when the file is missing");
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
export class NoCleanOptions extends Option {
|
|
53
|
+
constructor(defaultValue){
|
|
54
|
+
super("--no-clean", "Will not clean the project directories");
|
|
55
|
+
this.default(defaultValue);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
export class NoBuildOptions extends Option {
|
|
59
|
+
constructor(defaultValue){
|
|
60
|
+
super("--no-build", "Will not run the build step");
|
|
61
|
+
this.default(defaultValue);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
export class DryRunOption extends Option {
|
|
65
|
+
constructor(description, defaultValue = false){
|
|
66
|
+
super("--dry-run [dryRun]", description);
|
|
67
|
+
this.default(defaultValue);
|
|
68
|
+
this.preset(true);
|
|
69
|
+
this.argParser(parseOptionalBoolean);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
export class ProjectOption extends Option {
|
|
73
|
+
constructor(){
|
|
74
|
+
super("--project <project>", "Project name to target");
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Trace, timed, trace } from "./trace.js";
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { isPromise } from "node:util/types";
|
|
2
|
+
import { scribe } from "@decohere/scribe";
|
|
3
|
+
/**
|
|
4
|
+
* Trace mode is toggled with FORGE_TRACE, read when a method is decorated. Raising the log level
|
|
5
|
+
* to see the output is the CLI's job at its entry point: importing this module changes nothing.
|
|
6
|
+
*/ function isTraceEnabled() {
|
|
7
|
+
return process.env.FORGE_TRACE != null;
|
|
8
|
+
}
|
|
9
|
+
function serializeArgs(args) {
|
|
10
|
+
if (args.length === 0) return "";
|
|
11
|
+
return args.map((arg)=>{
|
|
12
|
+
if (Array.isArray(arg)) return `[${arg.map(String).join(", ")}]`;
|
|
13
|
+
const s = String(arg);
|
|
14
|
+
return s.length > 60 ? `${s.slice(0, 57)}...` : s;
|
|
15
|
+
}).join(", ");
|
|
16
|
+
}
|
|
17
|
+
function wrapWithTiming(fn, className, methodName, logLevel) {
|
|
18
|
+
let logger;
|
|
19
|
+
return function(...args) {
|
|
20
|
+
if (logger == null) {
|
|
21
|
+
logger = scribe.logger(className);
|
|
22
|
+
}
|
|
23
|
+
const log = logger;
|
|
24
|
+
const label = `${methodName}(${serializeArgs(args)})`;
|
|
25
|
+
const start = performance.now();
|
|
26
|
+
const logDuration = ()=>{
|
|
27
|
+
log[logLevel](`${label}: ${(performance.now() - start).toFixed(2)}ms`);
|
|
28
|
+
};
|
|
29
|
+
const result = fn.apply(this, args);
|
|
30
|
+
if (isPromise(result)) {
|
|
31
|
+
return result.finally(logDuration);
|
|
32
|
+
}
|
|
33
|
+
logDuration();
|
|
34
|
+
return result;
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
export function trace(target, propertyKey, descriptor) {
|
|
38
|
+
if (!isTraceEnabled()) return descriptor;
|
|
39
|
+
const className = target.constructor.name;
|
|
40
|
+
descriptor.value = wrapWithTiming(descriptor.value, className, propertyKey, "trace");
|
|
41
|
+
return descriptor;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Always-on method timing. Logs duration at `info` regardless of `FORGE_TRACE`.
|
|
45
|
+
* Use this to time a specific method without raising the process log level.
|
|
46
|
+
*/ export function timed(target, propertyKey, descriptor) {
|
|
47
|
+
const className = target.constructor.name;
|
|
48
|
+
descriptor.value = wrapWithTiming(descriptor.value, className, propertyKey, "info");
|
|
49
|
+
return descriptor;
|
|
50
|
+
}
|
|
51
|
+
export function Trace(constructor) {
|
|
52
|
+
if (!isTraceEnabled()) return;
|
|
53
|
+
const className = constructor.name;
|
|
54
|
+
for (const key of Object.getOwnPropertyNames(constructor.prototype)){
|
|
55
|
+
if (key === "constructor") continue;
|
|
56
|
+
const descriptor = Object.getOwnPropertyDescriptor(constructor.prototype, key);
|
|
57
|
+
if (descriptor == null || typeof descriptor.value !== "function") continue;
|
|
58
|
+
descriptor.value = wrapWithTiming(descriptor.value, className, key, "trace");
|
|
59
|
+
Object.defineProperty(constructor.prototype, key, descriptor);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { InteropError } from "../errors/interop.error.js";
|
|
2
|
+
import { Shell } from "../shell/shell.js";
|
|
3
|
+
export class Docker extends Shell {
|
|
4
|
+
name = "docker";
|
|
5
|
+
program = "docker";
|
|
6
|
+
async build(contextDir, options = {}) {
|
|
7
|
+
if (contextDir == null || contextDir.trim() === "") {
|
|
8
|
+
throw new InteropError("Docker build context is required");
|
|
9
|
+
}
|
|
10
|
+
const args = [
|
|
11
|
+
"build"
|
|
12
|
+
];
|
|
13
|
+
if (options.dockerfile) {
|
|
14
|
+
args.push("-f", options.dockerfile);
|
|
15
|
+
}
|
|
16
|
+
for (const tag of options.tags ?? []){
|
|
17
|
+
args.push("-t", tag);
|
|
18
|
+
}
|
|
19
|
+
for (const [key, value] of Object.entries(options.buildArgs ?? {})){
|
|
20
|
+
args.push("--build-arg", `${key}=${value}`);
|
|
21
|
+
}
|
|
22
|
+
for (const [id, env] of Object.entries(options.secrets ?? {})){
|
|
23
|
+
args.push("--secret", `id=${id},env=${env}`);
|
|
24
|
+
}
|
|
25
|
+
if (options.noCache) {
|
|
26
|
+
args.push("--no-cache");
|
|
27
|
+
}
|
|
28
|
+
if (options.pull) {
|
|
29
|
+
args.push("--pull");
|
|
30
|
+
}
|
|
31
|
+
args.push(contextDir);
|
|
32
|
+
return await this.run({
|
|
33
|
+
cwd: options.cwd
|
|
34
|
+
}, ...args);
|
|
35
|
+
}
|
|
36
|
+
async push(tag, options = {}) {
|
|
37
|
+
if (tag == null || tag.trim() === "") {
|
|
38
|
+
throw new InteropError("Docker tag is required");
|
|
39
|
+
}
|
|
40
|
+
return await this.run({
|
|
41
|
+
cwd: options.cwd
|
|
42
|
+
}, "push", tag);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { InteropError } from "../errors/interop.error.js";
|
|
2
|
+
import { Shell } from "../shell/shell.js";
|
|
3
|
+
const Commands = {
|
|
4
|
+
stage: "stage",
|
|
5
|
+
tag: "tag",
|
|
6
|
+
add: "add",
|
|
7
|
+
commit: "commit",
|
|
8
|
+
push: "push",
|
|
9
|
+
listFiles: "ls-files"
|
|
10
|
+
};
|
|
11
|
+
const defaultOptions = {
|
|
12
|
+
cwd: "./"
|
|
13
|
+
};
|
|
14
|
+
export class Git extends Shell {
|
|
15
|
+
program = "git";
|
|
16
|
+
async getModifiedFiles(options = defaultOptions) {
|
|
17
|
+
const args = [
|
|
18
|
+
Commands.listFiles,
|
|
19
|
+
"--modified"
|
|
20
|
+
];
|
|
21
|
+
const { cwd } = options;
|
|
22
|
+
const result = await this.run({
|
|
23
|
+
cwd
|
|
24
|
+
}, ...args);
|
|
25
|
+
return result.data;
|
|
26
|
+
}
|
|
27
|
+
async getBranchName(options = defaultOptions) {
|
|
28
|
+
const { cwd } = options;
|
|
29
|
+
const args = [
|
|
30
|
+
"rev-parse",
|
|
31
|
+
"--symbolic-full-name",
|
|
32
|
+
"--abbrev-ref",
|
|
33
|
+
"HEAD"
|
|
34
|
+
];
|
|
35
|
+
const result = await this.run({
|
|
36
|
+
cwd
|
|
37
|
+
}, ...args);
|
|
38
|
+
return result.data[0];
|
|
39
|
+
}
|
|
40
|
+
async tag(tag, options = defaultOptions) {
|
|
41
|
+
const args = [
|
|
42
|
+
Commands.tag,
|
|
43
|
+
tag
|
|
44
|
+
];
|
|
45
|
+
const { cwd } = options;
|
|
46
|
+
const result = await this.run({
|
|
47
|
+
cwd
|
|
48
|
+
}, ...args);
|
|
49
|
+
return result;
|
|
50
|
+
}
|
|
51
|
+
async pushTag(tag, options = defaultOptions) {
|
|
52
|
+
const { cwd, remote = "origin" } = options;
|
|
53
|
+
const result = await this.run({
|
|
54
|
+
cwd
|
|
55
|
+
}, Commands.push, remote, tag);
|
|
56
|
+
return result;
|
|
57
|
+
}
|
|
58
|
+
async commit(message, options) {
|
|
59
|
+
const optionsAndDefaults = {
|
|
60
|
+
...defaultOptions,
|
|
61
|
+
...options
|
|
62
|
+
};
|
|
63
|
+
const { cwd, files = [], all } = optionsAndDefaults;
|
|
64
|
+
const args = [
|
|
65
|
+
Commands.commit
|
|
66
|
+
];
|
|
67
|
+
if (files.length > 0 && all) {
|
|
68
|
+
throw new InteropError("Cannot commit all and files at the same time");
|
|
69
|
+
}
|
|
70
|
+
if (files.length > 0) {
|
|
71
|
+
await this.stage({
|
|
72
|
+
files
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
if (options.all) {
|
|
76
|
+
args.push("-a");
|
|
77
|
+
}
|
|
78
|
+
args.push("-m", message);
|
|
79
|
+
const result = await this.run({
|
|
80
|
+
cwd
|
|
81
|
+
}, ...args);
|
|
82
|
+
return result;
|
|
83
|
+
}
|
|
84
|
+
async push(branch, options) {
|
|
85
|
+
const optionsAndDefaults = {
|
|
86
|
+
...defaultOptions,
|
|
87
|
+
...options
|
|
88
|
+
};
|
|
89
|
+
const { cwd, setUpstream } = optionsAndDefaults;
|
|
90
|
+
const args = [
|
|
91
|
+
Commands.push
|
|
92
|
+
];
|
|
93
|
+
args.push("--set-upstream", setUpstream ?? "origin");
|
|
94
|
+
args.push(branch);
|
|
95
|
+
const result = await this.run({
|
|
96
|
+
cwd
|
|
97
|
+
}, ...args);
|
|
98
|
+
return result;
|
|
99
|
+
}
|
|
100
|
+
async pull(branch, options = defaultOptions) {
|
|
101
|
+
const { cwd } = options;
|
|
102
|
+
return this.run({
|
|
103
|
+
cwd
|
|
104
|
+
}, "pull", "--rebase", "origin", branch);
|
|
105
|
+
}
|
|
106
|
+
async stage(options) {
|
|
107
|
+
const optionsAndDefaults = {
|
|
108
|
+
...defaultOptions,
|
|
109
|
+
...options
|
|
110
|
+
};
|
|
111
|
+
const { cwd, files = [], all } = optionsAndDefaults;
|
|
112
|
+
if (all) {
|
|
113
|
+
await this.run({
|
|
114
|
+
cwd
|
|
115
|
+
}, Commands.stage, "-a");
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
for (const file of files){
|
|
119
|
+
await this.run({
|
|
120
|
+
cwd
|
|
121
|
+
}, Commands.stage, file);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// << Git >>
|
|
2
|
+
// << Docker >>
|
|
3
|
+
export * from "./docker/docker.js";
|
|
4
|
+
export * from "./git/git.js";
|
|
5
|
+
export * as git from "./git/git.types.js";
|
|
6
|
+
// << Node >>
|
|
7
|
+
export * from "./node/node.js";
|
|
8
|
+
// << Npm >>
|
|
9
|
+
export * from "./npm/npm.js";
|
|
10
|
+
export * as npm from "./npm/npm.types.js";
|
|
11
|
+
// << Pnpm >>
|
|
12
|
+
export * from "./pnpm/pnpm.js";
|
|
13
|
+
export * as pnpm from "./pnpm/pnpm.types.js";
|
|
14
|
+
// << Shell >>
|
|
15
|
+
export * from "./shell/shell.js";
|
|
16
|
+
export * as shell from "./shell/shell.types.js";
|
|
17
|
+
export * from "./types/detect-package-manager.js";
|
|
18
|
+
export * from "./types/package-manager.js";
|
|
19
|
+
// << Yarn >>
|
|
20
|
+
export * from "./yarn/yarn.js";
|
|
21
|
+
export * as yarn from "./yarn/yarn.types.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./node.js";
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { Shell } from "../shell/shell.js";
|
|
3
|
+
export class Node extends Shell {
|
|
4
|
+
program = "node";
|
|
5
|
+
async runScript(script, ...args) {
|
|
6
|
+
const spawnOpt = {
|
|
7
|
+
cwd: ".",
|
|
8
|
+
stdio: "inherit",
|
|
9
|
+
env: {
|
|
10
|
+
...process.env
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
return new Promise((resolve, reject)=>{
|
|
14
|
+
const proc = spawn("node", [
|
|
15
|
+
script,
|
|
16
|
+
...args
|
|
17
|
+
], spawnOpt);
|
|
18
|
+
proc.on("exit", (code)=>{
|
|
19
|
+
if (code !== 0) {
|
|
20
|
+
reject();
|
|
21
|
+
}
|
|
22
|
+
resolve(null);
|
|
23
|
+
});
|
|
24
|
+
proc.on("close", (code)=>{
|
|
25
|
+
resolve(null);
|
|
26
|
+
if (code === 0) {
|
|
27
|
+
resolve({
|
|
28
|
+
message: `${script} completed`
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
reject(`Running '${script}' failed with code ${code}`);
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { InteropError } from "../errors/interop.error.js";
|
|
2
|
+
import { Shell } from "../shell/shell.js";
|
|
3
|
+
const Commands = {
|
|
4
|
+
publish: "publish",
|
|
5
|
+
version: "version",
|
|
6
|
+
link: "link",
|
|
7
|
+
install: "install"
|
|
8
|
+
};
|
|
9
|
+
export class Npm extends Shell {
|
|
10
|
+
name = "npm";
|
|
11
|
+
program = "npm";
|
|
12
|
+
async publish(directory, options = {
|
|
13
|
+
channel: "beta"
|
|
14
|
+
}) {
|
|
15
|
+
const { cwd, dryRun, skipIfPublished, channel, authToken, otp, provenance } = options;
|
|
16
|
+
if (directory == null) {
|
|
17
|
+
throw new InteropError("Directory is required");
|
|
18
|
+
}
|
|
19
|
+
if (skipIfPublished && !dryRun) {
|
|
20
|
+
const alreadyPublished = await this.isPublished(directory, {
|
|
21
|
+
cwd
|
|
22
|
+
});
|
|
23
|
+
if (alreadyPublished) {
|
|
24
|
+
return {
|
|
25
|
+
message: `Skipping publish — version already exists on registry`,
|
|
26
|
+
data: []
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
const args = [
|
|
31
|
+
Commands.publish,
|
|
32
|
+
directory
|
|
33
|
+
];
|
|
34
|
+
let tmpUserConfigPath;
|
|
35
|
+
args.push("--tag", channel ?? "beta");
|
|
36
|
+
if (provenance) {
|
|
37
|
+
args.push("--provenance");
|
|
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
|
+
if (dryRun) {
|
|
45
|
+
args.push("--dry-run");
|
|
46
|
+
}
|
|
47
|
+
const env = {
|
|
48
|
+
...process.env
|
|
49
|
+
};
|
|
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
|
+
try {
|
|
59
|
+
return await this.run({
|
|
60
|
+
cwd,
|
|
61
|
+
env
|
|
62
|
+
}, ...args);
|
|
63
|
+
} catch (error) {
|
|
64
|
+
if (skipIfPublished && !dryRun && isAlreadyPublishedError(error)) {
|
|
65
|
+
return {
|
|
66
|
+
message: `Skipping publish — version already exists on registry`,
|
|
67
|
+
data: []
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
throw error;
|
|
71
|
+
} finally{
|
|
72
|
+
if (tmpUserConfigPath) {
|
|
73
|
+
await this.removeTempUserConfig(tmpUserConfigPath);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* @description Runs in the directory of the package
|
|
79
|
+
* @param options
|
|
80
|
+
* @returns
|
|
81
|
+
*/ async version(versionType, options) {
|
|
82
|
+
const { cwd, noTag, preid } = options;
|
|
83
|
+
const args = [
|
|
84
|
+
Commands.version,
|
|
85
|
+
versionType
|
|
86
|
+
];
|
|
87
|
+
if (noTag) {
|
|
88
|
+
args.push("--no-git-tag-version");
|
|
89
|
+
}
|
|
90
|
+
if (preid) {
|
|
91
|
+
args.push("--preid", preid);
|
|
92
|
+
}
|
|
93
|
+
return this.run({
|
|
94
|
+
cwd
|
|
95
|
+
}, ...args);
|
|
96
|
+
}
|
|
97
|
+
async link(directory, options = {
|
|
98
|
+
cwd: "./"
|
|
99
|
+
}) {
|
|
100
|
+
const { cwd } = options;
|
|
101
|
+
const args = [
|
|
102
|
+
Commands.link,
|
|
103
|
+
directory
|
|
104
|
+
];
|
|
105
|
+
return this.run({
|
|
106
|
+
cwd
|
|
107
|
+
}, ...args);
|
|
108
|
+
}
|
|
109
|
+
async install(options = {
|
|
110
|
+
cwd: "./"
|
|
111
|
+
}) {
|
|
112
|
+
const { cwd } = options;
|
|
113
|
+
return this.run({
|
|
114
|
+
cwd
|
|
115
|
+
}, Commands.install);
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Install only `dependencies` as real folders: `--omit=dev` skips devDependencies and
|
|
119
|
+
* `--no-bin-links` skips the `.bin` symlinks, so the result holds no symlinks at all.
|
|
120
|
+
*/ async installProduction(options = {
|
|
121
|
+
cwd: "./"
|
|
122
|
+
}) {
|
|
123
|
+
const { cwd } = options;
|
|
124
|
+
return this.run({
|
|
125
|
+
cwd
|
|
126
|
+
}, Commands.install, "--omit=dev", "--no-bin-links", "--no-audit", "--no-fund", "--loglevel=error");
|
|
127
|
+
}
|
|
128
|
+
async syncLockFile(options = {
|
|
129
|
+
cwd: "./"
|
|
130
|
+
}) {
|
|
131
|
+
const { cwd } = options;
|
|
132
|
+
return this.run({
|
|
133
|
+
cwd
|
|
134
|
+
}, Commands.install, "--package-lock-only", "--ignore-scripts");
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* True when package@version exists on the registry.
|
|
138
|
+
* Uses ambient npm config (cwd `.npmrc`); do not pass tokens on argv.
|
|
139
|
+
*/ async isPublished(directory, options) {
|
|
140
|
+
const { readFileSync } = await import("node:fs");
|
|
141
|
+
const { join } = await import("node:path");
|
|
142
|
+
try {
|
|
143
|
+
const pkgJson = JSON.parse(readFileSync(join(directory, "package.json"), "utf8"));
|
|
144
|
+
const spec = `${pkgJson.name}@${pkgJson.version}`;
|
|
145
|
+
await this.run(options, "view", spec, "version");
|
|
146
|
+
return true;
|
|
147
|
+
} catch {
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
async createTempUserConfig(authToken) {
|
|
152
|
+
const { mkdtemp, writeFile } = await import("node:fs/promises");
|
|
153
|
+
const { tmpdir } = await import("node:os");
|
|
154
|
+
const { join } = await import("node:path");
|
|
155
|
+
const tempDir = await mkdtemp(join(tmpdir(), "decohere-npmrc-"));
|
|
156
|
+
const npmrcPath = join(tempDir, ".npmrc");
|
|
157
|
+
const npmrc = [
|
|
158
|
+
"registry=https://registry.npmjs.org/",
|
|
159
|
+
"always-auth=true",
|
|
160
|
+
`//registry.npmjs.org/:_authToken=${authToken}`
|
|
161
|
+
].join("\n");
|
|
162
|
+
await writeFile(npmrcPath, `${npmrc}\n`, {
|
|
163
|
+
mode: 0o600
|
|
164
|
+
});
|
|
165
|
+
return npmrcPath;
|
|
166
|
+
}
|
|
167
|
+
async removeTempUserConfig(npmrcPath) {
|
|
168
|
+
const { rm } = await import("node:fs/promises");
|
|
169
|
+
const { dirname } = await import("node:path");
|
|
170
|
+
const dir = dirname(npmrcPath);
|
|
171
|
+
await rm(dir, {
|
|
172
|
+
recursive: true,
|
|
173
|
+
force: true
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
function isAlreadyPublishedError(error) {
|
|
178
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
179
|
+
return /cannot publish over the previously published versions/i.test(message);
|
|
180
|
+
}
|