@ts-cloud/core 0.7.59 → 0.7.60

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.d.ts CHANGED
@@ -2,6 +2,7 @@
2
2
  * ts-cloud Core - CloudFormation Generator Engine
3
3
  */
4
4
  export * from './types';
5
+ export * from './state-dir';
5
6
  export * from './deployment-mode';
6
7
  export * from './app-database';
7
8
  export * from './drivers';
package/dist/index.js CHANGED
@@ -1017,6 +1017,34 @@ var RealtimePresets = {
1017
1017
  }
1018
1018
  }
1019
1019
  };
1020
+ // src/state-dir.ts
1021
+ import { isAbsolute, join, relative } from "node:path";
1022
+ var DEFAULT_STATE_DIR = ".ts-cloud";
1023
+ var STATE_DIR_ENV_VAR = "TS_CLOUD_STATE_DIR";
1024
+ var configuredStateDir = null;
1025
+ function setStateDir(dir) {
1026
+ const trimmed = dir?.trim();
1027
+ configuredStateDir = trimmed || null;
1028
+ }
1029
+ function stateDir() {
1030
+ const fromEnv = process.env[STATE_DIR_ENV_VAR]?.trim();
1031
+ return fromEnv || configuredStateDir || DEFAULT_STATE_DIR;
1032
+ }
1033
+ function statePath(...segments) {
1034
+ return join(stateDir(), ...segments);
1035
+ }
1036
+ function resolveStatePath(cwd, ...segments) {
1037
+ const dir = stateDir();
1038
+ return isAbsolute(dir) ? join(dir, ...segments) : join(cwd, dir, ...segments);
1039
+ }
1040
+ function isStatePath(path, root = process.cwd()) {
1041
+ const inside = (parent, child) => {
1042
+ const rel = relative(parent, child);
1043
+ return rel === "" || !rel.startsWith("..") && !isAbsolute(rel);
1044
+ };
1045
+ const absolute = isAbsolute(path) ? path : join(root, path);
1046
+ return inside(resolveStatePath(root), absolute);
1047
+ }
1020
1048
  // src/deployment-mode.ts
1021
1049
  function detectDeploymentTargets(config) {
1022
1050
  const server = config.infrastructure?.compute != null;
@@ -1985,7 +2013,7 @@ function formatDiff(diff) {
1985
2013
  }
1986
2014
  // src/modules/storage.ts
1987
2015
  import { existsSync, readdirSync } from "node:fs";
1988
- import { join } from "node:path";
2016
+ import { join as join2 } from "node:path";
1989
2017
  class Storage {
1990
2018
  static createBucket(options) {
1991
2019
  const {
@@ -2544,11 +2572,11 @@ class Storage {
2544
2572
  static docsExist(options = {}) {
2545
2573
  const { projectRoot = process.cwd(), docsPaths = ["docs", "documentation", "doc"] } = options;
2546
2574
  for (const docsPath of docsPaths) {
2547
- const fullPath = join(projectRoot, docsPath);
2575
+ const fullPath = join2(projectRoot, docsPath);
2548
2576
  if (existsSync(fullPath)) {
2549
2577
  const distPaths = ["dist", "build", ".bunpress/dist", "_site", "out", "public"];
2550
2578
  for (const distPath of distPaths) {
2551
- const fullDistPath = join(fullPath, distPath);
2579
+ const fullDistPath = join2(fullPath, distPath);
2552
2580
  if (existsSync(fullDistPath)) {
2553
2581
  try {
2554
2582
  const files = readdirSync(fullDistPath);
@@ -2663,16 +2691,16 @@ class Storage {
2663
2691
  const privatePath = paths.private || "private";
2664
2692
  return {
2665
2693
  web: {
2666
- exists: existsSync(join(projectRoot, webPath)),
2667
- path: join(projectRoot, webPath)
2694
+ exists: existsSync(join2(projectRoot, webPath)),
2695
+ path: join2(projectRoot, webPath)
2668
2696
  },
2669
2697
  docs: {
2670
- exists: existsSync(join(projectRoot, docsPath)),
2671
- path: join(projectRoot, docsPath)
2698
+ exists: existsSync(join2(projectRoot, docsPath)),
2699
+ path: join2(projectRoot, docsPath)
2672
2700
  },
2673
2701
  private: {
2674
- exists: existsSync(join(projectRoot, privatePath)),
2675
- path: join(projectRoot, privatePath)
2702
+ exists: existsSync(join2(projectRoot, privatePath)),
2703
+ path: join2(projectRoot, privatePath)
2676
2704
  }
2677
2705
  };
2678
2706
  }
@@ -14840,7 +14868,7 @@ class Auth {
14840
14868
  // src/modules/deployment.ts
14841
14869
  import { createHash } from "node:crypto";
14842
14870
  import { copyFileSync, existsSync as existsSync2, readdirSync as readdirSync2, readFileSync, statSync, writeFileSync } from "node:fs";
14843
- import { basename, dirname, extname, join as join2 } from "node:path";
14871
+ import { basename, dirname, extname, join as join3 } from "node:path";
14844
14872
  class Deployment {
14845
14873
  static createApplication(options) {
14846
14874
  const { slug, environment, applicationName, computePlatform } = options;
@@ -15202,7 +15230,7 @@ class AssetHasher {
15202
15230
  if (dir === ".") {
15203
15231
  return `${name}.${hash}${ext}`;
15204
15232
  }
15205
- return join2(dir, `${name}.${hash}${ext}`);
15233
+ return join3(dir, `${name}.${hash}${ext}`);
15206
15234
  }
15207
15235
  static collectFiles(directory, relativeTo) {
15208
15236
  const files = [];
@@ -15212,7 +15240,7 @@ class AssetHasher {
15212
15240
  }
15213
15241
  const entries = readdirSync2(directory, { withFileTypes: true });
15214
15242
  for (const entry of entries) {
15215
- const fullPath = join2(directory, entry.name);
15243
+ const fullPath = join3(directory, entry.name);
15216
15244
  if (entry.isDirectory()) {
15217
15245
  files.push(...AssetHasher.collectFiles(fullPath, baseDir));
15218
15246
  } else if (entry.isFile()) {
@@ -15242,7 +15270,7 @@ class AssetHasher {
15242
15270
  assets.push(asset);
15243
15271
  hashMap[relativePath] = hashedRelativePath;
15244
15272
  if (outputDir) {
15245
- const destPath = join2(outputDir, hashedRelativePath);
15273
+ const destPath = join3(outputDir, hashedRelativePath);
15246
15274
  const destDir = dirname(destPath);
15247
15275
  if (!existsSync2(destDir)) {
15248
15276
  const { mkdirSync } = __require("node:fs");
@@ -15260,7 +15288,7 @@ class AssetHasher {
15260
15288
  hashMap
15261
15289
  };
15262
15290
  if (outputDir) {
15263
- const manifestPath = join2(outputDir, "asset-manifest.json");
15291
+ const manifestPath = join3(outputDir, "asset-manifest.json");
15264
15292
  writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));
15265
15293
  }
15266
15294
  return manifest;
@@ -15355,7 +15383,7 @@ class AssetHasher {
15355
15383
  cc = cacheControl.hashed || "public, max-age=31536000, immutable";
15356
15384
  }
15357
15385
  return {
15358
- localPath: join2(sourceDir, asset.originalPath),
15386
+ localPath: join3(sourceDir, asset.originalPath),
15359
15387
  s3Key: keyPrefix ? `${keyPrefix}/${asset.hashedPath}` : asset.hashedPath,
15360
15388
  contentType: asset.contentType,
15361
15389
  cacheControl: cc,
@@ -22303,7 +22331,7 @@ function createDashboardSite(options) {
22303
22331
  };
22304
22332
  }
22305
22333
  // src/presets/management-dashboard.ts
22306
- var DASHBOARD_STATE_DIR = ".ts-cloud";
22334
+ var DASHBOARD_STATE_DIR = DEFAULT_STATE_DIR;
22307
22335
  var DASHBOARD_ENTRY = "./node_modules/@stacksjs/ts-cloud/dist/bin/cli.js";
22308
22336
  function apexOf(domain) {
22309
22337
  const parts = domain.split(".").filter(Boolean);
@@ -22492,7 +22520,7 @@ init_signature();
22492
22520
  // src/aws/credentials.ts
22493
22521
  import { existsSync as existsSync3, readFileSync as readFileSync2 } from "node:fs";
22494
22522
  import { homedir } from "node:os";
22495
- import { join as join3 } from "node:path";
22523
+ import { join as join4 } from "node:path";
22496
22524
  function fromEnvironment() {
22497
22525
  const accessKeyId = process.env.AWS_ACCESS_KEY_ID;
22498
22526
  const secretAccessKey = process.env.AWS_SECRET_ACCESS_KEY;
@@ -22507,7 +22535,7 @@ function fromEnvironment() {
22507
22535
  }
22508
22536
  function fromSharedCredentials(options) {
22509
22537
  const profile = options?.profile || process.env.AWS_PROFILE || "default";
22510
- const credentialsPath = options?.credentialsFile || join3(homedir(), ".aws", "credentials");
22538
+ const credentialsPath = options?.credentialsFile || join4(homedir(), ".aws", "credentials");
22511
22539
  if (!existsSync3(credentialsPath)) {
22512
22540
  return null;
22513
22541
  }
@@ -22744,7 +22772,7 @@ function resolveRegion(profile) {
22744
22772
  if (envRegion) {
22745
22773
  return envRegion;
22746
22774
  }
22747
- const configPath = join3(homedir(), ".aws", "config");
22775
+ const configPath = join4(homedir(), ".aws", "config");
22748
22776
  if (existsSync3(configPath)) {
22749
22777
  try {
22750
22778
  const content = readFileSync2(configPath, "utf-8");
@@ -24635,20 +24663,21 @@ function suggestQuotaIncrease(quotas) {
24635
24663
  // src/utils/cache.ts
24636
24664
  import { createHash as createHash2 } from "node:crypto";
24637
24665
  import { existsSync as existsSync4, mkdirSync, readdirSync as readdirSync3, readFileSync as readFileSync3, unlinkSync, writeFileSync as writeFileSync2 } from "node:fs";
24638
- import { join as join4 } from "node:path";
24666
+ import { join as join5 } from "node:path";
24639
24667
  class FileCache {
24640
24668
  cacheDir;
24641
24669
  ttl;
24642
24670
  constructor(cacheDir, options = {}) {
24643
24671
  this.cacheDir = cacheDir;
24644
24672
  this.ttl = options.ttl || 24 * 60 * 60 * 1000;
24645
- if (!existsSync4(cacheDir)) {
24646
- mkdirSync(cacheDir, { recursive: true });
24647
- }
24673
+ }
24674
+ ensureCacheDir() {
24675
+ if (!existsSync4(this.cacheDir))
24676
+ mkdirSync(this.cacheDir, { recursive: true });
24648
24677
  }
24649
24678
  getCachePath(key) {
24650
24679
  const hash2 = createHash2("sha256").update(key).digest("hex");
24651
- return join4(this.cacheDir, `${hash2}.json`);
24680
+ return join5(this.cacheDir, `${hash2}.json`);
24652
24681
  }
24653
24682
  get(key) {
24654
24683
  const cachePath = this.getCachePath(key);
@@ -24669,6 +24698,7 @@ class FileCache {
24669
24698
  }
24670
24699
  }
24671
24700
  set(key, value, hash2) {
24701
+ this.ensureCacheDir();
24672
24702
  const cachePath = this.getCachePath(key);
24673
24703
  const entry = {
24674
24704
  value,
@@ -24681,16 +24711,20 @@ class FileCache {
24681
24711
  return this.get(key) !== undefined;
24682
24712
  }
24683
24713
  clear() {
24714
+ if (!existsSync4(this.cacheDir))
24715
+ return;
24684
24716
  const files = readdirSync3(this.cacheDir);
24685
24717
  for (const file of files) {
24686
- unlinkSync(join4(this.cacheDir, file));
24718
+ unlinkSync(join5(this.cacheDir, file));
24687
24719
  }
24688
24720
  }
24689
24721
  prune() {
24722
+ if (!existsSync4(this.cacheDir))
24723
+ return;
24690
24724
  const files = readdirSync3(this.cacheDir);
24691
24725
  const now = Date.now();
24692
24726
  for (const file of files) {
24693
- const filePath = join4(this.cacheDir, file);
24727
+ const filePath = join5(this.cacheDir, file);
24694
24728
  try {
24695
24729
  const data = readFileSync3(filePath, "utf-8");
24696
24730
  const entry = JSON.parse(data);
@@ -24706,7 +24740,7 @@ class FileCache {
24706
24740
 
24707
24741
  class TemplateCache {
24708
24742
  cache;
24709
- constructor(cacheDir = ".ts-cloud/cache/templates") {
24743
+ constructor(cacheDir = statePath("cache", "templates")) {
24710
24744
  this.cache = new FileCache(cacheDir, {
24711
24745
  ttl: 24 * 60 * 60 * 1000
24712
24746
  });
@@ -24737,11 +24771,16 @@ class TemplateCache {
24737
24771
  this.cache.prune();
24738
24772
  }
24739
24773
  }
24740
- var templateCache = new TemplateCache;
24774
+ var sharedTemplateCache = null;
24775
+ function templateCache() {
24776
+ if (!sharedTemplateCache)
24777
+ sharedTemplateCache = new TemplateCache;
24778
+ return sharedTemplateCache;
24779
+ }
24741
24780
  // src/utils/hash.ts
24742
24781
  import { createHash as createHash3 } from "node:crypto";
24743
24782
  import { createReadStream, readdirSync as readdirSync4, statSync as statSync2 } from "node:fs";
24744
- import { join as join5, relative } from "node:path";
24783
+ import { join as join6, relative as relative2 } from "node:path";
24745
24784
  async function hashFile(filePath, options = {}) {
24746
24785
  const algorithm = options.algorithm || "sha256";
24747
24786
  const chunkSize = options.chunkSize || 64 * 1024;
@@ -24760,13 +24799,13 @@ function hashBuffer(buffer, algorithm = "sha256") {
24760
24799
  return createHash3(algorithm).update(buffer).digest("hex");
24761
24800
  }
24762
24801
  async function hashDirectory(dirPath, options = {}) {
24763
- const ignorePatterns = options.ignorePatterns || ["node_modules", ".git", "dist", "build", ".ts-cloud"];
24802
+ const ignorePatterns = options.ignorePatterns || ["node_modules", ".git", "dist", "build", stateDir()];
24764
24803
  const files = [];
24765
24804
  async function walk(dir) {
24766
24805
  const entries = readdirSync4(dir, { withFileTypes: true });
24767
24806
  for (const entry of entries) {
24768
- const fullPath = join5(dir, entry.name);
24769
- const relativePath = relative(dirPath, fullPath);
24807
+ const fullPath = join6(dir, entry.name);
24808
+ const relativePath = relative2(dirPath, fullPath);
24770
24809
  if (ignorePatterns.some((pattern) => relativePath.includes(pattern))) {
24771
24810
  continue;
24772
24811
  }
@@ -45332,10 +45371,10 @@ import { execSync } from "node:child_process";
45332
45371
  import { createHash as createHash4 } from "node:crypto";
45333
45372
  import { cpSync, mkdtempSync, rmSync, writeFileSync as writeFileSync3 } from "node:fs";
45334
45373
  import { tmpdir } from "node:os";
45335
- import { dirname as dirname2, isAbsolute, join as join6, resolve } from "node:path";
45374
+ import { dirname as dirname2, isAbsolute as isAbsolute2, join as join7, resolve } from "node:path";
45336
45375
  import { fileURLToPath } from "node:url";
45337
45376
  function adapterSourcePath() {
45338
- return join6(dirname2(fileURLToPath(import.meta.url)), "runtime", "adapter.ts");
45377
+ return join7(dirname2(fileURLToPath(import.meta.url)), "runtime", "adapter.ts");
45339
45378
  }
45340
45379
  function runBuildHooks(hooks, cwd, onStep) {
45341
45380
  for (const hook of hooks ?? []) {
@@ -45357,11 +45396,11 @@ async function packageServerlessApp(opts) {
45357
45396
  const entry = app.entry;
45358
45397
  if (!entry)
45359
45398
  throw new Error("serverless app: `entry` is required to package a Node/Bun application");
45360
- const entryPath = isAbsolute(entry) ? entry : join6(projectRoot, entry);
45361
- const stage = mkdtempSync(join6(tmpdir(), "tscloud-pkg-"));
45399
+ const entryPath = isAbsolute2(entry) ? entry : join7(projectRoot, entry);
45400
+ const stage = mkdtempSync(join7(tmpdir(), "tscloud-pkg-"));
45362
45401
  try {
45363
- cpSync(adapterSourcePath(), join6(stage, "adapter.ts"));
45364
- const bootstrapPath = join6(stage, "bootstrap.ts");
45402
+ cpSync(adapterSourcePath(), join7(stage, "adapter.ts"));
45403
+ const bootstrapPath = join7(stage, "bootstrap.ts");
45365
45404
  writeFileSync3(bootstrapPath, generateBootstrap({ entryImport: entryPath, adapterImport: "./adapter" }));
45366
45405
  opts.onStep?.("bundling application");
45367
45406
  const result = await Bun.build({
@@ -46353,33 +46392,33 @@ CMD [ "index.http" ]
46353
46392
  import { execFileSync } from "node:child_process";
46354
46393
  import { mkdtempSync as mkdtempSync2, readFileSync as readFileSync4, rmSync as rmSync2 } from "node:fs";
46355
46394
  import { tmpdir as tmpdir2 } from "node:os";
46356
- import { dirname as dirname3, join as join7 } from "node:path";
46395
+ import { dirname as dirname3, join as join8 } from "node:path";
46357
46396
  import { fileURLToPath as fileURLToPath2 } from "node:url";
46358
46397
  function assetsDir() {
46359
46398
  return dirname3(fileURLToPath2(import.meta.url));
46360
46399
  }
46361
46400
  function sharedRuntimeLoop() {
46362
- return readFileSync4(join7(assetsDir(), "runtime.mjs"), "utf-8");
46401
+ return readFileSync4(join8(assetsDir(), "runtime.mjs"), "utf-8");
46363
46402
  }
46364
46403
  function bootstrap(kind) {
46365
- return readFileSync4(join7(assetsDir(), `${kind}-bootstrap`), "utf-8");
46404
+ return readFileSync4(join8(assetsDir(), `${kind}-bootstrap`), "utf-8");
46366
46405
  }
46367
46406
  function buildNodeRuntimeLayerZip(options) {
46368
46407
  const architecture = options.architecture ?? "x86_64";
46369
46408
  const nodeArch = architecture === "arm64" ? "arm64" : "x64";
46370
46409
  const version = options.version.replace(/^v/, "");
46371
46410
  const step = options.onStep ?? (() => {});
46372
- const stage = mkdtempSync2(join7(tmpdir2(), "tscloud-node-layer-"));
46411
+ const stage = mkdtempSync2(join8(tmpdir2(), "tscloud-node-layer-"));
46373
46412
  try {
46374
46413
  const exact = version.includes(".") ? version : resolveLatestNode(version);
46375
46414
  const dir = `node-v${exact}-linux-${nodeArch}`;
46376
46415
  const url = `https://nodejs.org/dist/v${exact}/${dir}.tar.xz`;
46377
46416
  step(`downloading Node ${exact} (${architecture})`);
46378
- const tar = join7(stage, "node.tar.xz");
46417
+ const tar = join8(stage, "node.tar.xz");
46379
46418
  fetchToFile(url, tar);
46380
46419
  step("extracting node binary");
46381
46420
  execFileSync("tar", ["-xf", tar, "-C", stage, `${dir}/bin/node`], { stdio: "inherit" });
46382
- const nodeBin = readFileSync4(join7(stage, dir, "bin", "node"));
46421
+ const nodeBin = readFileSync4(join8(stage, dir, "bin", "node"));
46383
46422
  step("packaging layer");
46384
46423
  const entries = [
46385
46424
  { name: "bootstrap", data: bootstrap("node"), mode: 493 },
@@ -46396,15 +46435,15 @@ function buildBunRuntimeLayerZip(options) {
46396
46435
  const bunArch = architecture === "arm64" ? "bun-linux-aarch64" : "bun-linux-x64";
46397
46436
  const version = options.version === "latest" ? "" : options.version.replace(/^v/, "");
46398
46437
  const step = options.onStep ?? (() => {});
46399
- const stage = mkdtempSync2(join7(tmpdir2(), "tscloud-bun-layer-"));
46438
+ const stage = mkdtempSync2(join8(tmpdir2(), "tscloud-bun-layer-"));
46400
46439
  try {
46401
46440
  const url = version ? `https://github.com/oven-sh/bun/releases/download/bun-v${version}/${bunArch}.zip` : `https://github.com/oven-sh/bun/releases/latest/download/${bunArch}.zip`;
46402
46441
  step(`downloading Bun ${version || "latest"} (${architecture})`);
46403
- const zipPath = join7(stage, "bun.zip");
46442
+ const zipPath = join8(stage, "bun.zip");
46404
46443
  fetchToFile(url, zipPath);
46405
46444
  step("extracting bun binary");
46406
46445
  execFileSync("unzip", ["-o", "-q", zipPath, "-d", stage], { stdio: "inherit" });
46407
- const bunBin = readFileSync4(join7(stage, bunArch, "bun"));
46446
+ const bunBin = readFileSync4(join8(stage, bunArch, "bun"));
46408
46447
  step("packaging layer");
46409
46448
  const entries = [
46410
46449
  { name: "bootstrap", data: bootstrap("bun"), mode: 493 },
@@ -46589,14 +46628,14 @@ clear_env = no
46589
46628
  }
46590
46629
  // src/serverless-php/runtime-assets.ts
46591
46630
  import { readFileSync as readFileSync5 } from "node:fs";
46592
- import { dirname as dirname4, join as join8 } from "node:path";
46631
+ import { dirname as dirname4, join as join9 } from "node:path";
46593
46632
  import { fileURLToPath as fileURLToPath3 } from "node:url";
46594
46633
  function assetsDir2() {
46595
- return join8(dirname4(fileURLToPath3(import.meta.url)), "runtime-assets");
46634
+ return join9(dirname4(fileURLToPath3(import.meta.url)), "runtime-assets");
46596
46635
  }
46597
46636
  function phpRuntimeLayerAssets() {
46598
46637
  const dir = assetsDir2();
46599
- const read = (f) => readFileSync5(join8(dir, f), "utf-8");
46638
+ const read = (f) => readFileSync5(join9(dir, f), "utf-8");
46600
46639
  return [
46601
46640
  { path: "bootstrap", contents: read("bootstrap"), mode: 493 },
46602
46641
  { path: "tscloud/runtime.php", contents: read("runtime.php"), mode: 420 },
@@ -46635,10 +46674,10 @@ var LARAVEL_SERVERLESS_BUILD_STEPS = [
46635
46674
  import { execFileSync as execFileSync2 } from "node:child_process";
46636
46675
  import { existsSync as existsSync5, mkdirSync as mkdirSync2, mkdtempSync as mkdtempSync3, readdirSync as readdirSync5, readFileSync as readFileSync6, rmSync as rmSync3, statSync as statSync3, writeFileSync as writeFileSync4 } from "node:fs";
46637
46676
  import { tmpdir as tmpdir3 } from "node:os";
46638
- import { join as join9, relative as relative2 } from "node:path";
46677
+ import { join as join10, relative as relative3 } from "node:path";
46639
46678
  function* walk(dir) {
46640
46679
  for (const entry of readdirSync5(dir)) {
46641
- const full = join9(dir, entry);
46680
+ const full = join10(dir, entry);
46642
46681
  if (statSync3(full).isDirectory())
46643
46682
  yield* walk(full);
46644
46683
  else
@@ -46649,26 +46688,26 @@ function buildPhpRuntimeLayerZip(options = {}) {
46649
46688
  const architecture = options.architecture ?? "x86_64";
46650
46689
  const platform = options.platform ?? (architecture === "arm64" ? "linux/arm64" : "linux/amd64");
46651
46690
  const step = options.onStep ?? (() => {});
46652
- const stage = mkdtempSync3(join9(tmpdir3(), "tscloud-php-layer-"));
46691
+ const stage = mkdtempSync3(join10(tmpdir3(), "tscloud-php-layer-"));
46653
46692
  const imageTag = "tscloud-php-layer:build";
46654
46693
  try {
46655
- writeFileSync4(join9(stage, "Dockerfile"), generatePhpLayerDockerfile(options));
46694
+ writeFileSync4(join10(stage, "Dockerfile"), generatePhpLayerDockerfile(options));
46656
46695
  step("Building PHP runtime image (docker)");
46657
46696
  execFileSync2("docker", ["build", "--platform", platform, "-t", imageTag, stage], { stdio: "inherit" });
46658
46697
  step("Extracting /opt/php from image");
46659
46698
  const cid = execFileSync2("docker", ["create", "--platform", platform, imageTag], { encoding: "utf-8" }).trim();
46660
- const optDir = join9(stage, "opt");
46699
+ const optDir = join10(stage, "opt");
46661
46700
  mkdirSync2(optDir, { recursive: true });
46662
46701
  try {
46663
- execFileSync2("docker", ["cp", `${cid}:/opt/php`, join9(optDir, "php")], { stdio: "inherit" });
46702
+ execFileSync2("docker", ["cp", `${cid}:/opt/php`, join10(optDir, "php")], { stdio: "inherit" });
46664
46703
  } finally {
46665
46704
  execFileSync2("docker", ["rm", cid], { stdio: "ignore" });
46666
46705
  }
46667
- if (!existsSync5(join9(optDir, "php", "bin", "php")))
46706
+ if (!existsSync5(join10(optDir, "php", "bin", "php")))
46668
46707
  throw new Error("layer build produced no /opt/php/bin/php");
46669
46708
  const entries = [];
46670
46709
  for (const file of walk(optDir)) {
46671
- const rel = relative2(optDir, file).replace(/\\/g, "/");
46710
+ const rel = relative3(optDir, file).replace(/\\/g, "/");
46672
46711
  const mode = statSync3(file).mode & 73 ? 493 : 420;
46673
46712
  entries.push({ name: rel, data: readFileSync6(file), mode });
46674
46713
  }
@@ -46689,7 +46728,7 @@ function buildPhpRuntimeLayerZip(options = {}) {
46689
46728
  import { execSync as execSync2 } from "node:child_process";
46690
46729
  import { createHash as createHash5 } from "node:crypto";
46691
46730
  import { readdirSync as readdirSync6, readFileSync as readFileSync7, statSync as statSync4 } from "node:fs";
46692
- import { join as join10, relative as relative3, resolve as resolve2 } from "node:path";
46731
+ import { join as join11, relative as relative4, resolve as resolve2 } from "node:path";
46693
46732
  var PHP_DEFAULT_EXCLUDES = [
46694
46733
  ".git",
46695
46734
  ".github",
@@ -46710,8 +46749,8 @@ function isExcluded(rel, excludes) {
46710
46749
  }
46711
46750
  function* walk2(dir, root, excludes) {
46712
46751
  for (const entry of readdirSync6(dir)) {
46713
- const full = join10(dir, entry);
46714
- const rel = relative3(root, full).replace(/\\/g, "/");
46752
+ const full = join11(dir, entry);
46753
+ const rel = relative4(root, full).replace(/\\/g, "/");
46715
46754
  if (isExcluded(rel, excludes))
46716
46755
  continue;
46717
46756
  if (statSync4(full).isDirectory())
@@ -46732,10 +46771,10 @@ function runPhpBuildHooks(opts) {
46732
46771
  }
46733
46772
  function collectPhpAppEntries(projectRoot, exclude = []) {
46734
46773
  const root = resolve2(projectRoot);
46735
- const excludes = [...PHP_DEFAULT_EXCLUDES, ...exclude];
46774
+ const excludes = [...PHP_DEFAULT_EXCLUDES, stateDir(), ...exclude];
46736
46775
  const entries = [];
46737
46776
  for (const file of walk2(root, root, excludes)) {
46738
- const rel = relative3(root, file).replace(/\\/g, "/");
46777
+ const rel = relative4(root, file).replace(/\\/g, "/");
46739
46778
  const executable = (statSync4(file).mode & 73) !== 0;
46740
46779
  entries.push({ name: rel, data: readFileSync7(file), mode: executable ? 493 : 420 });
46741
46780
  }
@@ -47226,11 +47265,14 @@ export {
47226
47265
  suggestCommand,
47227
47266
  storageAdvancedManager,
47228
47267
  staticSiteManager,
47268
+ statePath,
47269
+ stateDir,
47229
47270
  stackDependencyManager,
47230
47271
  signRequestAsync,
47231
47272
  signRequest,
47232
47273
  sharedRuntimeLoop,
47233
47274
  sha256,
47275
+ setStateDir,
47234
47276
  serviceMeshManager,
47235
47277
  sequence,
47236
47278
  senderReputationManager,
@@ -47247,6 +47289,7 @@ export {
47247
47289
  responseToResult,
47248
47290
  resourceManagementManager,
47249
47291
  resolveStorageBucketName,
47292
+ resolveStatePath,
47250
47293
  resolveSiteStackName,
47251
47294
  resolveSiteResourceName,
47252
47295
  resolveSiteBucketName,
@@ -47309,6 +47352,7 @@ export {
47309
47352
  lambdaConcurrencyManager,
47310
47353
  isWebCryptoAvailable,
47311
47354
  isValidRegion,
47355
+ isStatePath,
47312
47356
  isNodeCryptoAvailable,
47313
47357
  isManagementDashboardSiteName,
47314
47358
  isLocalDevelopment,
@@ -47492,6 +47536,7 @@ export {
47492
47536
  SecretsManager,
47493
47537
  Secrets,
47494
47538
  Search,
47539
+ STATE_DIR_ENV_VAR,
47495
47540
  SMS,
47496
47541
  S3Error,
47497
47542
  S3Client,
@@ -47574,6 +47619,7 @@ export {
47574
47619
  DNSSECManager,
47575
47620
  DNS,
47576
47621
  DLQMonitoringManager,
47622
+ DEFAULT_STATE_DIR,
47577
47623
  DEFAULT_SERVICE_LIMITS,
47578
47624
  DASHBOARD_STATE_DIR,
47579
47625
  DASHBOARD_PORT_SPAN,
@@ -57,8 +57,16 @@ export interface ManagementDashboardOptions {
57
57
  */
58
58
  version?: string;
59
59
  }
60
- /** Where the live dashboard keeps its users, session key and cache. */
61
- export declare const DASHBOARD_STATE_DIR = ".ts-cloud";
60
+ /**
61
+ * Where the live dashboard keeps its users, session key and cache ON THE BOX,
62
+ * relative to its release directory.
63
+ *
64
+ * Deliberately the literal default rather than {@link stateDir}: that one is
65
+ * about the operator's machine (a Stacks checkout points it at `storage/cloud`),
66
+ * while this is the path the deploy carries across releases via `sharedPaths`.
67
+ * The two must agree with each other, not with the local checkout.
68
+ */
69
+ export declare const DASHBOARD_STATE_DIR: string;
62
70
  /**
63
71
  * The dashboard service's entry point inside its release dir. The CLI is
64
72
  * installed from npm by the release's `bun install`, so this path exists on the
@@ -0,0 +1,44 @@
1
+ /** The directory used when nothing configures one. */
2
+ export declare const DEFAULT_STATE_DIR = ".ts-cloud";
3
+ /** Environment variable that overrides both the config and the default. */
4
+ export declare const STATE_DIR_ENV_VAR = "TS_CLOUD_STATE_DIR";
5
+ /**
6
+ * Records the `stateDir` coming from `cloud.config.ts`.
7
+ *
8
+ * Called by the config loader. Passing a nullish or blank value clears it,
9
+ * which is what a config without `stateDir` should do — otherwise a stale value
10
+ * from a previously loaded config would leak into the next one (tests, and the
11
+ * dashboard server loading configs for several projects).
12
+ */
13
+ export declare function setStateDir(dir?: string | null): void;
14
+ /**
15
+ * The configured state directory, as written — relative or absolute.
16
+ */
17
+ export declare function stateDir(): string;
18
+ /**
19
+ * Joins segments onto the state directory without resolving it against a root.
20
+ *
21
+ * Use this for the exported "where does X live" helpers, whose value is a
22
+ * project-relative path that callers then resolve against their own `cwd`.
23
+ */
24
+ export declare function statePath(...segments: string[]): string;
25
+ /**
26
+ * Absolute path to a file or directory inside the state directory.
27
+ *
28
+ * An absolute {@link stateDir} wins over `cwd`, so a project can pin its state
29
+ * to a fixed location regardless of where a command is run from.
30
+ */
31
+ export declare function resolveStatePath(cwd: string, ...segments: string[]): string;
32
+ /**
33
+ * Whether `path` is the state directory or something inside it.
34
+ *
35
+ * Everything that walks a project to package, hash, or ship it has to skip the
36
+ * state directory: it holds the dashboard credentials and the session key, and
37
+ * a deploy artifact that carries them is a credential leak. A hardcoded
38
+ * `.ts-cloud` check stops being enough the moment the directory is configurable,
39
+ * so ask this instead of comparing names.
40
+ *
41
+ * `path` may be absolute or relative to `root` (the project root, defaulting to
42
+ * the working directory).
43
+ */
44
+ export declare function isStatePath(path: string, root?: string): boolean;
@@ -0,0 +1 @@
1
+ export {};
package/dist/types.d.ts CHANGED
@@ -155,6 +155,21 @@ export interface CloudConfig {
155
155
  * Tags applied to all resources
156
156
  */
157
157
  tags?: Record<string, string>;
158
+ /**
159
+ * Where ts-cloud keeps its machine-local state: dashboard credentials and
160
+ * session secret, the auth encryption key, the control-plane database, the
161
+ * staged dashboard release, cached templates, restore scratch space.
162
+ *
163
+ * Defaults to `.ts-cloud` in the project root. Point it at an existing home
164
+ * for runtime state to keep the root clean - a Stacks application sets
165
+ * `storage/cloud`. Relative paths resolve against the project root; absolute
166
+ * paths are used as-is. `TS_CLOUD_STATE_DIR` overrides this.
167
+ *
168
+ * Not to be confused with `storage/cloud/state/`, where the drivers record
169
+ * the provisioned box per stack - that one is meant to be committed so CI can
170
+ * find an existing server instead of provisioning a duplicate.
171
+ */
172
+ stateDir?: string;
158
173
  }
159
174
  export type CloudOptions = Partial<CloudConfig>;
160
175
  export interface ProjectConfig {
@@ -51,6 +51,15 @@ export declare class FileCache<T = any> {
51
51
  private cacheDir;
52
52
  private ttl;
53
53
  constructor(cacheDir: string, options?: CacheOptions);
54
+ /**
55
+ * Create the cache directory, on the first write rather than on construction.
56
+ *
57
+ * Constructing a cache must not touch the filesystem: these objects get built
58
+ * eagerly at import time, before anything has had a chance to configure where
59
+ * state lives, and a directory created then lands in the wrong place and
60
+ * sticks around empty.
61
+ */
62
+ private ensureCacheDir;
54
63
  /**
55
64
  * Get cache file path for key
56
65
  */
@@ -107,7 +116,4 @@ export declare class TemplateCache {
107
116
  */
108
117
  prune(): void;
109
118
  }
110
- /**
111
- * Global template cache instance
112
- */
113
- export declare const templateCache: TemplateCache;
119
+ export declare function templateCache(): TemplateCache;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@ts-cloud/core",
3
3
  "type": "module",
4
- "version": "0.7.59",
4
+ "version": "0.7.60",
5
5
  "description": "Core CloudFormation generation library for ts-cloud",
6
6
  "author": "Chris Breuer <chris@stacksjs.com>",
7
7
  "license": "MIT",
@@ -31,7 +31,7 @@
31
31
  "typecheck": "tsc --noEmit"
32
32
  },
33
33
  "dependencies": {
34
- "@ts-cloud/aws-types": "0.7.59"
34
+ "@ts-cloud/aws-types": "0.7.60"
35
35
  },
36
36
  "devDependencies": {
37
37
  "typescript": "^7.0.2"