@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.
Files changed (194) hide show
  1. package/README.md +41 -95
  2. package/bin/cicd/cli.d.ts +6 -0
  3. package/bin/cicd/cli.js +128 -0
  4. package/bin/cicd/form.d.ts +29 -0
  5. package/bin/cicd/form.js +346 -0
  6. package/bin/cicd/generators/azure-devops.d.ts +2 -0
  7. package/bin/cicd/generators/azure-devops.js +370 -0
  8. package/bin/cicd/generators/bitbucket.d.ts +2 -0
  9. package/bin/cicd/generators/bitbucket.js +217 -0
  10. package/bin/cicd/generators/circleci.d.ts +2 -0
  11. package/bin/cicd/generators/circleci.js +274 -0
  12. package/bin/cicd/generators/github-actions.d.ts +14 -0
  13. package/bin/cicd/generators/github-actions.js +426 -0
  14. package/bin/cicd/generators/gitlab-ci.d.ts +2 -0
  15. package/bin/cicd/generators/gitlab-ci.js +237 -0
  16. package/bin/cicd/generators/index.d.ts +6 -0
  17. package/bin/cicd/generators/index.js +15 -0
  18. package/bin/cicd/generators/jenkins.d.ts +2 -0
  19. package/bin/cicd/generators/jenkins.js +248 -0
  20. package/bin/cicd/generators/template-loader.d.ts +17 -0
  21. package/bin/cicd/generators/template-loader.js +128 -0
  22. package/bin/cicd/index.d.ts +1 -0
  23. package/bin/cicd/index.js +5 -0
  24. package/bin/cli.d.ts +1 -5
  25. package/bin/cli.js +72 -7
  26. package/bin/commands/project.commands.d.ts +19 -6
  27. package/bin/commands/project.commands.js +602 -66
  28. package/bin/config/index.d.ts +5 -0
  29. package/bin/config/index.js +10 -0
  30. package/bin/config/manager.d.ts +98 -0
  31. package/bin/config/manager.js +222 -0
  32. package/bin/containerize/analyzers/bootstrap-analyzer.d.ts +46 -0
  33. package/bin/containerize/analyzers/bootstrap-analyzer.js +187 -0
  34. package/bin/containerize/analyzers/project-analyzer.d.ts +20 -0
  35. package/bin/containerize/analyzers/project-analyzer.js +150 -0
  36. package/bin/containerize/cli.d.ts +4 -0
  37. package/bin/containerize/cli.js +113 -0
  38. package/bin/containerize/form.d.ts +15 -0
  39. package/bin/containerize/form.js +152 -0
  40. package/bin/containerize/generators/ci-generator.d.ts +31 -0
  41. package/bin/containerize/generators/ci-generator.js +940 -0
  42. package/bin/containerize/generators/docker-compose-generator.d.ts +8 -0
  43. package/bin/containerize/generators/docker-compose-generator.js +187 -0
  44. package/bin/containerize/generators/dockerfile-generator.d.ts +8 -0
  45. package/bin/containerize/generators/dockerfile-generator.js +657 -0
  46. package/bin/containerize/generators/kubernetes-generator.d.ts +8 -0
  47. package/bin/containerize/generators/kubernetes-generator.js +134 -0
  48. package/bin/containerize/generators/template-loader.d.ts +36 -0
  49. package/bin/containerize/generators/template-loader.js +129 -0
  50. package/bin/containerize/index.d.ts +4 -0
  51. package/bin/containerize/index.js +13 -0
  52. package/bin/containerize/presets/preset-registry.d.ts +20 -0
  53. package/bin/containerize/presets/preset-registry.js +102 -0
  54. package/bin/costs/cli.d.ts +5 -0
  55. package/bin/costs/cli.js +185 -0
  56. package/bin/costs/form.d.ts +44 -0
  57. package/bin/costs/form.js +412 -0
  58. package/bin/costs/index.d.ts +4 -0
  59. package/bin/costs/index.js +25 -0
  60. package/bin/costs/pricing-manager.d.ts +84 -0
  61. package/bin/costs/pricing-manager.js +342 -0
  62. package/bin/costs/providers/index.d.ts +32 -0
  63. package/bin/costs/providers/index.js +153 -0
  64. package/bin/costs/sources/api-source.d.ts +10 -0
  65. package/bin/costs/sources/api-source.js +32 -0
  66. package/bin/costs/sources/index.d.ts +6 -0
  67. package/bin/costs/sources/index.js +15 -0
  68. package/bin/costs/sources/local-json-source.d.ts +23 -0
  69. package/bin/costs/sources/local-json-source.js +59 -0
  70. package/bin/costs/sources/remote-json-source.d.ts +11 -0
  71. package/bin/costs/sources/remote-json-source.js +53 -0
  72. package/bin/costs/types.d.ts +53 -0
  73. package/bin/costs/types.js +5 -0
  74. package/bin/dev/cli.d.ts +4 -0
  75. package/bin/dev/cli.js +136 -0
  76. package/bin/dev/form.d.ts +36 -0
  77. package/bin/dev/form.js +254 -0
  78. package/bin/dev/index.d.ts +1 -0
  79. package/bin/dev/index.js +5 -0
  80. package/bin/generate/cli.d.ts +1 -1
  81. package/bin/generate/cli.js +29 -2
  82. package/bin/generate/form.d.ts +5 -1
  83. package/bin/generate/form.js +3 -3
  84. package/bin/generate/templates/nonopinionated/config.tpl +12 -0
  85. package/bin/generate/templates/nonopinionated/event.tpl +10 -0
  86. package/bin/generate/templates/nonopinionated/guard.tpl +18 -0
  87. package/bin/generate/templates/nonopinionated/handler.tpl +12 -0
  88. package/bin/generate/templates/nonopinionated/interceptor.tpl +27 -0
  89. package/bin/generate/templates/opinionated/config.tpl +47 -0
  90. package/bin/generate/templates/opinionated/entity.tpl +1 -8
  91. package/bin/generate/templates/opinionated/event.tpl +15 -0
  92. package/bin/generate/templates/opinionated/guard.tpl +41 -0
  93. package/bin/generate/templates/opinionated/handler.tpl +23 -0
  94. package/bin/generate/templates/opinionated/interceptor.tpl +50 -0
  95. package/bin/generate/utils/command-utils.d.ts +20 -5
  96. package/bin/generate/utils/command-utils.js +145 -48
  97. package/bin/generate/utils/nonopininated-cmd.d.ts +10 -1
  98. package/bin/generate/utils/nonopininated-cmd.js +100 -1
  99. package/bin/generate/utils/opinionated-cmd.d.ts +10 -1
  100. package/bin/generate/utils/opinionated-cmd.js +128 -16
  101. package/bin/generate/utils/string-utils.d.ts +6 -0
  102. package/bin/generate/utils/string-utils.js +13 -1
  103. package/bin/help/cli.d.ts +1 -1
  104. package/bin/help/command-help-registry.d.ts +23 -0
  105. package/bin/help/command-help-registry.js +303 -0
  106. package/bin/help/command-help.d.ts +36 -0
  107. package/bin/help/command-help.js +56 -0
  108. package/bin/help/form.js +127 -22
  109. package/bin/help/main-help.d.ts +8 -0
  110. package/bin/help/main-help.js +126 -0
  111. package/bin/help/render.d.ts +32 -0
  112. package/bin/help/render.js +46 -0
  113. package/bin/info/cli.d.ts +1 -1
  114. package/bin/info/form.d.ts +1 -1
  115. package/bin/info/form.js +11 -11
  116. package/bin/migrate/analyzers/platform-detector.d.ts +14 -0
  117. package/bin/migrate/analyzers/platform-detector.js +116 -0
  118. package/bin/migrate/cli.d.ts +6 -0
  119. package/bin/migrate/cli.js +98 -0
  120. package/bin/migrate/form.d.ts +25 -0
  121. package/bin/migrate/form.js +348 -0
  122. package/bin/migrate/generators/compose-to-k8s.d.ts +2 -0
  123. package/bin/migrate/generators/compose-to-k8s.js +324 -0
  124. package/bin/migrate/generators/compose-to-railway.d.ts +2 -0
  125. package/bin/migrate/generators/compose-to-railway.js +138 -0
  126. package/bin/migrate/generators/compose-to-render.d.ts +2 -0
  127. package/bin/migrate/generators/compose-to-render.js +148 -0
  128. package/bin/migrate/generators/generic-migration.d.ts +9 -0
  129. package/bin/migrate/generators/generic-migration.js +221 -0
  130. package/bin/migrate/generators/heroku-to-fly.d.ts +2 -0
  131. package/bin/migrate/generators/heroku-to-fly.js +291 -0
  132. package/bin/migrate/generators/heroku-to-railway.d.ts +2 -0
  133. package/bin/migrate/generators/heroku-to-railway.js +283 -0
  134. package/bin/migrate/generators/heroku-to-render.d.ts +2 -0
  135. package/bin/migrate/generators/heroku-to-render.js +148 -0
  136. package/bin/migrate/generators/index.d.ts +7 -0
  137. package/bin/migrate/generators/index.js +17 -0
  138. package/bin/migrate/generators/template-loader.d.ts +21 -0
  139. package/bin/migrate/generators/template-loader.js +59 -0
  140. package/bin/migrate/index.d.ts +1 -0
  141. package/bin/migrate/index.js +5 -0
  142. package/bin/new/cli.d.ts +5 -1
  143. package/bin/new/cli.js +77 -14
  144. package/bin/new/form.d.ts +27 -4
  145. package/bin/new/form.js +605 -75
  146. package/bin/profile/analyzers/dockerfile-analyzer.d.ts +27 -0
  147. package/bin/profile/analyzers/dockerfile-analyzer.js +122 -0
  148. package/bin/profile/analyzers/image-analyzer.d.ts +19 -0
  149. package/bin/profile/analyzers/image-analyzer.js +85 -0
  150. package/bin/profile/cli.d.ts +4 -0
  151. package/bin/profile/cli.js +94 -0
  152. package/bin/profile/form.d.ts +56 -0
  153. package/bin/profile/form.js +401 -0
  154. package/bin/profile/index.d.ts +1 -0
  155. package/bin/profile/index.js +5 -0
  156. package/bin/profile/optimizers/index.d.ts +19 -0
  157. package/bin/profile/optimizers/index.js +137 -0
  158. package/bin/providers/add/form.d.ts +1 -1
  159. package/bin/providers/add/form.js +27 -6
  160. package/bin/providers/create/form.js +53 -3
  161. package/bin/scripts/form.js +27 -5
  162. package/bin/studio/cli.d.ts +15 -0
  163. package/bin/studio/cli.js +172 -0
  164. package/bin/studio/index.d.ts +5 -0
  165. package/bin/studio/index.js +9 -0
  166. package/bin/templates/cache.d.ts +54 -0
  167. package/bin/templates/cache.js +180 -0
  168. package/bin/templates/cli.d.ts +8 -0
  169. package/bin/templates/cli.js +294 -0
  170. package/bin/templates/fetcher.d.ts +49 -0
  171. package/bin/templates/fetcher.js +208 -0
  172. package/bin/templates/index.d.ts +11 -0
  173. package/bin/templates/index.js +37 -0
  174. package/bin/templates/manager.d.ts +116 -0
  175. package/bin/templates/manager.js +323 -0
  176. package/bin/templates/renderer.d.ts +49 -0
  177. package/bin/templates/renderer.js +204 -0
  178. package/bin/templates/types.d.ts +51 -0
  179. package/bin/templates/types.js +5 -0
  180. package/bin/utils/add-module-to-container.d.ts +14 -3
  181. package/bin/utils/add-module-to-container.js +327 -98
  182. package/bin/utils/cli-ui.d.ts +49 -3
  183. package/bin/utils/cli-ui.js +133 -13
  184. package/bin/utils/index.d.ts +4 -0
  185. package/bin/utils/index.js +4 -0
  186. package/bin/utils/input-validation.d.ts +50 -0
  187. package/bin/utils/input-validation.js +143 -0
  188. package/bin/utils/package-manager-commands.d.ts +24 -0
  189. package/bin/utils/package-manager-commands.js +50 -0
  190. package/bin/utils/safe-spawn.d.ts +35 -0
  191. package/bin/utils/safe-spawn.js +51 -0
  192. package/bin/utils/update-tsconfig-paths.d.ts +35 -0
  193. package/bin/utils/update-tsconfig-paths.js +326 -0
  194. 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
- const emitter = (0, degit_1.default)(`expressots/templates/provider`);
43
- await emitter.clone(providerInfo.providerName);
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
- (0, cli_ui_1.printError)("Project already exists or Folder is not empty", "");
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
  });
@@ -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
- return JSON.parse(fs_1.default.readFileSync(packageJsonPath, "utf8"));
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
- const command = `${runner} run ${script}`;
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, child_process_1.execSync)(command, options);
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,5 @@
1
+ /**
2
+ * ExpressoTS Studio CLI integration
3
+ * Launches the local Studio development environment
4
+ */
5
+ export { studioCommand } from "./cli";
@@ -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 };