@squidcloud/cli 1.0.450 → 1.0.451

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/dist/index.js CHANGED
@@ -11221,6 +11221,43 @@ const packageJson = __webpack_require__(2108)
11221
11221
 
11222
11222
  const version = packageJson.version
11223
11223
 
11224
+ // Array of tips to display randomly
11225
+ const TIPS = [
11226
+ '🔐 encrypt with Dotenvx: https://dotenvx.com',
11227
+ '🔐 prevent committing .env to code: https://dotenvx.com/precommit',
11228
+ '🔐 prevent building .env in docker: https://dotenvx.com/prebuild',
11229
+ '🤖 agentic secret storage: https://dotenvx.com/as2',
11230
+ '⚡️ secrets for agents: https://dotenvx.com/as2',
11231
+ '🛡️ auth for agents: https://vestauth.com',
11232
+ '🛠️ run anywhere with `dotenvx run -- yourcommand`',
11233
+ '⚙️ specify custom .env file path with { path: \'/custom/path/.env\' }',
11234
+ '⚙️ enable debug logging with { debug: true }',
11235
+ '⚙️ override existing env vars with { override: true }',
11236
+ '⚙️ suppress all logs with { quiet: true }',
11237
+ '⚙️ write to custom object with { processEnv: myObject }',
11238
+ '⚙️ load multiple .env files with { path: [\'.env.local\', \'.env\'] }'
11239
+ ]
11240
+
11241
+ // Get a random tip from the tips array
11242
+ function _getRandomTip () {
11243
+ return TIPS[Math.floor(Math.random() * TIPS.length)]
11244
+ }
11245
+
11246
+ function parseBoolean (value) {
11247
+ if (typeof value === 'string') {
11248
+ return !['false', '0', 'no', 'off', ''].includes(value.toLowerCase())
11249
+ }
11250
+ return Boolean(value)
11251
+ }
11252
+
11253
+ function supportsAnsi () {
11254
+ return process.stdout.isTTY // && process.env.TERM !== 'dumb'
11255
+ }
11256
+
11257
+ function dim (text) {
11258
+ return supportsAnsi() ? `\x1b[2m${text}\x1b[0m` : text
11259
+ }
11260
+
11224
11261
  const LINE = /(?:^|^)\s*(?:export\s+)?([\w.-]+)(?:\s*=\s*?|:\s+?)(\s*'(?:\\'|[^'])*'|\s*"(?:\\"|[^"])*"|\s*`(?:\\`|[^`])*`|[^#\r\n]+)?\s*(?:#.*)?(?:$|$)/mg
11225
11262
 
11226
11263
  // Parse src into an Object
@@ -11306,7 +11343,7 @@ function _parseVault (options) {
11306
11343
  }
11307
11344
 
11308
11345
  function _warn (message) {
11309
- console.log(`[dotenv@${version}][WARN] ${message}`)
11346
+ console.error(`[dotenv@${version}][WARN] ${message}`)
11310
11347
  }
11311
11348
 
11312
11349
  function _debug (message) {
@@ -11404,8 +11441,8 @@ function _resolveHome (envPath) {
11404
11441
  }
11405
11442
 
11406
11443
  function _configVault (options) {
11407
- const debug = Boolean(options && options.debug)
11408
- const quiet = options && 'quiet' in options ? options.quiet : true
11444
+ const debug = parseBoolean(process.env.DOTENV_CONFIG_DEBUG || (options && options.debug))
11445
+ const quiet = parseBoolean(process.env.DOTENV_CONFIG_QUIET || (options && options.quiet))
11409
11446
 
11410
11447
  if (debug || !quiet) {
11411
11448
  _log('Loading env from encrypted .env.vault')
@@ -11426,8 +11463,12 @@ function _configVault (options) {
11426
11463
  function configDotenv (options) {
11427
11464
  const dotenvPath = path.resolve(process.cwd(), '.env')
11428
11465
  let encoding = 'utf8'
11429
- const debug = Boolean(options && options.debug)
11430
- const quiet = options && 'quiet' in options ? options.quiet : true
11466
+ let processEnv = process.env
11467
+ if (options && options.processEnv != null) {
11468
+ processEnv = options.processEnv
11469
+ }
11470
+ let debug = parseBoolean(processEnv.DOTENV_CONFIG_DEBUG || (options && options.debug))
11471
+ let quiet = parseBoolean(processEnv.DOTENV_CONFIG_QUIET || (options && options.quiet))
11431
11472
 
11432
11473
  if (options && options.encoding) {
11433
11474
  encoding = options.encoding
@@ -11467,15 +11508,14 @@ function configDotenv (options) {
11467
11508
  }
11468
11509
  }
11469
11510
 
11470
- let processEnv = process.env
11471
- if (options && options.processEnv != null) {
11472
- processEnv = options.processEnv
11473
- }
11511
+ const populated = DotenvModule.populate(processEnv, parsedAll, options)
11474
11512
 
11475
- DotenvModule.populate(processEnv, parsedAll, options)
11513
+ // handle user settings DOTENV_CONFIG_ options inside .env file(s)
11514
+ debug = parseBoolean(processEnv.DOTENV_CONFIG_DEBUG || debug)
11515
+ quiet = parseBoolean(processEnv.DOTENV_CONFIG_QUIET || quiet)
11476
11516
 
11477
11517
  if (debug || !quiet) {
11478
- const keysCount = Object.keys(parsedAll).length
11518
+ const keysCount = Object.keys(populated).length
11479
11519
  const shortPaths = []
11480
11520
  for (const filePath of optionPaths) {
11481
11521
  try {
@@ -11489,7 +11529,7 @@ function configDotenv (options) {
11489
11529
  }
11490
11530
  }
11491
11531
 
11492
- _log(`injecting env (${keysCount}) from ${shortPaths.join(',')}`)
11532
+ _log(`injecting env (${keysCount}) from ${shortPaths.join(',')} ${dim(`-- tip: ${_getRandomTip()}`)}`)
11493
11533
  }
11494
11534
 
11495
11535
  if (lastError) {
@@ -11553,6 +11593,7 @@ function decrypt (encrypted, keyStr) {
11553
11593
  function populate (processEnv, parsed, options = {}) {
11554
11594
  const debug = Boolean(options && options.debug)
11555
11595
  const override = Boolean(options && options.override)
11596
+ const populated = {}
11556
11597
 
11557
11598
  if (typeof parsed !== 'object') {
11558
11599
  const err = new Error('OBJECT_REQUIRED: Please check the processEnv argument being passed to populate')
@@ -11565,6 +11606,7 @@ function populate (processEnv, parsed, options = {}) {
11565
11606
  if (Object.prototype.hasOwnProperty.call(processEnv, key)) {
11566
11607
  if (override === true) {
11567
11608
  processEnv[key] = parsed[key]
11609
+ populated[key] = parsed[key]
11568
11610
  }
11569
11611
 
11570
11612
  if (debug) {
@@ -11576,8 +11618,11 @@ function populate (processEnv, parsed, options = {}) {
11576
11618
  }
11577
11619
  } else {
11578
11620
  processEnv[key] = parsed[key]
11621
+ populated[key] = parsed[key]
11579
11622
  }
11580
11623
  }
11624
+
11625
+ return populated
11581
11626
  }
11582
11627
 
11583
11628
  const DotenvModule = {
@@ -29682,7 +29727,7 @@ exports.AI_PROVIDER_TYPES = [
29682
29727
  * Public OpenAI chat model names (active models only).
29683
29728
  * @category AI
29684
29729
  */
29685
- exports.OPENAI_CHAT_MODEL_NAMES = ['gpt-5-mini', 'gpt-5-nano', 'gpt-5.2', 'gpt-5.2-pro'];
29730
+ exports.OPENAI_CHAT_MODEL_NAMES = ['gpt-5-mini', 'gpt-5-nano', 'gpt-5.2', 'gpt-5.2-pro', 'gpt-5.4'];
29686
29731
  /**
29687
29732
  * Public Gemini chat model names (active models only).
29688
29733
  * @category AI
@@ -30167,6 +30212,7 @@ Object.defineProperty(exports, "__esModule", ({ value: true }));
30167
30212
  exports.extractFromBuffer = extractFromBuffer;
30168
30213
  exports.checkFileOrDirExists = checkFileOrDirExists;
30169
30214
  exports.assertFileOrDirExists = assertFileOrDirExists;
30215
+ exports.getRuntimeVmLabel = getRuntimeVmLabel;
30170
30216
  const assertic_1 = __webpack_require__(3205);
30171
30217
  const decompress_1 = __importDefault(__webpack_require__(482));
30172
30218
  const fs = __importStar(__webpack_require__(9896));
@@ -30187,6 +30233,14 @@ async function assertFileOrDirExists(path, errorProvider) {
30187
30233
  const exists = await checkFileOrDirExists(path);
30188
30234
  (0, assertic_1.assertTruthy)(exists, errorProvider || `File not exists: ${path}`);
30189
30235
  }
30236
+ function getRuntimeVmLabel() {
30237
+ // @ts-expect-error Bun is not defined in Node.js types
30238
+ if (typeof Bun !== 'undefined') {
30239
+ // @ts-expect-error Bun is not defined in Node.js types
30240
+ return `Bun v${Bun.version}`;
30241
+ }
30242
+ return `Node ${process.version}`;
30243
+ }
30190
30244
 
30191
30245
 
30192
30246
  /***/ },
@@ -30400,14 +30454,12 @@ exports.KOTLIN_CONTROLLERS = [
30400
30454
  'mutation',
30401
30455
  'native-query',
30402
30456
  'observability',
30403
- 'openapi',
30404
30457
  'query',
30405
30458
  'queue',
30406
30459
  'quota',
30407
30460
  'scheduler',
30408
30461
  'secret',
30409
30462
  'storage',
30410
- 'webhooks',
30411
30463
  'ws',
30412
30464
  'internal-extraction',
30413
30465
  'notification',
@@ -30564,6 +30616,7 @@ async function getOpenApiSpecAndControllersFromFile(codeDir) {
30564
30616
  */
30565
30617
  async function populateOpenApiControllersMap(bundleData, codeDir, codeType) {
30566
30618
  const openApiControllersMap = {};
30619
+ const openApiSpecsMap = {};
30567
30620
  const openApiSpecAndControllers = await getOpenApiSpecAndControllersFromFile(codeDir);
30568
30621
  for (const [key, value] of Object.entries(openApiSpecAndControllers.openApiControllersMap)) {
30569
30622
  if (key === 'default' && codeType === 'connector') {
@@ -30572,7 +30625,14 @@ async function populateOpenApiControllersMap(bundleData, codeDir, codeType) {
30572
30625
  }
30573
30626
  openApiControllersMap[key] = JSON.parse(value);
30574
30627
  }
30628
+ for (const [key, value] of Object.entries(openApiSpecAndControllers.openApiSpecsMap)) {
30629
+ if (key === 'default' && codeType === 'connector') {
30630
+ continue;
30631
+ }
30632
+ openApiSpecsMap[key] = value;
30633
+ }
30575
30634
  bundleData.openApiControllersMap = openApiControllersMap;
30635
+ bundleData.openApiSpecsMap = openApiSpecsMap;
30576
30636
  }
30577
30637
 
30578
30638
 
@@ -30879,10 +30939,15 @@ const resolve_1 = __webpack_require__(412);
30879
30939
  const shell_runner_1 = __webpack_require__(3089);
30880
30940
  class TsoaUtils {
30881
30941
  constructor() { }
30882
- static async generateAllSpecs(verbose = false, supportsDefault = true) {
30942
+ /**
30943
+ * @param projectDir - Root directory of the user's backend project.
30944
+ * @param supportsDefault - Include the default tsoa spec (pass false for connectors).
30945
+ * @param verbose - Log detailed progress and diagnostic messages.
30946
+ */
30947
+ static async generateAllSpecs(projectDir, supportsDefault, verbose) {
30883
30948
  const specsMap = {};
30884
30949
  // Read files in the current directory.
30885
- let files = await fsPromises.readdir(process.cwd());
30950
+ let files = await fsPromises.readdir(projectDir);
30886
30951
  // Filter files matching the TSOA pattern (e.g. "tsoa.json", "tsoa-myapp.json", "tsoaAnother.json").
30887
30952
  files = files.filter(file => /^tsoa.*\.json$/.test(file));
30888
30953
  // If an exact "tsoa.json" is not present, get the fallback path.
@@ -30890,7 +30955,7 @@ class TsoaUtils {
30890
30955
  const hasAnyTsoaFilesProvidedByUser = files.length > 0;
30891
30956
  const localBackendModule = await (0, resolve_1.findModulePath)('@squidcloud/local-backend');
30892
30957
  if (localBackendModule) {
30893
- const fallback = path.relative(process.cwd(), path.resolve(localBackendModule, '../../tsoa.json'));
30958
+ const fallback = path.relative(projectDir, path.resolve(localBackendModule, '../../tsoa.json'));
30894
30959
  if (verbose) {
30895
30960
  console.log(`"tsoa.json" not found in current directory. Using fallback: ${fallback}`);
30896
30961
  }
@@ -30901,7 +30966,7 @@ class TsoaUtils {
30901
30966
  // If this directory contains no '@Route' annotations - there are no tsoa controllers and there is no need to
30902
30967
  // run tsoa at all.
30903
30968
  if (!hasAnyTsoaFilesProvidedByUser) {
30904
- const hasTsoaAnnotationsInUserCode = await checkUserCodeUsesTsoa(path.resolve(process.cwd(), 'src'));
30969
+ const hasTsoaAnnotationsInUserCode = await checkUserCodeUsesTsoa(path.resolve(projectDir, 'src'));
30905
30970
  if (!hasTsoaAnnotationsInUserCode) {
30906
30971
  if (verbose) {
30907
30972
  console.log('No TSOA annotations were found in the user code; skipping API generation with TSOA.');
@@ -30928,14 +30993,16 @@ class TsoaUtils {
30928
30993
  console.log(`Processing config file: ${file}`);
30929
30994
  }
30930
30995
  // Use native join to create the folder.
30931
- const outputDir = path.join('dist', specName);
30932
- if (!fs.existsSync(outputDir)) {
30933
- fs.mkdirSync(outputDir, { recursive: true });
30996
+ // absOutputDir is used for Node fs operations; relOutputDir is passed to tsoa (which runs in projectDir).
30997
+ const relOutputDir = path.join('dist', specName);
30998
+ const absOutputDir = path.resolve(projectDir, relOutputDir);
30999
+ if (!fs.existsSync(absOutputDir)) {
31000
+ fs.mkdirSync(absOutputDir, { recursive: true });
30934
31001
  }
30935
31002
  // Read the original TSOA config.
30936
31003
  let configContent;
30937
31004
  try {
30938
- configContent = await fsPromises.readFile(file, { encoding: 'utf8' });
31005
+ configContent = await fsPromises.readFile(path.resolve(projectDir, file), { encoding: 'utf8' });
30939
31006
  }
30940
31007
  catch (e) {
30941
31008
  if (verbose) {
@@ -30954,10 +31021,11 @@ class TsoaUtils {
30954
31021
  throw e;
30955
31022
  }
30956
31023
  // Override the output directories for both spec and routes.
31024
+ // Use relative paths so tsoa (running in projectDir) resolves them correctly.
30957
31025
  config.spec = config.spec || {};
30958
31026
  config.routes = config.routes || {};
30959
- config.spec.outputDirectory = outputDir;
30960
- config.routes.routesDir = outputDir;
31027
+ config.spec.outputDirectory = relOutputDir;
31028
+ config.routes.routesDir = relOutputDir;
30961
31029
  // Fix middlewareTemplate path when using fallback from local-backend
30962
31030
  if (config.routes.middlewareTemplate && !path.isAbsolute(file)) {
30963
31031
  const localBackendModule = await (0, resolve_1.findModulePath)('@squidcloud/local-backend');
@@ -30979,7 +31047,7 @@ class TsoaUtils {
30979
31047
  // Rewrite the middlewareTemplate path to be relative to the current working directory.
30980
31048
  const templatePath = path.resolve(packageRoot, 'dist/local-backend/openapi-template.hbs');
30981
31049
  // Calculate relative path from current working directory (which might be backend/ or test-tsoa/backend/)
30982
- config.routes.middlewareTemplate = path.relative(process.cwd(), templatePath);
31050
+ config.routes.middlewareTemplate = path.relative(projectDir, templatePath);
30983
31051
  if (verbose) {
30984
31052
  console.log(`Rewritten middlewareTemplate path to: ${config.routes.middlewareTemplate}`);
30985
31053
  console.log(`Template absolute path: ${templatePath}`);
@@ -30988,27 +31056,28 @@ class TsoaUtils {
30988
31056
  }
30989
31057
  }
30990
31058
  }
30991
- // Use a relative path for the temporary config file.
30992
- const tempConfigPath = `temp-tsoa-config-${specName}.json`;
31059
+ // Use a relative name for the tsoa command (tsoa runs in projectDir) and absolute path for Node fs operations.
31060
+ const tempConfigName = `temp-tsoa-config-${specName}.json`;
31061
+ const absTempConfigPath = path.resolve(projectDir, tempConfigName);
30993
31062
  try {
30994
- await fsPromises.writeFile(tempConfigPath, JSON.stringify(config, null, 2), { encoding: 'utf8' });
31063
+ await fsPromises.writeFile(absTempConfigPath, JSON.stringify(config, null, 2), { encoding: 'utf8' });
30995
31064
  }
30996
31065
  catch (e) {
30997
31066
  if (verbose) {
30998
- console.error(`Error writing temporary config file ${tempConfigPath}:`, e);
31067
+ console.error(`Error writing temporary config file ${absTempConfigPath}:`, e);
30999
31068
  }
31000
31069
  throw e;
31001
31070
  }
31002
31071
  // Build the TSOA commands (without using -o) that reference the temporary config.
31003
- const specCmd = `npx tsoa spec -c ${tempConfigPath}`;
31004
- const routesCmd = `npx tsoa routes -c ${tempConfigPath}`;
31072
+ const specCmd = `npx tsoa spec -c ${tempConfigName}`;
31073
+ const routesCmd = `npx tsoa routes -c ${tempConfigName}`;
31005
31074
  if (verbose) {
31006
- console.log(`Processing ${file} as "${specName}" with temporary config ${tempConfigPath}`);
31075
+ console.log(`Processing ${file} as "${specName}" with temporary config ${tempConfigName}`);
31007
31076
  console.log(`Running: ${specCmd}`);
31008
31077
  console.log(`Running: ${routesCmd}`);
31009
31078
  }
31010
31079
  try {
31011
- await Promise.all([(0, shell_runner_1.runInShell)(specCmd, false, process.cwd()), (0, shell_runner_1.runInShell)(routesCmd, false, process.cwd())]);
31080
+ await Promise.all([(0, shell_runner_1.runInShell)(specCmd, false, projectDir), (0, shell_runner_1.runInShell)(routesCmd, false, projectDir)]);
31012
31081
  }
31013
31082
  catch (e) {
31014
31083
  const combinedOutput = e?.combinedOutput;
@@ -31018,18 +31087,18 @@ class TsoaUtils {
31018
31087
  console.log(`No controllers found for ${file}, skipping...`);
31019
31088
  }
31020
31089
  // Clean up the temporary config file before continuing.
31021
- await fsPromises.unlink(tempConfigPath);
31090
+ await fsPromises.unlink(absTempConfigPath);
31022
31091
  continue;
31023
31092
  }
31024
31093
  if (verbose) {
31025
31094
  console.error(`Error processing ${file}:\n`, errorMessage);
31026
31095
  }
31027
- await fsPromises.unlink(tempConfigPath);
31096
+ await fsPromises.unlink(absTempConfigPath);
31028
31097
  throw new Error(`Failed to process ${file}: ` + errorMessage);
31029
31098
  }
31030
- // Define output file paths.
31031
- const swaggerOutput = path.join(outputDir, 'swagger.json');
31032
- const routesOutput = path.join(outputDir, 'routes.ts');
31099
+ // Define output file paths using absolute paths for Node fs operations.
31100
+ const swaggerOutput = path.join(absOutputDir, 'swagger.json');
31101
+ const routesOutput = path.join(absOutputDir, 'routes.ts');
31033
31102
  try {
31034
31103
  const swaggerContent = await fsPromises.readFile(swaggerOutput, { encoding: 'utf8' });
31035
31104
  const modifiedSwaggerContent = await TsoaUtils.attachCodeSamples(swaggerContent);
@@ -31043,11 +31112,11 @@ class TsoaUtils {
31043
31112
  if (verbose) {
31044
31113
  console.error(`Error reading generated files for ${file}:`, e);
31045
31114
  }
31046
- await fsPromises.unlink(tempConfigPath);
31115
+ await fsPromises.unlink(absTempConfigPath);
31047
31116
  throw e;
31048
31117
  }
31049
31118
  // Clean up the temporary config file.
31050
- await fsPromises.unlink(tempConfigPath);
31119
+ await fsPromises.unlink(absTempConfigPath);
31051
31120
  }
31052
31121
  const openApiControllersMap = {};
31053
31122
  const openApiSpecsMap = {};
@@ -31261,12 +31330,38 @@ const version_check_1 = __webpack_require__(4827);
31261
31330
  // - Default: colors only if TTY (stdout is a terminal).
31262
31331
  const isTerminal = process.stdout.isTTY;
31263
31332
  const useColorsInOutput = (0, process_env_utils_1.isEnvVarTruthy)('FORCE_COLOR') || (!(0, process_env_utils_1.isEnvVarTruthy)('NO_COLOR') && isTerminal);
31333
+ /**
31334
+ * Add Python source files and requirements.txt to a zip archive.
31335
+ * Used by both Python-only and hybrid builds.
31336
+ */
31337
+ function addPythonFilesToBundle(zip) {
31338
+ const SKIP_DIRS = new Set(['__pycache__', '__python_deps__']);
31339
+ function addPyFiles(dirPath, zipDir) {
31340
+ for (const entry of fsSync.readdirSync(dirPath, { withFileTypes: true })) {
31341
+ if (entry.name.startsWith('.'))
31342
+ continue;
31343
+ const fullPath = path_1.default.join(dirPath, entry.name);
31344
+ if (entry.isDirectory()) {
31345
+ if (SKIP_DIRS.has(entry.name))
31346
+ continue;
31347
+ addPyFiles(fullPath, path_1.default.join(zipDir, entry.name));
31348
+ }
31349
+ else if (entry.isFile() && !entry.name.endsWith('.pyc')) {
31350
+ zip.addLocalFile(fullPath, zipDir || undefined);
31351
+ }
31352
+ }
31353
+ }
31354
+ addPyFiles('src', '');
31355
+ if (fsSync.existsSync('requirements.txt')) {
31356
+ zip.addLocalFile('requirements.txt');
31357
+ }
31358
+ }
31264
31359
  async function build({ verbose, dev, skipVersionCheck }) {
31265
31360
  const buildPhaseStart = Date.now();
31266
31361
  (0, global_utils_1.enableDebugLogs)(verbose || (0, global_utils_1.isDebugEnabled)());
31267
31362
  (0, logpipes_1.installConsoleOverrides)(enable_debug_decorator_utils_1.debugLogFilterPipe);
31268
31363
  console.debug(`Starting Squid project build. CLI package version: ${packageJson.version}`);
31269
- await (0, validate_1.validateSquidProject)();
31364
+ const projectInfo = await (0, validate_1.validateSquidProject)();
31270
31365
  // Start version checks in the background if not disabled.
31271
31366
  const shouldSkipVersionCheck = skipVersionCheck || (0, process_env_utils_1.isEnvVarTruthy)('SQUID_SKIP_BUILD_TIME_VERSION_CHECK');
31272
31367
  // SQUID_CURRENT_CLI_VERSION_OVERRIDE is used for testing.
@@ -31278,9 +31373,23 @@ async function build({ verbose, dev, skipVersionCheck }) {
31278
31373
  await fs_1.promises.rm(distPath, { recursive: true, force: true });
31279
31374
  }
31280
31375
  await fs_1.promises.mkdir(distPath);
31376
+ // Python-only projects: zip src/ + requirements.txt, no webpack.
31377
+ if (projectInfo.isPython && !projectInfo.isHybrid) {
31378
+ console.log((0, logging_1.primary)('Building Python bundle...'));
31379
+ const bundlePath = path_1.default.join(distPath, 'bundle.zip');
31380
+ const zip = new adm_zip_1.default();
31381
+ addPythonFilesToBundle(zip);
31382
+ zip.writeZip(bundlePath);
31383
+ const stats = fsSync.statSync(bundlePath);
31384
+ console.log(`Python bundle created: ${bundlePath} (${(stats.size / 1024).toFixed(1)} KB)`);
31385
+ await (0, version_check_1.displayVersionWarningIfOutdated)(versionCheckPromise);
31386
+ (0, update_skills_1.displaySkillsWarningIfOutdated)();
31387
+ console.debug((0, log_utils_1.timeSince)(buildPhaseStart, 'Build time '));
31388
+ return;
31389
+ }
31281
31390
  const isSquidConnector = (0, process_env_utils_1.isEnvVarTruthy)('SQUID_CONNECTOR');
31282
31391
  const specsStart = Date.now();
31283
- const openApiSpecAndControllers = await tsoa_utils_1.TsoaUtils.generateAllSpecs(verbose, !isSquidConnector);
31392
+ const openApiSpecAndControllers = await tsoa_utils_1.TsoaUtils.generateAllSpecs(process.cwd(), !isSquidConnector, verbose);
31284
31393
  console.debug((0, log_utils_1.timeSince)(specsStart, 'OpenAPI specs generation time '));
31285
31394
  await fs_1.promises.writeFile(path_1.default.join(distPath, '', 'openapi-spec-and-controllers.json'), JSON.stringify(openApiSpecAndControllers));
31286
31395
  const isUserConfigMode = fsSync.existsSync(resolve_2.USER_WEBPACK_CONFIG_PATH);
@@ -31321,6 +31430,15 @@ async function build({ verbose, dev, skipVersionCheck }) {
31321
31430
  resolve();
31322
31431
  });
31323
31432
  });
31433
+ // For hybrid projects, add Python files to the webpack-produced bundle.
31434
+ if (projectInfo.isHybrid) {
31435
+ console.log('Adding Python files to hybrid bundle...');
31436
+ const zipPath = path_1.default.join(distPath, 'bundle.zip');
31437
+ const zip = new adm_zip_1.default(zipPath);
31438
+ addPythonFilesToBundle(zip);
31439
+ zip.writeZip(zipPath);
31440
+ console.log('Python files added to bundle.');
31441
+ }
31324
31442
  // Generate connector metadata if this is a connector build.
31325
31443
  if (isSquidConnector) {
31326
31444
  console.debug('Generating connector metadata...');
@@ -31461,6 +31579,7 @@ async function deploy(consoleRegion, appId, bundlePath, apiKey, verbose, direct,
31461
31579
  (0, logpipes_1.installConsoleOverrides)(enable_debug_decorator_utils_1.debugLogFilterPipe);
31462
31580
  const versionCheckPromise = (0, version_check_1.checkCliVersion)(packageJson.version);
31463
31581
  if (!direct && !isUserSpecifiedPath && !skipBuild) {
31582
+ // `squid build` handles all project types: TS, Python-only, and hybrid.
31464
31583
  console.log('Building code bundle...');
31465
31584
  await (0, shell_runner_1.runInShell)('npm run build');
31466
31585
  }
@@ -31708,7 +31827,8 @@ const update_skills_1 = __webpack_require__(7286);
31708
31827
  const process_utils_1 = __webpack_require__(8251);
31709
31828
  const validate_1 = __webpack_require__(2246);
31710
31829
  function setupDotEnv(baseDir) {
31711
- dotenv.config({ path: path_1.default.resolve(baseDir || './', '.env') });
31830
+ const envPath = path_1.default.resolve(baseDir || './', '.env');
31831
+ dotenv.config({ path: envPath });
31712
31832
  }
31713
31833
  function run() {
31714
31834
  (0, yargs_1.default)(process.argv.slice(2))
@@ -32202,6 +32322,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
32202
32322
  };
32203
32323
  Object.defineProperty(exports, "__esModule", ({ value: true }));
32204
32324
  exports.start = start;
32325
+ const fs_1 = __webpack_require__(9896);
32205
32326
  const fs = __importStar(__webpack_require__(1943));
32206
32327
  const path_1 = __importDefault(__webpack_require__(6928));
32207
32328
  const resolve_1 = __webpack_require__(412);
@@ -32213,47 +32334,99 @@ const process_utils_1 = __webpack_require__(8251);
32213
32334
  const validate_1 = __webpack_require__(2246);
32214
32335
  const version_check_1 = __webpack_require__(4827);
32215
32336
  async function start() {
32216
- const packageJson = await (0, validate_1.validateSquidProject)();
32337
+ const projectInfo = await (0, validate_1.validateSquidProject)();
32217
32338
  const versionCheckPromise = (0, version_check_1.checkCliVersion)(cliPackageJson.version);
32218
32339
  console.log((0, logging_1.primary)('Please note:'), 'to debug your application, you need to run the "start" npm script in your IDE in debug mode');
32219
32340
  await (0, version_check_1.displayVersionWarningIfOutdated)(versionCheckPromise);
32220
32341
  (0, update_skills_1.displaySkillsWarningIfOutdated)();
32221
32342
  console.log((0, logging_1.primary)('Starting...'));
32343
+ if (projectInfo.isPython && !projectInfo.isHybrid) {
32344
+ await startPython();
32345
+ return;
32346
+ }
32347
+ // For hybrid projects, log Python detection
32348
+ if (projectInfo.isHybrid) {
32349
+ console.log((0, logging_1.primary)('Hybrid project detected:'), 'TypeScript + Python');
32350
+ }
32351
+ const packageJson = projectInfo.packageJson;
32352
+ // Check for local development override
32353
+ const localBackendPath = process.env['SQUID_LOCAL_BACKEND_PATH'];
32354
+ if (localBackendPath) {
32355
+ // Local development mode: use nest start from local-backend with SQUID_BACKEND_PATH
32356
+ console.log('Using local-backend from SQUID_LOCAL_BACKEND_PATH:', localBackendPath);
32357
+ const backendPath = process.cwd();
32358
+ await (0, shell_runner_1.runInShell)(`npx nest start --exec "node -r ts-node/register -r tsconfig-paths/register src/main.ts"`, true, localBackendPath, { SQUID_BACKEND_PATH: backendPath });
32359
+ return;
32360
+ }
32222
32361
  const modulePath = await (0, resolve_1.findModulePath)('@squidcloud/local-backend');
32223
32362
  if (!modulePath) {
32224
32363
  (0, process_utils_1.exitWithError)(validate_1.INVALID_PROJECT_ERROR);
32225
32364
  }
32226
32365
  if (packageJson.scripts?.['start-squid']) {
32227
32366
  await (0, shell_runner_1.runInShell)(`npm run start-squid`);
32367
+ return;
32228
32368
  }
32229
- else {
32230
- // Detect if CLI is running as TypeScript (dev mode)
32231
- const isDevMode = __filename.endsWith('.ts');
32232
- let mainPath;
32233
- // In dev mode, try to use local-backend source files
32234
- if (isDevMode) {
32235
- const localBackendSrc = path_1.default.resolve(__dirname, '../../local-backend/src/main.ts');
32236
- try {
32237
- await fs.access(localBackendSrc);
32238
- mainPath = localBackendSrc;
32239
- console.log((0, logging_1.primary)('Dev mode detected:'), `using local-backend source files: ${mainPath}`);
32240
- }
32241
- catch (_) {
32242
- // Fall back to built version if source files not found
32243
- mainPath = path_1.default.resolve(modulePath, '../main.js');
32244
- }
32245
- }
32246
- else {
32247
- mainPath = path_1.default.resolve(modulePath, '../main.js');
32248
- }
32369
+ // Detect if CLI is running as TypeScript (dev mode)
32370
+ const isDevMode = __filename.endsWith('.ts');
32371
+ let mainPath;
32372
+ // In dev mode, try to use local-backend source files
32373
+ if (isDevMode) {
32374
+ const localBackendSrc = path_1.default.resolve(__dirname, '../../local-backend/src/main.ts');
32249
32375
  try {
32250
- await fs.access(mainPath);
32251
- await (0, shell_runner_1.runInShell)(`npx nodemon --watch ./src --watch ./.env --ext ts,js,json --quiet --exec node -r ts-node/register -r tsconfig-paths/register "${mainPath}"`);
32376
+ await fs.access(localBackendSrc);
32377
+ mainPath = localBackendSrc;
32378
+ console.log((0, logging_1.primary)('Dev mode detected:'), `using local-backend source files: ${mainPath}`);
32252
32379
  }
32253
32380
  catch (_) {
32254
- (0, process_utils_1.exitWithError)(validate_1.INVALID_PROJECT_ERROR);
32381
+ // Fall back to built version if source files not found
32382
+ mainPath = path_1.default.resolve(modulePath, '../main.js');
32255
32383
  }
32256
32384
  }
32385
+ else {
32386
+ mainPath = path_1.default.resolve(modulePath, '../main.js');
32387
+ }
32388
+ try {
32389
+ await fs.access(mainPath);
32390
+ const ext = projectInfo.isHybrid ? 'py,ts,js,json' : 'ts,js,json';
32391
+ const ignore = projectInfo.isHybrid ? ' --ignore __python_deps__' : '';
32392
+ await (0, shell_runner_1.runInShell)(`npx nodemon --watch ./src --watch ./.env${ignore} --ext ${ext} --quiet --exec node -r ts-node/register -r tsconfig-paths/register "${mainPath}"`);
32393
+ }
32394
+ catch (_) {
32395
+ (0, process_utils_1.exitWithError)(validate_1.INVALID_PROJECT_ERROR);
32396
+ }
32397
+ }
32398
+ async function startPython() {
32399
+ console.log((0, logging_1.primary)('Python backend detected.'));
32400
+ const backendPath = process.cwd();
32401
+ // Resolve local-backend path (realpathSync handles symlinks from npm link)
32402
+ const modulePath = await (0, resolve_1.findModulePath)('@squidcloud/local-backend');
32403
+ let localBackendDir;
32404
+ const isDevMode = __filename.endsWith('.ts');
32405
+ if (isDevMode) {
32406
+ localBackendDir = path_1.default.resolve(__dirname, '../../local-backend');
32407
+ }
32408
+ else if (modulePath) {
32409
+ // modulePath points to the package entry (e.g. dist/index.js), resolve to package root
32410
+ localBackendDir = (0, fs_1.realpathSync)(path_1.default.resolve(modulePath, '..'));
32411
+ }
32412
+ else {
32413
+ (0, process_utils_1.exitWithError)(validate_1.INVALID_PROJECT_ERROR);
32414
+ }
32415
+ const tsconfigPath = path_1.default.resolve(localBackendDir, 'tsconfig.json');
32416
+ const mainPath = isDevMode ? path_1.default.resolve(localBackendDir, 'src/main.ts') : path_1.default.resolve(localBackendDir, 'main.js');
32417
+ // Python requirements are installed by the local-backend process (with --target for isolation).
32418
+ // No global pip install needed here.
32419
+ const envVars = {
32420
+ SQUID_BACKEND_PATH: backendPath,
32421
+ TS_NODE_PROJECT: tsconfigPath,
32422
+ };
32423
+ try {
32424
+ await fs.access(mainPath);
32425
+ await (0, shell_runner_1.runInShell)(`npx nodemon --watch ./src --watch ./.env --ignore __python_deps__ --ext py,ts,js,json --quiet --exec node -r ts-node/register -r tsconfig-paths/register "${mainPath}"`, true, backendPath, envVars);
32426
+ }
32427
+ catch (_) {
32428
+ (0, process_utils_1.exitWithError)(validate_1.INVALID_PROJECT_ERROR);
32429
+ }
32257
32430
  }
32258
32431
 
32259
32432
 
@@ -32589,6 +32762,7 @@ async function reportLocalBackendInitialized(consoleRegion, appId, apiKey) {
32589
32762
  try {
32590
32763
  const headers = new Headers();
32591
32764
  headers.append('Authorization', `ApiKey ${apiKey}`);
32765
+ headers.append('Content-Type', 'application/json');
32592
32766
  const response = await fetch(url, { method: 'POST', body: JSON.stringify({ appId }), headers });
32593
32767
  if (!response.ok) {
32594
32768
  const responseText = await response.text();
@@ -32668,15 +32842,40 @@ exports.validateSquidProject = validateSquidProject;
32668
32842
  const promises_1 = __importDefault(__webpack_require__(1943));
32669
32843
  const process_utils_1 = __webpack_require__(8251);
32670
32844
  exports.INVALID_PROJECT_ERROR = 'Please make sure you are running this command in your Squid project directory';
32845
+ async function fileExists(filePath) {
32846
+ try {
32847
+ await promises_1.default.access(filePath);
32848
+ return true;
32849
+ }
32850
+ catch {
32851
+ return false;
32852
+ }
32853
+ }
32671
32854
  async function validateSquidProject() {
32855
+ const hasPython = (await fileExists('src/main.py')) || (await fileExists('src/index.py'));
32856
+ const hasTs = await fileExists('src/index.ts');
32857
+ const isHybrid = hasPython && hasTs;
32858
+ const isPythonOnly = hasPython && !hasTs;
32859
+ if (isPythonOnly) {
32860
+ // Python-only projects may not have package.json with start scripts
32861
+ let packageJson = {};
32862
+ try {
32863
+ const raw = await promises_1.default.readFile('package.json', 'utf8');
32864
+ packageJson = JSON.parse(raw);
32865
+ }
32866
+ catch {
32867
+ // No package.json is fine for Python projects
32868
+ }
32869
+ return { packageJson, isPython: true, isHybrid: false };
32870
+ }
32672
32871
  try {
32673
- const packageJson = await promises_1.default.readFile('package.json', 'utf8');
32674
- const parsedJson = JSON.parse(packageJson);
32872
+ const packageJsonRaw = await promises_1.default.readFile('package.json', 'utf8');
32873
+ const parsedJson = JSON.parse(packageJsonRaw);
32675
32874
  const { scripts } = parsedJson;
32676
32875
  if (!scripts?.['start-squid'] && !scripts?.['start']) {
32677
32876
  (0, process_utils_1.exitWithError)(exports.INVALID_PROJECT_ERROR);
32678
32877
  }
32679
- return parsedJson;
32878
+ return { packageJson: parsedJson, isPython: hasPython, isHybrid };
32680
32879
  }
32681
32880
  catch (_) {
32682
32881
  (0, process_utils_1.exitWithError)(exports.INVALID_PROJECT_ERROR);
@@ -37122,7 +37321,7 @@ terminalLink.stderr.isSupported = supports_hyperlinks.stderr;
37122
37321
  (module) {
37123
37322
 
37124
37323
  "use strict";
37125
- module.exports = /*#__PURE__*/JSON.parse('{"name":"dotenv","version":"16.6.1","description":"Loads environment variables from .env file","main":"lib/main.js","types":"lib/main.d.ts","exports":{".":{"types":"./lib/main.d.ts","require":"./lib/main.js","default":"./lib/main.js"},"./config":"./config.js","./config.js":"./config.js","./lib/env-options":"./lib/env-options.js","./lib/env-options.js":"./lib/env-options.js","./lib/cli-options":"./lib/cli-options.js","./lib/cli-options.js":"./lib/cli-options.js","./package.json":"./package.json"},"scripts":{"dts-check":"tsc --project tests/types/tsconfig.json","lint":"standard","pretest":"npm run lint && npm run dts-check","test":"tap run --allow-empty-coverage --disable-coverage --timeout=60000","test:coverage":"tap run --show-full-coverage --timeout=60000 --coverage-report=text --coverage-report=lcov","prerelease":"npm test","release":"standard-version"},"repository":{"type":"git","url":"git://github.com/motdotla/dotenv.git"},"homepage":"https://github.com/motdotla/dotenv#readme","funding":"https://dotenvx.com","keywords":["dotenv","env",".env","environment","variables","config","settings"],"readmeFilename":"README.md","license":"BSD-2-Clause","devDependencies":{"@types/node":"^18.11.3","decache":"^4.6.2","sinon":"^14.0.1","standard":"^17.0.0","standard-version":"^9.5.0","tap":"^19.2.0","typescript":"^4.8.4"},"engines":{"node":">=12"},"browser":{"fs":false}}');
37324
+ module.exports = /*#__PURE__*/JSON.parse('{"name":"dotenv","version":"17.3.1","description":"Loads environment variables from .env file","main":"lib/main.js","types":"lib/main.d.ts","exports":{".":{"types":"./lib/main.d.ts","require":"./lib/main.js","default":"./lib/main.js"},"./config":"./config.js","./config.js":"./config.js","./lib/env-options":"./lib/env-options.js","./lib/env-options.js":"./lib/env-options.js","./lib/cli-options":"./lib/cli-options.js","./lib/cli-options.js":"./lib/cli-options.js","./package.json":"./package.json"},"scripts":{"dts-check":"tsc --project tests/types/tsconfig.json","lint":"standard","pretest":"npm run lint && npm run dts-check","test":"tap run tests/**/*.js --allow-empty-coverage --disable-coverage --timeout=60000","test:coverage":"tap run tests/**/*.js --show-full-coverage --timeout=60000 --coverage-report=text --coverage-report=lcov","prerelease":"npm test","release":"standard-version"},"repository":{"type":"git","url":"git://github.com/motdotla/dotenv.git"},"homepage":"https://github.com/motdotla/dotenv#readme","funding":"https://dotenvx.com","keywords":["dotenv","env",".env","environment","variables","config","settings"],"readmeFilename":"README.md","license":"BSD-2-Clause","devDependencies":{"@types/node":"^18.11.3","decache":"^4.6.2","sinon":"^14.0.1","standard":"^17.0.0","standard-version":"^9.5.0","tap":"^19.2.0","typescript":"^4.8.4"},"engines":{"node":">=12"},"browser":{"fs":false}}');
37126
37325
 
37127
37326
  /***/ },
37128
37327
 
@@ -37138,7 +37337,7 @@ module.exports = /*#__PURE__*/JSON.parse('{"name":"seek-bzip","version":"1.0.6",
37138
37337
  (module) {
37139
37338
 
37140
37339
  "use strict";
37141
- module.exports = /*#__PURE__*/JSON.parse('{"name":"@squidcloud/cli","version":"1.0.450","description":"The Squid CLI","main":"dist/index.js","scripts":{"start":"node dist/index.js","start-ts":"ts-node -r tsconfig-paths/register src/index.ts","prebuild":"rimraf dist","build":"webpack --mode=production","build:dev":"webpack --mode=development","lint":"eslint","link":"npm run build && chmod 755 dist/index.js && npm link","watch":"webpack --watch","deploy":"npm run build && npm pack --silent | xargs -I {} mv {} package.tgz && npm install -g package.tgz && rm -rf package.tgz","publish:public":"npm run build && npm publish --access public"},"files":["dist/**/*"],"bin":{"squid":"dist/index.js"},"keywords":[],"author":"","license":"ISC","engines":{"node":">=18.0.0"},"dependencies":{"@squidcloud/local-backend":"^1.0.450","adm-zip":"^0.5.16","copy-webpack-plugin":"^12.0.2","decompress":"^4.2.1","logpipes":"^1.11.0","nodemon":"^3.1.9","terser-webpack-plugin":"^5.3.10","ts-loader":"^9.5.1","ts-node":"^10.9.2","tsconfig-paths":"^4.2.0","tsconfig-paths-webpack-plugin":"^4.1.0","webpack":"^5.101.3","zip-webpack-plugin":"^4.0.1"},"devDependencies":{"@types/adm-zip":"^0.5.7","@types/decompress":"^4.2.7","@types/node":"^20.19.9","terminal-link":"^3.0.0"}}');
37340
+ module.exports = /*#__PURE__*/JSON.parse('{"name":"@squidcloud/cli","version":"1.0.451","description":"The Squid CLI","main":"dist/index.js","scripts":{"start":"node dist/index.js","start-ts":"ts-node -r tsconfig-paths/register src/index.ts","prebuild":"rimraf dist","build":"webpack --mode=production","build:dev":"webpack --mode=development","lint":"eslint","link":"npm run build && chmod 755 dist/index.js && npm link","watch":"webpack --watch","deploy":"npm run build && npm pack --silent | xargs -I {} mv {} package.tgz && npm install -g package.tgz && rm -rf package.tgz","publish:public":"npm run build && npm publish --access public"},"files":["dist/**/*"],"bin":{"squid":"dist/index.js"},"keywords":[],"author":"","license":"ISC","engines":{"node":">=18.0.0"},"dependencies":{"@squidcloud/local-backend":"^1.0.451","adm-zip":"^0.5.16","copy-webpack-plugin":"^12.0.2","decompress":"^4.2.1","logpipes":"^1.11.0","nodemon":"^3.1.9","terser-webpack-plugin":"^5.3.10","ts-loader":"^9.5.1","ts-node":"^10.9.2","tsconfig-paths":"^4.2.0","tsconfig-paths-webpack-plugin":"^4.1.0","webpack":"^5.101.3","zip-webpack-plugin":"^4.0.1"},"devDependencies":{"@types/adm-zip":"^0.5.7","@types/decompress":"^4.2.7","@types/node":"^20.19.9","terminal-link":"^3.0.0"}}');
37142
37341
 
37143
37342
  /***/ }
37144
37343
 
@@ -393,8 +393,8 @@ export class MyService extends SquidService {
393
393
  Handle client connection and disconnection events.
394
394
 
395
395
  ```typescript
396
- import { SquidService, clientConnectionStateHandler, ClientConnectionState } from '@squidcloud/backend';
397
- import { ClientId } from '@squidcloud/client';
396
+ import { SquidService, clientConnectionStateHandler } from '@squidcloud/backend';
397
+ import { ClientConnectionState, ClientId } from '@squidcloud/client';
398
398
 
399
399
  export class MyService extends SquidService {
400
400
  @clientConnectionStateHandler()
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@squidcloud/cli",
3
- "version": "1.0.450",
3
+ "version": "1.0.451",
4
4
  "description": "The Squid CLI",
5
5
  "main": "dist/index.js",
6
6
  "scripts": {
@@ -28,7 +28,7 @@
28
28
  "node": ">=18.0.0"
29
29
  },
30
30
  "dependencies": {
31
- "@squidcloud/local-backend": "^1.0.450",
31
+ "@squidcloud/local-backend": "^1.0.451",
32
32
  "adm-zip": "^0.5.16",
33
33
  "copy-webpack-plugin": "^12.0.2",
34
34
  "decompress": "^4.2.1",