@expressots/cli 3.0.0 → 4.0.0-preview.2
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 +41 -95
- package/bin/cicd/cli.d.ts +6 -0
- package/bin/cicd/cli.js +126 -0
- package/bin/cicd/form.d.ts +29 -0
- package/bin/cicd/form.js +345 -0
- package/bin/cicd/generators/azure-devops.d.ts +2 -0
- package/bin/cicd/generators/azure-devops.js +370 -0
- package/bin/cicd/generators/bitbucket.d.ts +2 -0
- package/bin/cicd/generators/bitbucket.js +217 -0
- package/bin/cicd/generators/circleci.d.ts +2 -0
- package/bin/cicd/generators/circleci.js +274 -0
- package/bin/cicd/generators/github-actions.d.ts +14 -0
- package/bin/cicd/generators/github-actions.js +426 -0
- package/bin/cicd/generators/gitlab-ci.d.ts +2 -0
- package/bin/cicd/generators/gitlab-ci.js +237 -0
- package/bin/cicd/generators/index.d.ts +6 -0
- package/bin/cicd/generators/index.js +15 -0
- package/bin/cicd/generators/jenkins.d.ts +2 -0
- package/bin/cicd/generators/jenkins.js +248 -0
- package/bin/cicd/generators/template-loader.d.ts +17 -0
- package/bin/cicd/generators/template-loader.js +128 -0
- package/bin/cicd/index.d.ts +1 -0
- package/bin/cicd/index.js +5 -0
- package/bin/cli.d.ts +1 -1
- package/bin/cli.js +18 -3
- package/bin/commands/project.commands.d.ts +19 -6
- package/bin/commands/project.commands.js +390 -61
- package/bin/config/index.d.ts +5 -0
- package/bin/config/index.js +10 -0
- package/bin/config/manager.d.ts +98 -0
- package/bin/config/manager.js +222 -0
- package/bin/containerize/analyzers/bootstrap-analyzer.d.ts +46 -0
- package/bin/containerize/analyzers/bootstrap-analyzer.js +187 -0
- package/bin/containerize/analyzers/project-analyzer.d.ts +20 -0
- package/bin/containerize/analyzers/project-analyzer.js +150 -0
- package/bin/containerize/cli.d.ts +4 -0
- package/bin/containerize/cli.js +113 -0
- package/bin/containerize/form.d.ts +15 -0
- package/bin/containerize/form.js +154 -0
- package/bin/containerize/generators/ci-generator.d.ts +31 -0
- package/bin/containerize/generators/ci-generator.js +936 -0
- package/bin/containerize/generators/docker-compose-generator.d.ts +8 -0
- package/bin/containerize/generators/docker-compose-generator.js +186 -0
- package/bin/containerize/generators/dockerfile-generator.d.ts +8 -0
- package/bin/containerize/generators/dockerfile-generator.js +635 -0
- package/bin/containerize/generators/kubernetes-generator.d.ts +8 -0
- package/bin/containerize/generators/kubernetes-generator.js +133 -0
- package/bin/containerize/generators/template-loader.d.ts +36 -0
- package/bin/containerize/generators/template-loader.js +129 -0
- package/bin/containerize/index.d.ts +4 -0
- package/bin/containerize/index.js +13 -0
- package/bin/containerize/presets/preset-registry.d.ts +20 -0
- package/bin/containerize/presets/preset-registry.js +102 -0
- package/bin/costs/cli.d.ts +5 -0
- package/bin/costs/cli.js +183 -0
- package/bin/costs/form.d.ts +44 -0
- package/bin/costs/form.js +412 -0
- package/bin/costs/index.d.ts +4 -0
- package/bin/costs/index.js +25 -0
- package/bin/costs/pricing-manager.d.ts +84 -0
- package/bin/costs/pricing-manager.js +342 -0
- package/bin/costs/providers/index.d.ts +32 -0
- package/bin/costs/providers/index.js +153 -0
- package/bin/costs/sources/api-source.d.ts +10 -0
- package/bin/costs/sources/api-source.js +32 -0
- package/bin/costs/sources/index.d.ts +6 -0
- package/bin/costs/sources/index.js +15 -0
- package/bin/costs/sources/local-json-source.d.ts +23 -0
- package/bin/costs/sources/local-json-source.js +59 -0
- package/bin/costs/sources/remote-json-source.d.ts +11 -0
- package/bin/costs/sources/remote-json-source.js +53 -0
- package/bin/costs/types.d.ts +53 -0
- package/bin/costs/types.js +5 -0
- package/bin/dev/cli.d.ts +4 -0
- package/bin/dev/cli.js +134 -0
- package/bin/dev/form.d.ts +36 -0
- package/bin/dev/form.js +254 -0
- package/bin/dev/index.d.ts +1 -0
- package/bin/dev/index.js +5 -0
- package/bin/generate/cli.js +29 -2
- package/bin/generate/form.d.ts +5 -1
- package/bin/generate/form.js +3 -3
- package/bin/generate/templates/nonopinionated/config.tpl +12 -0
- package/bin/generate/templates/nonopinionated/event.tpl +10 -0
- package/bin/generate/templates/nonopinionated/guard.tpl +18 -0
- package/bin/generate/templates/nonopinionated/handler.tpl +12 -0
- package/bin/generate/templates/nonopinionated/interceptor.tpl +27 -0
- package/bin/generate/templates/opinionated/config.tpl +47 -0
- package/bin/generate/templates/opinionated/entity.tpl +1 -8
- package/bin/generate/templates/opinionated/event.tpl +15 -0
- package/bin/generate/templates/opinionated/guard.tpl +41 -0
- package/bin/generate/templates/opinionated/handler.tpl +23 -0
- package/bin/generate/templates/opinionated/interceptor.tpl +50 -0
- package/bin/generate/utils/command-utils.d.ts +7 -3
- package/bin/generate/utils/command-utils.js +95 -31
- package/bin/generate/utils/nonopininated-cmd.d.ts +10 -1
- package/bin/generate/utils/nonopininated-cmd.js +100 -1
- package/bin/generate/utils/opinionated-cmd.d.ts +10 -1
- package/bin/generate/utils/opinionated-cmd.js +112 -7
- package/bin/generate/utils/string-utils.d.ts +6 -0
- package/bin/generate/utils/string-utils.js +13 -1
- package/bin/help/form.js +11 -3
- package/bin/migrate/analyzers/platform-detector.d.ts +14 -0
- package/bin/migrate/analyzers/platform-detector.js +116 -0
- package/bin/migrate/cli.d.ts +6 -0
- package/bin/migrate/cli.js +96 -0
- package/bin/migrate/form.d.ts +25 -0
- package/bin/migrate/form.js +347 -0
- package/bin/migrate/generators/compose-to-k8s.d.ts +2 -0
- package/bin/migrate/generators/compose-to-k8s.js +324 -0
- package/bin/migrate/generators/compose-to-railway.d.ts +2 -0
- package/bin/migrate/generators/compose-to-railway.js +138 -0
- package/bin/migrate/generators/compose-to-render.d.ts +2 -0
- package/bin/migrate/generators/compose-to-render.js +148 -0
- package/bin/migrate/generators/generic-migration.d.ts +9 -0
- package/bin/migrate/generators/generic-migration.js +221 -0
- package/bin/migrate/generators/heroku-to-fly.d.ts +2 -0
- package/bin/migrate/generators/heroku-to-fly.js +291 -0
- package/bin/migrate/generators/heroku-to-railway.d.ts +2 -0
- package/bin/migrate/generators/heroku-to-railway.js +283 -0
- package/bin/migrate/generators/heroku-to-render.d.ts +2 -0
- package/bin/migrate/generators/heroku-to-render.js +148 -0
- package/bin/migrate/generators/index.d.ts +7 -0
- package/bin/migrate/generators/index.js +17 -0
- package/bin/migrate/generators/template-loader.d.ts +21 -0
- package/bin/migrate/generators/template-loader.js +59 -0
- package/bin/migrate/index.d.ts +1 -0
- package/bin/migrate/index.js +5 -0
- package/bin/new/cli.js +21 -6
- package/bin/new/form.d.ts +25 -4
- package/bin/new/form.js +285 -70
- package/bin/profile/analyzers/dockerfile-analyzer.d.ts +27 -0
- package/bin/profile/analyzers/dockerfile-analyzer.js +122 -0
- package/bin/profile/analyzers/image-analyzer.d.ts +19 -0
- package/bin/profile/analyzers/image-analyzer.js +85 -0
- package/bin/profile/cli.d.ts +4 -0
- package/bin/profile/cli.js +92 -0
- package/bin/profile/form.d.ts +56 -0
- package/bin/profile/form.js +400 -0
- package/bin/profile/index.d.ts +1 -0
- package/bin/profile/index.js +5 -0
- package/bin/profile/optimizers/index.d.ts +19 -0
- package/bin/profile/optimizers/index.js +137 -0
- package/bin/providers/add/form.d.ts +1 -1
- package/bin/providers/add/form.js +27 -6
- package/bin/providers/create/form.js +2 -1
- package/bin/scripts/form.js +27 -5
- package/bin/studio/cli.d.ts +15 -0
- package/bin/studio/cli.js +166 -0
- package/bin/studio/index.d.ts +5 -0
- package/bin/studio/index.js +9 -0
- package/bin/templates/cache.d.ts +54 -0
- package/bin/templates/cache.js +180 -0
- package/bin/templates/cli.d.ts +8 -0
- package/bin/templates/cli.js +292 -0
- package/bin/templates/fetcher.d.ts +49 -0
- package/bin/templates/fetcher.js +208 -0
- package/bin/templates/index.d.ts +11 -0
- package/bin/templates/index.js +37 -0
- package/bin/templates/manager.d.ts +116 -0
- package/bin/templates/manager.js +323 -0
- package/bin/templates/renderer.d.ts +49 -0
- package/bin/templates/renderer.js +204 -0
- package/bin/templates/types.d.ts +51 -0
- package/bin/templates/types.js +5 -0
- package/bin/utils/add-module-to-container.d.ts +2 -2
- package/bin/utils/add-module-to-container.js +15 -5
- package/bin/utils/cli-ui.d.ts +30 -3
- package/bin/utils/cli-ui.js +95 -13
- package/bin/utils/index.d.ts +4 -0
- package/bin/utils/index.js +4 -0
- package/bin/utils/input-validation.d.ts +50 -0
- package/bin/utils/input-validation.js +143 -0
- package/bin/utils/package-manager-commands.d.ts +24 -0
- package/bin/utils/package-manager-commands.js +50 -0
- package/bin/utils/safe-spawn.d.ts +35 -0
- package/bin/utils/safe-spawn.js +51 -0
- package/bin/utils/update-tsconfig-paths.d.ts +35 -0
- package/bin/utils/update-tsconfig-paths.js +286 -0
- package/package.json +154 -154
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.applyOptimizations = exports.generateOptimizations = void 0;
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
/**
|
|
9
|
+
* Generate optimization recommendations based on Dockerfile analysis
|
|
10
|
+
*/
|
|
11
|
+
function generateOptimizations(analysis) {
|
|
12
|
+
const optimizations = [];
|
|
13
|
+
// Multi-stage build
|
|
14
|
+
if (!analysis.hasMultiStage) {
|
|
15
|
+
optimizations.push({
|
|
16
|
+
id: "OPT001",
|
|
17
|
+
priority: "high",
|
|
18
|
+
category: "Size",
|
|
19
|
+
title: "Use multi-stage build",
|
|
20
|
+
description: "Multi-stage builds significantly reduce final image size by separating build and runtime dependencies.",
|
|
21
|
+
impact: "Can reduce image size by 50-80%",
|
|
22
|
+
autoFixable: false,
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
// Non-root user
|
|
26
|
+
if (!analysis.hasNonRootUser) {
|
|
27
|
+
optimizations.push({
|
|
28
|
+
id: "OPT002",
|
|
29
|
+
priority: "high",
|
|
30
|
+
category: "Security",
|
|
31
|
+
title: "Run as non-root user",
|
|
32
|
+
description: "Running containers as root is a security risk. Create and use a non-root user.",
|
|
33
|
+
impact: "Reduces attack surface if container is compromised",
|
|
34
|
+
autoFixable: false,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
// Health check
|
|
38
|
+
if (!analysis.hasHealthCheck) {
|
|
39
|
+
optimizations.push({
|
|
40
|
+
id: "OPT003",
|
|
41
|
+
priority: "medium",
|
|
42
|
+
category: "Reliability",
|
|
43
|
+
title: "Add HEALTHCHECK instruction",
|
|
44
|
+
description: "Health checks allow orchestrators to detect and restart unhealthy containers.",
|
|
45
|
+
impact: "Faster failure detection, better uptime",
|
|
46
|
+
autoFixable: true,
|
|
47
|
+
fix: (content) => {
|
|
48
|
+
// Add health check before CMD
|
|
49
|
+
const cmdMatch = content.match(/^CMD\s+.*/m);
|
|
50
|
+
if (cmdMatch) {
|
|
51
|
+
const healthCheck = `HEALTHCHECK --interval=30s --timeout=3s --start-period=40s \\
|
|
52
|
+
CMD node -e "require('http').get('http://localhost:3000/health', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})"
|
|
53
|
+
|
|
54
|
+
`;
|
|
55
|
+
return content.replace(cmdMatch[0], healthCheck + cmdMatch[0]);
|
|
56
|
+
}
|
|
57
|
+
return content;
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
// npm ci instead of npm install
|
|
62
|
+
if (analysis.hasNpmInstallWithoutCi) {
|
|
63
|
+
optimizations.push({
|
|
64
|
+
id: "OPT004",
|
|
65
|
+
priority: "low",
|
|
66
|
+
category: "Best Practice",
|
|
67
|
+
title: "Use npm ci instead of npm install",
|
|
68
|
+
description: "npm ci provides faster, more reliable, and reproducible builds.",
|
|
69
|
+
impact: "Faster builds, reproducible dependencies",
|
|
70
|
+
autoFixable: true,
|
|
71
|
+
fix: (content) => {
|
|
72
|
+
return content.replace(/npm install(?!\s+[-\w])/g, "npm ci");
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
// .dockerignore
|
|
77
|
+
if (!analysis.hasDockerignore) {
|
|
78
|
+
optimizations.push({
|
|
79
|
+
id: "OPT005",
|
|
80
|
+
priority: "medium",
|
|
81
|
+
category: "Size",
|
|
82
|
+
title: "Add .dockerignore file",
|
|
83
|
+
description: "Exclude unnecessary files from the build context to speed up builds and reduce image size.",
|
|
84
|
+
impact: "Faster builds, smaller images",
|
|
85
|
+
autoFixable: false,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
// Combine RUN commands
|
|
89
|
+
if (analysis.layers > 10) {
|
|
90
|
+
optimizations.push({
|
|
91
|
+
id: "OPT006",
|
|
92
|
+
priority: "low",
|
|
93
|
+
category: "Size",
|
|
94
|
+
title: "Combine RUN commands",
|
|
95
|
+
description: `You have ${analysis.layers} RUN layers. Consider combining related commands to reduce layers.`,
|
|
96
|
+
impact: "Slightly smaller image, faster pulls",
|
|
97
|
+
autoFixable: false,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
// Check base image
|
|
101
|
+
if (analysis.baseImage &&
|
|
102
|
+
!analysis.baseImage.includes("alpine") &&
|
|
103
|
+
!analysis.baseImage.includes("slim")) {
|
|
104
|
+
optimizations.push({
|
|
105
|
+
id: "OPT007",
|
|
106
|
+
priority: "medium",
|
|
107
|
+
category: "Size",
|
|
108
|
+
title: "Consider using Alpine or slim base image",
|
|
109
|
+
description: `Current base image: ${analysis.baseImage}. Alpine images are typically 5-10x smaller.`,
|
|
110
|
+
impact: "Significantly smaller image size",
|
|
111
|
+
autoFixable: false,
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
return optimizations;
|
|
115
|
+
}
|
|
116
|
+
exports.generateOptimizations = generateOptimizations;
|
|
117
|
+
/**
|
|
118
|
+
* Apply auto-fixable optimizations to Dockerfile
|
|
119
|
+
*/
|
|
120
|
+
async function applyOptimizations(dockerfilePath, optimizations) {
|
|
121
|
+
let content = fs_1.default.readFileSync(dockerfilePath, "utf-8");
|
|
122
|
+
let appliedCount = 0;
|
|
123
|
+
for (const opt of optimizations) {
|
|
124
|
+
if (opt.autoFixable && opt.fix) {
|
|
125
|
+
const newContent = opt.fix(content);
|
|
126
|
+
if (newContent !== content) {
|
|
127
|
+
content = newContent;
|
|
128
|
+
appliedCount++;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
if (appliedCount > 0) {
|
|
133
|
+
fs_1.default.writeFileSync(dockerfilePath, content, "utf-8");
|
|
134
|
+
}
|
|
135
|
+
return appliedCount;
|
|
136
|
+
}
|
|
137
|
+
exports.applyOptimizations = applyOptimizations;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare function addProvider(packageName: string, version?: string, isDevDependency?: boolean): Promise<void>;
|
|
1
|
+
export declare function addProvider(packageName: string, version?: string | false, isDevDependency?: boolean): Promise<void>;
|
|
2
2
|
export declare function removeProvider(packageName: string): Promise<void>;
|
|
@@ -5,10 +5,11 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.removeProvider = exports.addProvider = void 0;
|
|
7
7
|
const chalk_1 = __importDefault(require("chalk"));
|
|
8
|
-
const node_child_process_1 = require("node:child_process");
|
|
9
8
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
10
9
|
const node_process_1 = require("node:process");
|
|
11
10
|
const cli_ui_1 = require("../../utils/cli-ui");
|
|
11
|
+
const input_validation_1 = require("../../utils/input-validation");
|
|
12
|
+
const safe_spawn_1 = require("../../utils/safe-spawn");
|
|
12
13
|
const PACKAGE_MANAGERS = {
|
|
13
14
|
npm: {
|
|
14
15
|
install: "install",
|
|
@@ -38,11 +39,14 @@ function detectPackageManager() {
|
|
|
38
39
|
}
|
|
39
40
|
async function execProcess({ command, args, directory, }) {
|
|
40
41
|
return new Promise((resolve, reject) => {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
42
|
+
// `safeSpawn` (cross-spawn) resolves the Windows `.cmd` shim and
|
|
43
|
+
// applies cmd.exe-aware escaping for every argv entry. Combined
|
|
44
|
+
// with the `isValidPackageName` / `isValidVersion` guards on the
|
|
45
|
+
// caller side, this prevents command injection via the package
|
|
46
|
+
// name or version specifier (which can legitimately contain
|
|
47
|
+
// `>`, `<`, `|`, etc. in a semver range).
|
|
48
|
+
const processRunner = (0, safe_spawn_1.safeSpawn)(command, args, {
|
|
44
49
|
cwd: directory,
|
|
45
|
-
shell: true,
|
|
46
50
|
});
|
|
47
51
|
console.log(chalk_1.default.bold.blue(`Executing: ${command} ${args.join(" ")}`));
|
|
48
52
|
console.log(chalk_1.default.yellow("-------------------------------------------------"));
|
|
@@ -66,6 +70,20 @@ async function execProcess({ command, args, directory, }) {
|
|
|
66
70
|
});
|
|
67
71
|
}
|
|
68
72
|
async function addProvider(packageName, version, isDevDependency = false) {
|
|
73
|
+
if (!(0, input_validation_1.isValidPackageName)(packageName)) {
|
|
74
|
+
(0, cli_ui_1.printError)(`Invalid package name: ${JSON.stringify(packageName)}`, "add-package");
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
// yargs assigns `false` for the version flag when the user omits
|
|
78
|
+
// it (see add/cli.ts). Treat that and "latest" as "no suffix".
|
|
79
|
+
let versionSuffix = "";
|
|
80
|
+
if (typeof version === "string" && version !== "latest") {
|
|
81
|
+
if (!(0, input_validation_1.isValidVersion)(version)) {
|
|
82
|
+
(0, cli_ui_1.printError)(`Invalid version specifier: ${JSON.stringify(version)}`, "add-package");
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
versionSuffix = `@${version}`;
|
|
86
|
+
}
|
|
69
87
|
const packageManager = detectPackageManager();
|
|
70
88
|
if (!packageManager) {
|
|
71
89
|
(0, cli_ui_1.printError)("No package manager found in the project", "add-package");
|
|
@@ -75,7 +93,6 @@ async function addProvider(packageName, version, isDevDependency = false) {
|
|
|
75
93
|
const command = isDevDependency
|
|
76
94
|
? pkgManagerConfig.addDev
|
|
77
95
|
: pkgManagerConfig.install;
|
|
78
|
-
const versionSuffix = version && version !== "latest" ? `@${version}` : "";
|
|
79
96
|
console.log(`${isDevDependency ? "Adding devDependency" : "Installing"} ${packageName}...`);
|
|
80
97
|
await execProcess({
|
|
81
98
|
command: packageManager,
|
|
@@ -85,6 +102,10 @@ async function addProvider(packageName, version, isDevDependency = false) {
|
|
|
85
102
|
}
|
|
86
103
|
exports.addProvider = addProvider;
|
|
87
104
|
async function removeProvider(packageName) {
|
|
105
|
+
if (!(0, input_validation_1.isValidPackageName)(packageName)) {
|
|
106
|
+
(0, cli_ui_1.printError)(`Invalid package name: ${JSON.stringify(packageName)}`, "remove-package");
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
88
109
|
const packageManager = detectPackageManager();
|
|
89
110
|
if (!packageManager) {
|
|
90
111
|
(0, cli_ui_1.printError)("No package manager found in the project", "remove-package");
|
|
@@ -39,7 +39,8 @@ const createExternalProvider = async (provider) => {
|
|
|
39
39
|
]);
|
|
40
40
|
}
|
|
41
41
|
try {
|
|
42
|
-
|
|
42
|
+
// Pinned to the v4.0.0 GA tag, same policy as `expressots new`.
|
|
43
|
+
const emitter = (0, degit_1.default)(`expressots/templates/provider#v4.0.0-preview.1`);
|
|
43
44
|
await emitter.clone(providerInfo.providerName);
|
|
44
45
|
(0, change_package_info_1.changePackageName)({
|
|
45
46
|
directory: providerInfo.providerName,
|
package/bin/scripts/form.js
CHANGED
|
@@ -4,19 +4,28 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.scriptsForm = void 0;
|
|
7
|
-
const child_process_1 = require("child_process");
|
|
8
7
|
const fs_1 = __importDefault(require("fs"));
|
|
9
8
|
const inquirer_1 = __importDefault(require("inquirer"));
|
|
10
9
|
const path_1 = __importDefault(require("path"));
|
|
11
10
|
const cli_ui_1 = require("../utils/cli-ui");
|
|
11
|
+
const input_validation_1 = require("../utils/input-validation");
|
|
12
|
+
const safe_spawn_1 = require("../utils/safe-spawn");
|
|
12
13
|
const cwd = process.cwd();
|
|
13
14
|
const packageJsonPath = path_1.default.join(cwd, "package.json");
|
|
14
15
|
function readPackageJson() {
|
|
16
|
+
let raw;
|
|
15
17
|
try {
|
|
16
|
-
|
|
18
|
+
raw = fs_1.default.readFileSync(packageJsonPath, "utf8");
|
|
17
19
|
}
|
|
18
20
|
catch (e) {
|
|
19
|
-
(0, cli_ui_1.printError)(`Error reading package.json`, "scripts-command");
|
|
21
|
+
(0, cli_ui_1.printError)(`Error reading package.json: ${e.message}`, "scripts-command");
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
try {
|
|
25
|
+
return JSON.parse(raw);
|
|
26
|
+
}
|
|
27
|
+
catch (e) {
|
|
28
|
+
(0, cli_ui_1.printError)(`package.json is not valid JSON: ${e.message}`, "scripts-command");
|
|
20
29
|
process.exit(1);
|
|
21
30
|
}
|
|
22
31
|
}
|
|
@@ -51,14 +60,27 @@ async function promptUserToSelectScripts(scripts) {
|
|
|
51
60
|
}
|
|
52
61
|
function executeScripts(scripts, selectedScripts, runner) {
|
|
53
62
|
selectedScripts.forEach((script) => {
|
|
63
|
+
if (!(0, input_validation_1.isValidScriptName)(script)) {
|
|
64
|
+
(0, cli_ui_1.printWarning)(`Skipping script with invalid name: ${JSON.stringify(script)}`, "scripts-command");
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
54
67
|
console.log(`Running ${script}...`);
|
|
55
68
|
try {
|
|
56
|
-
|
|
69
|
+
// `safeSpawnSync` (cross-spawn) handles Windows `.cmd` shim
|
|
70
|
+
// resolution and cmd.exe-aware argv escaping. Script names
|
|
71
|
+
// are also constrained to a strict alphanumeric/dash/dot
|
|
72
|
+
// charset by `isValidScriptName` above for defense in depth.
|
|
57
73
|
const options = {
|
|
58
74
|
stdio: "inherit",
|
|
59
75
|
env: { ...process.env },
|
|
60
76
|
};
|
|
61
|
-
(0,
|
|
77
|
+
const result = (0, safe_spawn_1.safeSpawnSync)(runner, ["run", script], options);
|
|
78
|
+
if (result.error) {
|
|
79
|
+
throw result.error;
|
|
80
|
+
}
|
|
81
|
+
if (typeof result.status === "number" && result.status !== 0) {
|
|
82
|
+
throw new Error(`exited with code ${result.status}`);
|
|
83
|
+
}
|
|
62
84
|
}
|
|
63
85
|
catch (e) {
|
|
64
86
|
(0, cli_ui_1.printWarning)(`Command ${script} cancelled or failed - ${e}`, "scripts-command");
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Studio command CLI implementation
|
|
3
|
+
*/
|
|
4
|
+
import type { CommandModule } from "yargs";
|
|
5
|
+
interface StudioYargsOptions {
|
|
6
|
+
port: number;
|
|
7
|
+
"agent-port": number;
|
|
8
|
+
"no-browser": boolean;
|
|
9
|
+
src: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Studio command definition
|
|
13
|
+
*/
|
|
14
|
+
export declare function studioCommand(): CommandModule<object, StudioYargsOptions>;
|
|
15
|
+
export {};
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Studio command CLI implementation
|
|
4
|
+
*/
|
|
5
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
6
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
7
|
+
};
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.studioCommand = void 0;
|
|
10
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
11
|
+
const ora_1 = __importDefault(require("ora"));
|
|
12
|
+
const fs_1 = require("fs");
|
|
13
|
+
const path_1 = require("path");
|
|
14
|
+
const safe_spawn_1 = require("../utils/safe-spawn");
|
|
15
|
+
/**
|
|
16
|
+
* Check if @expressots/studio is installed
|
|
17
|
+
*/
|
|
18
|
+
function isStudioInstalled() {
|
|
19
|
+
try {
|
|
20
|
+
// Check if the package exists in node_modules
|
|
21
|
+
const studioPath = (0, path_1.resolve)(process.cwd(), "node_modules/@expressots/studio");
|
|
22
|
+
return (0, fs_1.existsSync)(studioPath);
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Install @expressots/studio as a dev dependency
|
|
30
|
+
*/
|
|
31
|
+
async function installStudio() {
|
|
32
|
+
const spinner = (0, ora_1.default)("Installing @expressots/studio...").start();
|
|
33
|
+
try {
|
|
34
|
+
const hasYarn = (0, fs_1.existsSync)((0, path_1.resolve)(process.cwd(), "yarn.lock"));
|
|
35
|
+
const hasPnpm = (0, fs_1.existsSync)((0, path_1.resolve)(process.cwd(), "pnpm-lock.yaml"));
|
|
36
|
+
let pkgManager;
|
|
37
|
+
let args;
|
|
38
|
+
if (hasPnpm) {
|
|
39
|
+
pkgManager = "pnpm";
|
|
40
|
+
args = ["add", "-D", "@expressots/studio"];
|
|
41
|
+
}
|
|
42
|
+
else if (hasYarn) {
|
|
43
|
+
pkgManager = "yarn";
|
|
44
|
+
args = ["add", "-D", "@expressots/studio"];
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
pkgManager = "npm";
|
|
48
|
+
args = ["install", "-D", "@expressots/studio"];
|
|
49
|
+
}
|
|
50
|
+
// `safeSpawnSync` (cross-spawn) resolves the Windows `.cmd` shim
|
|
51
|
+
// and properly escapes argv for cmd.exe. The argv here is a
|
|
52
|
+
// fixed list of literal strings, so it is safe by construction.
|
|
53
|
+
const result = (0, safe_spawn_1.safeSpawnSync)(pkgManager, args, {
|
|
54
|
+
stdio: "pipe",
|
|
55
|
+
});
|
|
56
|
+
if (result.error)
|
|
57
|
+
throw result.error;
|
|
58
|
+
if (typeof result.status === "number" && result.status !== 0) {
|
|
59
|
+
throw new Error(`exited with code ${result.status}`);
|
|
60
|
+
}
|
|
61
|
+
spinner.succeed(chalk_1.default.green("@expressots/studio installed successfully"));
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
spinner.fail(chalk_1.default.red("Failed to install @expressots/studio"));
|
|
66
|
+
console.error(chalk_1.default.yellow("\nPlease install manually: npm install -D @expressots/studio"));
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Launch the Studio
|
|
72
|
+
*/
|
|
73
|
+
async function launchStudio(options) {
|
|
74
|
+
const args = ["start"];
|
|
75
|
+
if (options.port) {
|
|
76
|
+
args.push("--port", String(options.port));
|
|
77
|
+
}
|
|
78
|
+
if (options.agentPort) {
|
|
79
|
+
args.push("--agent-port", String(options.agentPort));
|
|
80
|
+
}
|
|
81
|
+
if (options.noBrowser) {
|
|
82
|
+
args.push("--no-browser");
|
|
83
|
+
}
|
|
84
|
+
if (options.src) {
|
|
85
|
+
args.push("--src", options.src);
|
|
86
|
+
}
|
|
87
|
+
const isWindows = process.platform === "win32";
|
|
88
|
+
const studioBinName = isWindows
|
|
89
|
+
? "expressots-studio.cmd"
|
|
90
|
+
: "expressots-studio";
|
|
91
|
+
const studioPath = (0, path_1.resolve)(process.cwd(), "node_modules/.bin", studioBinName);
|
|
92
|
+
// `safeSpawn` (cross-spawn) handles Windows `.cmd` shim invocation
|
|
93
|
+
// and per-arg cmd.exe escaping, so user-controlled flags like
|
|
94
|
+
// `--src` or `--port` cannot break out into shell metacharacters
|
|
95
|
+
// even if the project path itself contains spaces.
|
|
96
|
+
const child = (0, safe_spawn_1.safeSpawn)(studioPath, args, {
|
|
97
|
+
stdio: "inherit",
|
|
98
|
+
cwd: process.cwd(),
|
|
99
|
+
});
|
|
100
|
+
child.on("error", (error) => {
|
|
101
|
+
console.error(chalk_1.default.red("Failed to start Studio:"), error.message);
|
|
102
|
+
process.exit(1);
|
|
103
|
+
});
|
|
104
|
+
child.on("exit", (code) => {
|
|
105
|
+
process.exit(code ?? 0);
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Studio command handler
|
|
110
|
+
*/
|
|
111
|
+
async function studioHandler(options) {
|
|
112
|
+
// Check if Studio is installed
|
|
113
|
+
if (!isStudioInstalled()) {
|
|
114
|
+
console.log(chalk_1.default.yellow("📦 @expressots/studio is not installed in this project."));
|
|
115
|
+
console.log("");
|
|
116
|
+
// Try to install
|
|
117
|
+
const installed = await installStudio();
|
|
118
|
+
if (!installed) {
|
|
119
|
+
process.exit(1);
|
|
120
|
+
}
|
|
121
|
+
console.log("");
|
|
122
|
+
}
|
|
123
|
+
// Launch Studio
|
|
124
|
+
await launchStudio(options);
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Studio command definition
|
|
128
|
+
*/
|
|
129
|
+
function studioCommand() {
|
|
130
|
+
return {
|
|
131
|
+
command: "studio",
|
|
132
|
+
describe: "Launch ExpressoTS Studio - Developer Experience Platform",
|
|
133
|
+
builder: (yargs) => yargs
|
|
134
|
+
.option("port", {
|
|
135
|
+
alias: "p",
|
|
136
|
+
type: "number",
|
|
137
|
+
default: 3333,
|
|
138
|
+
description: "UI port",
|
|
139
|
+
})
|
|
140
|
+
.option("agent-port", {
|
|
141
|
+
alias: "a",
|
|
142
|
+
type: "number",
|
|
143
|
+
default: 3334,
|
|
144
|
+
description: "Agent WebSocket port",
|
|
145
|
+
})
|
|
146
|
+
.option("no-browser", {
|
|
147
|
+
type: "boolean",
|
|
148
|
+
default: false,
|
|
149
|
+
description: "Do not open browser automatically",
|
|
150
|
+
})
|
|
151
|
+
.option("src", {
|
|
152
|
+
type: "string",
|
|
153
|
+
default: "./src",
|
|
154
|
+
description: "Source directory to scan",
|
|
155
|
+
}),
|
|
156
|
+
handler: async (argv) => {
|
|
157
|
+
await studioHandler({
|
|
158
|
+
port: argv.port,
|
|
159
|
+
agentPort: argv["agent-port"],
|
|
160
|
+
noBrowser: argv["no-browser"],
|
|
161
|
+
src: argv.src,
|
|
162
|
+
});
|
|
163
|
+
},
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
exports.studioCommand = studioCommand;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* ExpressoTS Studio CLI integration
|
|
4
|
+
* Launches the local Studio development environment
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.studioCommand = void 0;
|
|
8
|
+
var cli_1 = require("./cli");
|
|
9
|
+
Object.defineProperty(exports, "studioCommand", { enumerable: true, get: function () { return cli_1.studioCommand; } });
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Template cache management
|
|
3
|
+
* Stores templates locally for offline access and performance
|
|
4
|
+
*/
|
|
5
|
+
import type { CacheConfig } from "./types";
|
|
6
|
+
export declare class TemplateCache {
|
|
7
|
+
private config;
|
|
8
|
+
constructor(config?: Partial<CacheConfig>);
|
|
9
|
+
/**
|
|
10
|
+
* Ensure cache directory exists
|
|
11
|
+
*/
|
|
12
|
+
private ensureCacheDir;
|
|
13
|
+
/**
|
|
14
|
+
* Generate cache key from template identifier
|
|
15
|
+
*/
|
|
16
|
+
private getCacheKey;
|
|
17
|
+
/**
|
|
18
|
+
* Get cache file path
|
|
19
|
+
*/
|
|
20
|
+
private getCachePath;
|
|
21
|
+
/**
|
|
22
|
+
* Check if cache entry is valid (not expired)
|
|
23
|
+
*/
|
|
24
|
+
private isValid;
|
|
25
|
+
/**
|
|
26
|
+
* Get cached template content
|
|
27
|
+
*/
|
|
28
|
+
get<T>(category: string, platform: string, variant?: string): T | null;
|
|
29
|
+
/**
|
|
30
|
+
* Set cached template content
|
|
31
|
+
*/
|
|
32
|
+
set<T>(category: string, platform: string, data: T, variant?: string, ttl?: number): void;
|
|
33
|
+
/**
|
|
34
|
+
* Delete cached template
|
|
35
|
+
*/
|
|
36
|
+
delete(category: string, platform: string, variant?: string): void;
|
|
37
|
+
/**
|
|
38
|
+
* Clear all cached templates
|
|
39
|
+
*/
|
|
40
|
+
clear(): void;
|
|
41
|
+
/**
|
|
42
|
+
* Get cache statistics
|
|
43
|
+
*/
|
|
44
|
+
getStats(): {
|
|
45
|
+
files: number;
|
|
46
|
+
totalSize: number;
|
|
47
|
+
oldestEntry: Date | null;
|
|
48
|
+
};
|
|
49
|
+
/**
|
|
50
|
+
* Get cache directory path
|
|
51
|
+
*/
|
|
52
|
+
getCacheDirectory(): string;
|
|
53
|
+
}
|
|
54
|
+
export declare function getTemplateCache(config?: Partial<CacheConfig>): TemplateCache;
|