@expressots/cli 3.0.0 → 4.0.0-preview.3
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 +128 -0
- package/bin/cicd/form.d.ts +29 -0
- package/bin/cicd/form.js +346 -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 -5
- package/bin/cli.js +72 -7
- package/bin/commands/project.commands.d.ts +19 -6
- package/bin/commands/project.commands.js +602 -66
- 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 +152 -0
- package/bin/containerize/generators/ci-generator.d.ts +31 -0
- package/bin/containerize/generators/ci-generator.js +940 -0
- package/bin/containerize/generators/docker-compose-generator.d.ts +8 -0
- package/bin/containerize/generators/docker-compose-generator.js +187 -0
- package/bin/containerize/generators/dockerfile-generator.d.ts +8 -0
- package/bin/containerize/generators/dockerfile-generator.js +657 -0
- package/bin/containerize/generators/kubernetes-generator.d.ts +8 -0
- package/bin/containerize/generators/kubernetes-generator.js +134 -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 +185 -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 +136 -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.d.ts +1 -1
- 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 +20 -5
- package/bin/generate/utils/command-utils.js +145 -48
- 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 +128 -16
- package/bin/generate/utils/string-utils.d.ts +6 -0
- package/bin/generate/utils/string-utils.js +13 -1
- package/bin/help/cli.d.ts +1 -1
- package/bin/help/command-help-registry.d.ts +23 -0
- package/bin/help/command-help-registry.js +303 -0
- package/bin/help/command-help.d.ts +36 -0
- package/bin/help/command-help.js +56 -0
- package/bin/help/form.js +127 -22
- package/bin/help/main-help.d.ts +8 -0
- package/bin/help/main-help.js +126 -0
- package/bin/help/render.d.ts +32 -0
- package/bin/help/render.js +46 -0
- package/bin/info/cli.d.ts +1 -1
- package/bin/info/form.d.ts +1 -1
- package/bin/info/form.js +11 -11
- 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 +98 -0
- package/bin/migrate/form.d.ts +25 -0
- package/bin/migrate/form.js +348 -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.d.ts +5 -1
- package/bin/new/cli.js +77 -14
- package/bin/new/form.d.ts +27 -4
- package/bin/new/form.js +605 -75
- 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 +94 -0
- package/bin/profile/form.d.ts +56 -0
- package/bin/profile/form.js +401 -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 +53 -3
- package/bin/scripts/form.js +27 -5
- package/bin/studio/cli.d.ts +15 -0
- package/bin/studio/cli.js +172 -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 +294 -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 +14 -3
- package/bin/utils/add-module-to-container.js +327 -98
- package/bin/utils/cli-ui.d.ts +49 -3
- package/bin/utils/cli-ui.js +133 -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 +326 -0
- package/package.json +165 -156
|
@@ -7,9 +7,44 @@ exports.createExternalProvider = void 0;
|
|
|
7
7
|
const chalk_1 = __importDefault(require("chalk"));
|
|
8
8
|
const degit_1 = __importDefault(require("degit"));
|
|
9
9
|
const inquirer_1 = __importDefault(require("inquirer"));
|
|
10
|
+
const cli_1 = require("../../cli");
|
|
10
11
|
const center_text_1 = require("../../utils/center-text");
|
|
11
12
|
const change_package_info_1 = require("../../utils/change-package-info");
|
|
12
13
|
const cli_ui_1 = require("../../utils/cli-ui");
|
|
14
|
+
/**
|
|
15
|
+
* Override the templates ref/tag, mirroring `EXPRESSOTS_TEMPLATE_REF` in the
|
|
16
|
+
* `new` command. Lets users target a branch (e.g. `feature/v4.0`) before the
|
|
17
|
+
* matching version tag has been pushed.
|
|
18
|
+
*/
|
|
19
|
+
const TEMPLATE_REF_OVERRIDE = process.env.EXPRESSOTS_TEMPLATE_REF?.trim() || "";
|
|
20
|
+
const PREVIEW_FALLBACK_REF = "feature/v4.0";
|
|
21
|
+
function isPreviewBuild() {
|
|
22
|
+
return /-(?:preview|alpha|beta|rc)\b/i.test(cli_1.BUNDLE_VERSION);
|
|
23
|
+
}
|
|
24
|
+
function resolveProviderRef() {
|
|
25
|
+
if (TEMPLATE_REF_OVERRIDE)
|
|
26
|
+
return TEMPLATE_REF_OVERRIDE;
|
|
27
|
+
return `v${cli_1.BUNDLE_VERSION}`;
|
|
28
|
+
}
|
|
29
|
+
async function cloneProviderTemplate(targetDir) {
|
|
30
|
+
const primaryRef = resolveProviderRef();
|
|
31
|
+
const primaryRepo = `expressots/templates/provider#${primaryRef}`;
|
|
32
|
+
try {
|
|
33
|
+
await (0, degit_1.default)(primaryRepo, { force: false }).clone(targetDir);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
catch (err) {
|
|
37
|
+
const isMissingRef = err?.code === "MISSING_REF";
|
|
38
|
+
const canFallback = isMissingRef && !TEMPLATE_REF_OVERRIDE && isPreviewBuild();
|
|
39
|
+
if (!canFallback)
|
|
40
|
+
throw err;
|
|
41
|
+
console.log(chalk_1.default.yellow(`\n⚠ Templates tag "${primaryRef}" not found on GitHub yet — falling back to "${PREVIEW_FALLBACK_REF}". ` +
|
|
42
|
+
`Set EXPRESSOTS_TEMPLATE_REF=<branch-or-tag> to override.`));
|
|
43
|
+
await (0, degit_1.default)(`expressots/templates/provider#${PREVIEW_FALLBACK_REF}`, {
|
|
44
|
+
force: false,
|
|
45
|
+
}).clone(targetDir);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
13
48
|
async function printInfo(providerName) {
|
|
14
49
|
console.log("\n");
|
|
15
50
|
console.log("🐎 Provider", chalk_1.default.green(providerName), "created successfully!");
|
|
@@ -39,8 +74,15 @@ const createExternalProvider = async (provider) => {
|
|
|
39
74
|
]);
|
|
40
75
|
}
|
|
41
76
|
try {
|
|
42
|
-
|
|
43
|
-
|
|
77
|
+
// Pinned to the templates tag matching this CLI's published version,
|
|
78
|
+
// same policy as `expressots new`. BUNDLE_VERSION reads from the
|
|
79
|
+
// CLI's own package.json so the ref always tracks the release.
|
|
80
|
+
//
|
|
81
|
+
// Mirrors the preview-fallback logic in `new/form.ts`: during the
|
|
82
|
+
// preview window the matching `vX.Y.Z` tag may not yet be on
|
|
83
|
+
// `expressots/templates`, so we soft-fall back to the active
|
|
84
|
+
// release branch and warn rather than failing opaquely.
|
|
85
|
+
await cloneProviderTemplate(providerInfo.providerName);
|
|
44
86
|
(0, change_package_info_1.changePackageName)({
|
|
45
87
|
directory: providerInfo.providerName,
|
|
46
88
|
name: providerInfo.providerName,
|
|
@@ -50,7 +92,15 @@ const createExternalProvider = async (provider) => {
|
|
|
50
92
|
}
|
|
51
93
|
catch (err) {
|
|
52
94
|
console.log("\n");
|
|
53
|
-
|
|
95
|
+
const msg = err?.message ? String(err.message) : String(err);
|
|
96
|
+
const code = err?.code ? ` [${err.code}]` : "";
|
|
97
|
+
if (err?.code === "DEST_NOT_EMPTY" ||
|
|
98
|
+
/already exists|not empty/i.test(msg)) {
|
|
99
|
+
(0, cli_ui_1.printError)(`Target folder "${providerInfo.providerName}" already exists or is not empty`, "");
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
(0, cli_ui_1.printError)(`Failed to scaffold provider${code}: ${msg}`, "");
|
|
103
|
+
}
|
|
54
104
|
reject(err);
|
|
55
105
|
}
|
|
56
106
|
});
|
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,172 @@
|
|
|
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 cli_1 = require("../cli");
|
|
15
|
+
const safe_spawn_1 = require("../utils/safe-spawn");
|
|
16
|
+
/**
|
|
17
|
+
* Check if @expressots/studio is installed
|
|
18
|
+
*/
|
|
19
|
+
function isStudioInstalled() {
|
|
20
|
+
try {
|
|
21
|
+
// Check if the package exists in node_modules
|
|
22
|
+
const studioPath = (0, path_1.resolve)(process.cwd(), "node_modules/@expressots/studio");
|
|
23
|
+
return (0, fs_1.existsSync)(studioPath);
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Install @expressots/studio as a dev dependency
|
|
31
|
+
*/
|
|
32
|
+
async function installStudio() {
|
|
33
|
+
const spinner = (0, ora_1.default)("Installing @expressots/studio...").start();
|
|
34
|
+
try {
|
|
35
|
+
const hasYarn = (0, fs_1.existsSync)((0, path_1.resolve)(process.cwd(), "yarn.lock"));
|
|
36
|
+
const hasPnpm = (0, fs_1.existsSync)((0, path_1.resolve)(process.cwd(), "pnpm-lock.yaml"));
|
|
37
|
+
// Pin the studio install to the same minor as the running CLI so
|
|
38
|
+
// `expressots studio` from a preview-N CLI fetches a matching
|
|
39
|
+
// preview-N studio. Falls back to a caret on the major if the CLI
|
|
40
|
+
// version isn't a valid prerelease (defensive).
|
|
41
|
+
const studioSpec = `@expressots/studio@^${cli_1.BUNDLE_VERSION}`;
|
|
42
|
+
let pkgManager;
|
|
43
|
+
let args;
|
|
44
|
+
if (hasPnpm) {
|
|
45
|
+
pkgManager = "pnpm";
|
|
46
|
+
args = ["add", "-D", studioSpec];
|
|
47
|
+
}
|
|
48
|
+
else if (hasYarn) {
|
|
49
|
+
pkgManager = "yarn";
|
|
50
|
+
args = ["add", "-D", studioSpec];
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
pkgManager = "npm";
|
|
54
|
+
args = ["install", "-D", studioSpec];
|
|
55
|
+
}
|
|
56
|
+
// `safeSpawnSync` (cross-spawn) resolves the Windows `.cmd` shim
|
|
57
|
+
// and properly escapes argv for cmd.exe. The argv here is a
|
|
58
|
+
// fixed list of literal strings, so it is safe by construction.
|
|
59
|
+
const result = (0, safe_spawn_1.safeSpawnSync)(pkgManager, args, {
|
|
60
|
+
stdio: "pipe",
|
|
61
|
+
});
|
|
62
|
+
if (result.error)
|
|
63
|
+
throw result.error;
|
|
64
|
+
if (typeof result.status === "number" && result.status !== 0) {
|
|
65
|
+
throw new Error(`exited with code ${result.status}`);
|
|
66
|
+
}
|
|
67
|
+
spinner.succeed(chalk_1.default.green("@expressots/studio installed successfully"));
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
spinner.fail(chalk_1.default.red("Failed to install @expressots/studio"));
|
|
72
|
+
console.error(chalk_1.default.yellow("\nPlease install manually: npm install -D @expressots/studio"));
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Launch the Studio
|
|
78
|
+
*/
|
|
79
|
+
async function launchStudio(options) {
|
|
80
|
+
const args = ["start"];
|
|
81
|
+
if (options.port) {
|
|
82
|
+
args.push("--port", String(options.port));
|
|
83
|
+
}
|
|
84
|
+
if (options.agentPort) {
|
|
85
|
+
args.push("--agent-port", String(options.agentPort));
|
|
86
|
+
}
|
|
87
|
+
if (options.noBrowser) {
|
|
88
|
+
args.push("--no-browser");
|
|
89
|
+
}
|
|
90
|
+
if (options.src) {
|
|
91
|
+
args.push("--src", options.src);
|
|
92
|
+
}
|
|
93
|
+
const isWindows = process.platform === "win32";
|
|
94
|
+
const studioBinName = isWindows
|
|
95
|
+
? "expressots-studio.cmd"
|
|
96
|
+
: "expressots-studio";
|
|
97
|
+
const studioPath = (0, path_1.resolve)(process.cwd(), "node_modules/.bin", studioBinName);
|
|
98
|
+
// `safeSpawn` (cross-spawn) handles Windows `.cmd` shim invocation
|
|
99
|
+
// and per-arg cmd.exe escaping, so user-controlled flags like
|
|
100
|
+
// `--src` or `--port` cannot break out into shell metacharacters
|
|
101
|
+
// even if the project path itself contains spaces.
|
|
102
|
+
const child = (0, safe_spawn_1.safeSpawn)(studioPath, args, {
|
|
103
|
+
stdio: "inherit",
|
|
104
|
+
cwd: process.cwd(),
|
|
105
|
+
});
|
|
106
|
+
child.on("error", (error) => {
|
|
107
|
+
console.error(chalk_1.default.red("Failed to start Studio:"), error.message);
|
|
108
|
+
process.exit(1);
|
|
109
|
+
});
|
|
110
|
+
child.on("exit", (code) => {
|
|
111
|
+
process.exit(code ?? 0);
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Studio command handler
|
|
116
|
+
*/
|
|
117
|
+
async function studioHandler(options) {
|
|
118
|
+
// Check if Studio is installed
|
|
119
|
+
if (!isStudioInstalled()) {
|
|
120
|
+
console.log(chalk_1.default.yellow("📦 @expressots/studio is not installed in this project."));
|
|
121
|
+
console.log("");
|
|
122
|
+
// Try to install
|
|
123
|
+
const installed = await installStudio();
|
|
124
|
+
if (!installed) {
|
|
125
|
+
process.exit(1);
|
|
126
|
+
}
|
|
127
|
+
console.log("");
|
|
128
|
+
}
|
|
129
|
+
// Launch Studio
|
|
130
|
+
await launchStudio(options);
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Studio command definition
|
|
134
|
+
*/
|
|
135
|
+
function studioCommand() {
|
|
136
|
+
return {
|
|
137
|
+
command: "studio",
|
|
138
|
+
describe: "Launch ExpressoTS Studio - Developer Experience Platform",
|
|
139
|
+
builder: (yargs) => yargs
|
|
140
|
+
.option("port", {
|
|
141
|
+
alias: "p",
|
|
142
|
+
type: "number",
|
|
143
|
+
default: 3333,
|
|
144
|
+
description: "UI port",
|
|
145
|
+
})
|
|
146
|
+
.option("agent-port", {
|
|
147
|
+
alias: "a",
|
|
148
|
+
type: "number",
|
|
149
|
+
default: 3334,
|
|
150
|
+
description: "Agent WebSocket port",
|
|
151
|
+
})
|
|
152
|
+
.option("no-browser", {
|
|
153
|
+
type: "boolean",
|
|
154
|
+
default: false,
|
|
155
|
+
description: "Do not open browser automatically",
|
|
156
|
+
})
|
|
157
|
+
.option("src", {
|
|
158
|
+
type: "string",
|
|
159
|
+
default: "./src",
|
|
160
|
+
description: "Source directory to scan",
|
|
161
|
+
}),
|
|
162
|
+
handler: async (argv) => {
|
|
163
|
+
await studioHandler({
|
|
164
|
+
port: argv.port,
|
|
165
|
+
agentPort: argv["agent-port"],
|
|
166
|
+
noBrowser: argv["no-browser"],
|
|
167
|
+
src: argv.src,
|
|
168
|
+
});
|
|
169
|
+
},
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
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;
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Template cache management
|
|
4
|
+
* Stores templates locally for offline access and performance
|
|
5
|
+
*/
|
|
6
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
7
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
8
|
+
};
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.getTemplateCache = exports.TemplateCache = void 0;
|
|
11
|
+
const fs_1 = __importDefault(require("fs"));
|
|
12
|
+
const path_1 = __importDefault(require("path"));
|
|
13
|
+
const os_1 = __importDefault(require("os"));
|
|
14
|
+
const DEFAULT_CACHE_DIR = path_1.default.join(os_1.default.homedir(), ".expressots", "cache", "templates");
|
|
15
|
+
const DEFAULT_TTL = 86400; // 24 hours in seconds
|
|
16
|
+
class TemplateCache {
|
|
17
|
+
constructor(config) {
|
|
18
|
+
this.config = {
|
|
19
|
+
directory: config?.directory || DEFAULT_CACHE_DIR,
|
|
20
|
+
ttl: config?.ttl || DEFAULT_TTL,
|
|
21
|
+
};
|
|
22
|
+
this.ensureCacheDir();
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Ensure cache directory exists
|
|
26
|
+
*/
|
|
27
|
+
ensureCacheDir() {
|
|
28
|
+
if (!fs_1.default.existsSync(this.config.directory)) {
|
|
29
|
+
fs_1.default.mkdirSync(this.config.directory, { recursive: true });
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Generate cache key from template identifier
|
|
34
|
+
*/
|
|
35
|
+
getCacheKey(category, platform, variant) {
|
|
36
|
+
const parts = [category, platform];
|
|
37
|
+
if (variant)
|
|
38
|
+
parts.push(variant);
|
|
39
|
+
return parts.join("-") + ".cache.json";
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Get cache file path
|
|
43
|
+
*/
|
|
44
|
+
getCachePath(key) {
|
|
45
|
+
return path_1.default.join(this.config.directory, key);
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Check if cache entry is valid (not expired)
|
|
49
|
+
*/
|
|
50
|
+
isValid(entry) {
|
|
51
|
+
const now = Date.now();
|
|
52
|
+
const expiresAt = entry.timestamp + entry.ttl * 1000;
|
|
53
|
+
return now < expiresAt;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Get cached template content
|
|
57
|
+
*/
|
|
58
|
+
get(category, platform, variant) {
|
|
59
|
+
const key = this.getCacheKey(category, platform, variant);
|
|
60
|
+
const cachePath = this.getCachePath(key);
|
|
61
|
+
try {
|
|
62
|
+
if (!fs_1.default.existsSync(cachePath)) {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
const content = fs_1.default.readFileSync(cachePath, "utf-8");
|
|
66
|
+
const entry = JSON.parse(content);
|
|
67
|
+
if (!this.isValid(entry)) {
|
|
68
|
+
// Cache expired, remove it
|
|
69
|
+
this.delete(category, platform, variant);
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
return entry.data;
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Set cached template content
|
|
80
|
+
*/
|
|
81
|
+
set(category, platform, data, variant, ttl) {
|
|
82
|
+
const key = this.getCacheKey(category, platform, variant);
|
|
83
|
+
const cachePath = this.getCachePath(key);
|
|
84
|
+
const entry = {
|
|
85
|
+
data,
|
|
86
|
+
timestamp: Date.now(),
|
|
87
|
+
ttl: ttl || this.config.ttl,
|
|
88
|
+
};
|
|
89
|
+
try {
|
|
90
|
+
fs_1.default.writeFileSync(cachePath, JSON.stringify(entry, null, 2), "utf-8");
|
|
91
|
+
}
|
|
92
|
+
catch (error) {
|
|
93
|
+
// Silently fail - cache is optional
|
|
94
|
+
console.error("Failed to write cache:", error);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Delete cached template
|
|
99
|
+
*/
|
|
100
|
+
delete(category, platform, variant) {
|
|
101
|
+
const key = this.getCacheKey(category, platform, variant);
|
|
102
|
+
const cachePath = this.getCachePath(key);
|
|
103
|
+
try {
|
|
104
|
+
if (fs_1.default.existsSync(cachePath)) {
|
|
105
|
+
fs_1.default.unlinkSync(cachePath);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
// Ignore deletion errors
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Clear all cached templates
|
|
114
|
+
*/
|
|
115
|
+
clear() {
|
|
116
|
+
try {
|
|
117
|
+
const files = fs_1.default.readdirSync(this.config.directory);
|
|
118
|
+
for (const file of files) {
|
|
119
|
+
if (file.endsWith(".cache.json")) {
|
|
120
|
+
fs_1.default.unlinkSync(path_1.default.join(this.config.directory, file));
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
// Ignore errors
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Get cache statistics
|
|
130
|
+
*/
|
|
131
|
+
getStats() {
|
|
132
|
+
let files = 0;
|
|
133
|
+
let totalSize = 0;
|
|
134
|
+
let oldestTimestamp = Infinity;
|
|
135
|
+
try {
|
|
136
|
+
const entries = fs_1.default.readdirSync(this.config.directory);
|
|
137
|
+
for (const file of entries) {
|
|
138
|
+
if (!file.endsWith(".cache.json"))
|
|
139
|
+
continue;
|
|
140
|
+
const filePath = path_1.default.join(this.config.directory, file);
|
|
141
|
+
const stat = fs_1.default.statSync(filePath);
|
|
142
|
+
files++;
|
|
143
|
+
totalSize += stat.size;
|
|
144
|
+
try {
|
|
145
|
+
const content = JSON.parse(fs_1.default.readFileSync(filePath, "utf-8"));
|
|
146
|
+
if (content.timestamp < oldestTimestamp) {
|
|
147
|
+
oldestTimestamp = content.timestamp;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
catch {
|
|
151
|
+
// Skip invalid cache files
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
catch {
|
|
156
|
+
// Directory doesn't exist or can't be read
|
|
157
|
+
}
|
|
158
|
+
return {
|
|
159
|
+
files,
|
|
160
|
+
totalSize,
|
|
161
|
+
oldestEntry: oldestTimestamp === Infinity ? null : new Date(oldestTimestamp),
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Get cache directory path
|
|
166
|
+
*/
|
|
167
|
+
getCacheDirectory() {
|
|
168
|
+
return this.config.directory;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
exports.TemplateCache = TemplateCache;
|
|
172
|
+
// Singleton instance
|
|
173
|
+
let cacheInstance = null;
|
|
174
|
+
function getTemplateCache(config) {
|
|
175
|
+
if (!cacheInstance) {
|
|
176
|
+
cacheInstance = new TemplateCache(config);
|
|
177
|
+
}
|
|
178
|
+
return cacheInstance;
|
|
179
|
+
}
|
|
180
|
+
exports.getTemplateCache = getTemplateCache;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Templates CLI command
|
|
3
|
+
* Manage template sources, cache, and configuration
|
|
4
|
+
*/
|
|
5
|
+
import { CommandModule } from "yargs";
|
|
6
|
+
type CommandModuleArgs = Record<string, never>;
|
|
7
|
+
declare const templatesCommand: () => CommandModule<CommandModuleArgs, any>;
|
|
8
|
+
export { templatesCommand };
|