@geekmidas/cli 1.5.0 → 1.6.0

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 (119) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/dist/{HostingerProvider-B9N-TKbp.mjs → HostingerProvider-402UdK89.mjs} +34 -1
  3. package/dist/HostingerProvider-402UdK89.mjs.map +1 -0
  4. package/dist/{HostingerProvider-DUV9-Tzg.cjs → HostingerProvider-BiXdHjiq.cjs} +34 -1
  5. package/dist/HostingerProvider-BiXdHjiq.cjs.map +1 -0
  6. package/dist/{Route53Provider-C8mS0zY6.mjs → Route53Provider-DbBo7Uz5.mjs} +53 -1
  7. package/dist/Route53Provider-DbBo7Uz5.mjs.map +1 -0
  8. package/dist/{Route53Provider-Bs7Arms9.cjs → Route53Provider-kfJ77LmL.cjs} +53 -1
  9. package/dist/Route53Provider-kfJ77LmL.cjs.map +1 -0
  10. package/dist/backup-provisioner-B5e-F6zX.cjs +164 -0
  11. package/dist/backup-provisioner-B5e-F6zX.cjs.map +1 -0
  12. package/dist/backup-provisioner-BIArpmTr.mjs +163 -0
  13. package/dist/backup-provisioner-BIArpmTr.mjs.map +1 -0
  14. package/dist/{config-ZQM1vBoz.cjs → config-6JHOwLCx.cjs} +30 -2
  15. package/dist/{config-ZQM1vBoz.cjs.map → config-6JHOwLCx.cjs.map} +1 -1
  16. package/dist/{config-DfCJ29PQ.mjs → config-DxASSNjr.mjs} +25 -3
  17. package/dist/{config-DfCJ29PQ.mjs.map → config-DxASSNjr.mjs.map} +1 -1
  18. package/dist/config.cjs +3 -2
  19. package/dist/config.d.cts +14 -2
  20. package/dist/config.d.cts.map +1 -1
  21. package/dist/config.d.mts +15 -3
  22. package/dist/config.d.mts.map +1 -1
  23. package/dist/config.mjs +3 -3
  24. package/dist/{dokploy-api-z0833e7r.mjs → dokploy-api-2ldYoN3i.mjs} +131 -1
  25. package/dist/dokploy-api-2ldYoN3i.mjs.map +1 -0
  26. package/dist/dokploy-api-C93pveuy.mjs +3 -0
  27. package/dist/dokploy-api-CbDh4o93.cjs +3 -0
  28. package/dist/{dokploy-api-CQvhV6Hd.cjs → dokploy-api-DLgvEQlr.cjs} +131 -1
  29. package/dist/dokploy-api-DLgvEQlr.cjs.map +1 -0
  30. package/dist/{index-C0SpUT9Y.d.mts → index-C-KxSGGK.d.mts} +133 -31
  31. package/dist/index-C-KxSGGK.d.mts.map +1 -0
  32. package/dist/{index-B58qjyBd.d.cts → index-Cyk2rTyj.d.cts} +132 -30
  33. package/dist/index-Cyk2rTyj.d.cts.map +1 -0
  34. package/dist/index.cjs +662 -152
  35. package/dist/index.cjs.map +1 -1
  36. package/dist/index.mjs +626 -116
  37. package/dist/index.mjs.map +1 -1
  38. package/dist/{openapi-BcSjLfWq.mjs → openapi-BYlyAbH3.mjs} +6 -5
  39. package/dist/openapi-BYlyAbH3.mjs.map +1 -0
  40. package/dist/{openapi-D6Hcfov0.cjs → openapi-CnvwSRDU.cjs} +6 -5
  41. package/dist/openapi-CnvwSRDU.cjs.map +1 -0
  42. package/dist/openapi.cjs +3 -3
  43. package/dist/openapi.d.cts +1 -0
  44. package/dist/openapi.d.cts.map +1 -1
  45. package/dist/openapi.d.mts +2 -1
  46. package/dist/openapi.d.mts.map +1 -1
  47. package/dist/openapi.mjs +3 -3
  48. package/dist/{types-B9UZ7fOG.d.mts → types-CZg5iUgD.d.mts} +1 -1
  49. package/dist/{types-B9UZ7fOG.d.mts.map → types-CZg5iUgD.d.mts.map} +1 -1
  50. package/dist/workspace/index.cjs +1 -1
  51. package/dist/workspace/index.d.cts +1 -1
  52. package/dist/workspace/index.d.mts +2 -2
  53. package/dist/workspace/index.mjs +1 -1
  54. package/dist/{workspace-BW2iU37P.mjs → workspace-9IQIjwkQ.mjs} +20 -4
  55. package/dist/workspace-9IQIjwkQ.mjs.map +1 -0
  56. package/dist/{workspace-2Do2YcGZ.cjs → workspace-D2ocAlpl.cjs} +20 -4
  57. package/dist/workspace-D2ocAlpl.cjs.map +1 -0
  58. package/examples/cron-example.ts +6 -6
  59. package/examples/function-example.ts +1 -1
  60. package/package.json +6 -3
  61. package/src/config.ts +44 -0
  62. package/src/deploy/__tests__/backup-provisioner.spec.ts +428 -0
  63. package/src/deploy/__tests__/createDnsProvider.spec.ts +23 -0
  64. package/src/deploy/__tests__/env-resolver.spec.ts +1 -1
  65. package/src/deploy/__tests__/undeploy.spec.ts +758 -0
  66. package/src/deploy/backup-provisioner.ts +316 -0
  67. package/src/deploy/dns/DnsProvider.ts +39 -1
  68. package/src/deploy/dns/HostingerProvider.ts +74 -0
  69. package/src/deploy/dns/Route53Provider.ts +81 -0
  70. package/src/deploy/dns/index.ts +25 -0
  71. package/src/deploy/dokploy-api.ts +237 -0
  72. package/src/deploy/index.ts +71 -13
  73. package/src/deploy/state.ts +171 -0
  74. package/src/deploy/undeploy.ts +407 -0
  75. package/src/dev/__tests__/index.spec.ts +490 -0
  76. package/src/dev/index.ts +313 -18
  77. package/src/generators/FunctionGenerator.ts +1 -1
  78. package/src/generators/Generator.ts +4 -1
  79. package/src/init/__tests__/generators.spec.ts +167 -18
  80. package/src/init/__tests__/init.spec.ts +66 -3
  81. package/src/init/generators/auth.ts +6 -5
  82. package/src/init/generators/config.ts +49 -7
  83. package/src/init/generators/docker.ts +8 -8
  84. package/src/init/generators/index.ts +1 -0
  85. package/src/init/generators/models.ts +3 -5
  86. package/src/init/generators/package.ts +4 -0
  87. package/src/init/generators/test.ts +133 -0
  88. package/src/init/generators/ui.ts +13 -12
  89. package/src/init/generators/web.ts +9 -8
  90. package/src/init/index.ts +2 -0
  91. package/src/init/templates/api.ts +6 -6
  92. package/src/init/templates/minimal.ts +2 -2
  93. package/src/init/templates/worker.ts +2 -2
  94. package/src/init/versions.ts +3 -3
  95. package/src/openapi.ts +6 -2
  96. package/src/test/__tests__/__fixtures__/workspace.ts +104 -0
  97. package/src/test/__tests__/api.spec.ts +199 -0
  98. package/src/test/__tests__/auth.spec.ts +162 -0
  99. package/src/test/__tests__/index.spec.ts +323 -0
  100. package/src/test/__tests__/web.spec.ts +210 -0
  101. package/src/test/index.ts +165 -14
  102. package/src/workspace/__tests__/index.spec.ts +3 -0
  103. package/src/workspace/index.ts +4 -2
  104. package/src/workspace/schema.ts +26 -0
  105. package/src/workspace/types.ts +14 -37
  106. package/dist/HostingerProvider-B9N-TKbp.mjs.map +0 -1
  107. package/dist/HostingerProvider-DUV9-Tzg.cjs.map +0 -1
  108. package/dist/Route53Provider-Bs7Arms9.cjs.map +0 -1
  109. package/dist/Route53Provider-C8mS0zY6.mjs.map +0 -1
  110. package/dist/dokploy-api-CQvhV6Hd.cjs.map +0 -1
  111. package/dist/dokploy-api-CWc02yyg.cjs +0 -3
  112. package/dist/dokploy-api-DSJYNx88.mjs +0 -3
  113. package/dist/dokploy-api-z0833e7r.mjs.map +0 -1
  114. package/dist/index-B58qjyBd.d.cts.map +0 -1
  115. package/dist/index-C0SpUT9Y.d.mts.map +0 -1
  116. package/dist/openapi-BcSjLfWq.mjs.map +0 -1
  117. package/dist/openapi-D6Hcfov0.cjs.map +0 -1
  118. package/dist/workspace-2Do2YcGZ.cjs.map +0 -1
  119. package/dist/workspace-BW2iU37P.mjs.map +0 -1
@@ -0,0 +1,163 @@
1
+ import { CreateAccessKeyCommand, CreateUserCommand, GetUserCommand, IAMClient, PutUserPolicyCommand } from "@aws-sdk/client-iam";
2
+ import { CreateBucketCommand, HeadBucketCommand, PutBucketVersioningCommand, S3Client } from "@aws-sdk/client-s3";
3
+
4
+ //#region src/deploy/backup-provisioner.ts
5
+ /**
6
+ * Generate a random suffix for unique resource names
7
+ */
8
+ function randomSuffix() {
9
+ return Math.random().toString(36).substring(2, 8);
10
+ }
11
+ /**
12
+ * Sanitize a name for AWS resources (lowercase alphanumeric and hyphens)
13
+ */
14
+ function sanitizeName(name) {
15
+ return name.toLowerCase().replace(/[^a-z0-9-]/g, "-");
16
+ }
17
+ /**
18
+ * Create AWS clients with optional profile credentials
19
+ */
20
+ async function createAwsClients(region, profile, endpoint) {
21
+ const config = { region };
22
+ if (profile) {
23
+ const { fromIni } = await import("@aws-sdk/credential-providers");
24
+ config.credentials = fromIni({ profile });
25
+ }
26
+ if (endpoint) {
27
+ config.endpoint = endpoint;
28
+ config.forcePathStyle = true;
29
+ config.credentials = {
30
+ accessKeyId: "test",
31
+ secretAccessKey: "test"
32
+ };
33
+ }
34
+ return {
35
+ s3: new S3Client(config),
36
+ iam: new IAMClient(config)
37
+ };
38
+ }
39
+ /**
40
+ * Check if an S3 bucket exists
41
+ */
42
+ async function bucketExists(s3, bucketName) {
43
+ try {
44
+ await s3.send(new HeadBucketCommand({ Bucket: bucketName }));
45
+ return true;
46
+ } catch (error) {
47
+ if (error.name === "NotFound") return false;
48
+ if (error.$metadata?.httpStatusCode === 403) return true;
49
+ throw error;
50
+ }
51
+ }
52
+ /**
53
+ * Check if an IAM user exists
54
+ */
55
+ async function userExists(iam, userName) {
56
+ try {
57
+ await iam.send(new GetUserCommand({ UserName: userName }));
58
+ return true;
59
+ } catch (error) {
60
+ const errorName = error.name;
61
+ if (errorName === "NoSuchEntity" || errorName === "NoSuchEntityException") return false;
62
+ throw error;
63
+ }
64
+ }
65
+ /**
66
+ * Provision backup destination for a deployment.
67
+ *
68
+ * Creates AWS resources (S3 bucket, IAM user) and Dokploy destination if needed.
69
+ * Reuses existing resources from state when possible.
70
+ */
71
+ async function provisionBackupDestination(options) {
72
+ const { api, projectName, stage, config, existingState, logger, awsEndpoint } = options;
73
+ if (existingState?.destinationId) try {
74
+ await api.getDestination(existingState.destinationId);
75
+ logger.log(" Using existing backup destination");
76
+ return existingState;
77
+ } catch {
78
+ logger.log(" Existing destination not found, recreating...");
79
+ }
80
+ const aws = await createAwsClients(config.region, config.profile, awsEndpoint);
81
+ const sanitizedProject = sanitizeName(projectName);
82
+ const bucketName = existingState?.bucketName ?? `${sanitizedProject}-${stage}-backups-${randomSuffix()}`;
83
+ const bucketAlreadyExists = await bucketExists(aws.s3, bucketName);
84
+ if (!bucketAlreadyExists) {
85
+ logger.log(` Creating S3 bucket: ${bucketName}`);
86
+ const createBucketParams = { Bucket: bucketName };
87
+ if (config.region !== "us-east-1") createBucketParams.CreateBucketConfiguration = { LocationConstraint: config.region };
88
+ await aws.s3.send(new CreateBucketCommand(createBucketParams));
89
+ await aws.s3.send(new PutBucketVersioningCommand({
90
+ Bucket: bucketName,
91
+ VersioningConfiguration: { Status: "Enabled" }
92
+ }));
93
+ } else logger.log(` Using existing S3 bucket: ${bucketName}`);
94
+ const iamUserName = existingState?.iamUserName ?? `dokploy-backup-${sanitizedProject}-${stage}`;
95
+ const iamUserAlreadyExists = await userExists(aws.iam, iamUserName);
96
+ if (!iamUserAlreadyExists) {
97
+ logger.log(` Creating IAM user: ${iamUserName}`);
98
+ await aws.iam.send(new CreateUserCommand({ UserName: iamUserName }));
99
+ } else logger.log(` Using existing IAM user: ${iamUserName}`);
100
+ const policyDocument = {
101
+ Version: "2012-10-17",
102
+ Statement: [{
103
+ Effect: "Allow",
104
+ Action: [
105
+ "s3:GetObject",
106
+ "s3:PutObject",
107
+ "s3:DeleteObject",
108
+ "s3:ListBucket",
109
+ "s3:GetBucketLocation"
110
+ ],
111
+ Resource: [`arn:aws:s3:::${bucketName}`, `arn:aws:s3:::${bucketName}/*`]
112
+ }]
113
+ };
114
+ logger.log(" Updating IAM policy");
115
+ await aws.iam.send(new PutUserPolicyCommand({
116
+ UserName: iamUserName,
117
+ PolicyName: "DokployBackupAccess",
118
+ PolicyDocument: JSON.stringify(policyDocument)
119
+ }));
120
+ let accessKeyId;
121
+ let secretAccessKey;
122
+ if (existingState?.iamAccessKeyId && existingState?.iamSecretAccessKey) {
123
+ logger.log(" Using existing IAM access key");
124
+ accessKeyId = existingState.iamAccessKeyId;
125
+ secretAccessKey = existingState.iamSecretAccessKey;
126
+ } else {
127
+ logger.log(" Creating IAM access key");
128
+ const accessKeyResult = await aws.iam.send(new CreateAccessKeyCommand({ UserName: iamUserName }));
129
+ if (!accessKeyResult.AccessKey) throw new Error("Failed to create IAM access key");
130
+ accessKeyId = accessKeyResult.AccessKey.AccessKeyId;
131
+ secretAccessKey = accessKeyResult.AccessKey.SecretAccessKey;
132
+ }
133
+ const destinationName = `${sanitizedProject}-${stage}-s3`;
134
+ logger.log(` Creating Dokploy destination: ${destinationName}`);
135
+ const { destination, created } = await api.findOrCreateDestination(destinationName, {
136
+ accessKey: accessKeyId,
137
+ secretAccessKey,
138
+ bucket: bucketName,
139
+ region: config.region
140
+ });
141
+ if (created) logger.log(" ✓ Dokploy destination created");
142
+ else logger.log(" ✓ Using existing Dokploy destination");
143
+ try {
144
+ await api.testDestinationConnection(destination.destinationId);
145
+ logger.log(" ✓ Destination connection verified");
146
+ } catch (error) {
147
+ logger.log(` ⚠ Warning: Could not verify destination connection: ${error}`);
148
+ }
149
+ return {
150
+ bucketName,
151
+ bucketArn: `arn:aws:s3:::${bucketName}`,
152
+ iamUserName,
153
+ iamAccessKeyId: accessKeyId,
154
+ iamSecretAccessKey: secretAccessKey,
155
+ destinationId: destination.destinationId,
156
+ region: config.region,
157
+ createdAt: existingState?.createdAt ?? (/* @__PURE__ */ new Date()).toISOString()
158
+ };
159
+ }
160
+
161
+ //#endregion
162
+ export { provisionBackupDestination };
163
+ //# sourceMappingURL=backup-provisioner-BIArpmTr.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"backup-provisioner-BIArpmTr.mjs","names":["name: string","region: string","profile?: string","endpoint?: string","config: S3ClientConfig & IAMClientConfig","s3: S3Client","bucketName: string","iam: IAMClient","userName: string","options: ProvisionBackupOptions","createBucketParams: {\n\t\t\tBucket: string;\n\t\t\tCreateBucketConfiguration?: {\n\t\t\t\tLocationConstraint: BucketLocationConstraint;\n\t\t\t};\n\t\t}","accessKeyId: string","secretAccessKey: string"],"sources":["../src/deploy/backup-provisioner.ts"],"sourcesContent":["/**\n * Backup Destination Provisioner\n *\n * Creates AWS resources (S3 bucket, IAM user, access keys) and configures\n * Dokploy backup destinations for database backups.\n */\n\nimport {\n\tCreateAccessKeyCommand,\n\tCreateUserCommand,\n\tGetUserCommand,\n\tIAMClient,\n\ttype IAMClientConfig,\n\tPutUserPolicyCommand,\n} from '@aws-sdk/client-iam';\nimport {\n\ttype BucketLocationConstraint,\n\tCreateBucketCommand,\n\tHeadBucketCommand,\n\tPutBucketVersioningCommand,\n\tS3Client,\n\ttype S3ClientConfig,\n} from '@aws-sdk/client-s3';\nimport type { BackupsConfig } from '../workspace/types.js';\nimport type { DokployApi } from './dokploy-api.js';\nimport type { BackupState } from './state.js';\n\nexport interface ProvisionBackupOptions {\n\t/** Dokploy API client */\n\tapi: DokployApi;\n\t/** Dokploy project ID */\n\tprojectId: string;\n\t/** Workspace name (used for resource naming) */\n\tprojectName: string;\n\t/** Deploy stage (e.g., 'production', 'staging') */\n\tstage: string;\n\t/** Backup configuration */\n\tconfig: BackupsConfig;\n\t/** Existing backup state (if any) */\n\texistingState?: BackupState;\n\t/** Logger for progress output */\n\tlogger: { log: (msg: string) => void };\n\t/** AWS endpoint override (for testing with LocalStack) */\n\tawsEndpoint?: string;\n}\n\n/**\n * Generate a random suffix for unique resource names\n */\nfunction randomSuffix(): string {\n\treturn Math.random().toString(36).substring(2, 8);\n}\n\n/**\n * Sanitize a name for AWS resources (lowercase alphanumeric and hyphens)\n */\nfunction sanitizeName(name: string): string {\n\treturn name.toLowerCase().replace(/[^a-z0-9-]/g, '-');\n}\n\n/**\n * Create AWS clients with optional profile credentials\n */\nasync function createAwsClients(\n\tregion: string,\n\tprofile?: string,\n\tendpoint?: string,\n): Promise<{ s3: S3Client; iam: IAMClient }> {\n\tconst config: S3ClientConfig & IAMClientConfig = { region };\n\n\tif (profile) {\n\t\tconst { fromIni } = await import('@aws-sdk/credential-providers');\n\t\tconfig.credentials = fromIni({ profile });\n\t}\n\n\t// Support custom endpoint for testing (e.g., LocalStack)\n\tif (endpoint) {\n\t\tconfig.endpoint = endpoint;\n\t\t(config as S3ClientConfig).forcePathStyle = true;\n\t\t// Use test credentials when endpoint is specified\n\t\tconfig.credentials = {\n\t\t\taccessKeyId: 'test',\n\t\t\tsecretAccessKey: 'test',\n\t\t};\n\t}\n\n\treturn {\n\t\ts3: new S3Client(config),\n\t\tiam: new IAMClient(config),\n\t};\n}\n\n/**\n * Check if an S3 bucket exists\n */\nasync function bucketExists(\n\ts3: S3Client,\n\tbucketName: string,\n): Promise<boolean> {\n\ttry {\n\t\tawait s3.send(new HeadBucketCommand({ Bucket: bucketName }));\n\t\treturn true;\n\t} catch (error) {\n\t\tif ((error as { name?: string }).name === 'NotFound') {\n\t\t\treturn false;\n\t\t}\n\t\t// 403 means bucket exists but we don't have access\n\t\tif (\n\t\t\t(error as { $metadata?: { httpStatusCode?: number } }).$metadata\n\t\t\t\t?.httpStatusCode === 403\n\t\t) {\n\t\t\treturn true;\n\t\t}\n\t\tthrow error;\n\t}\n}\n\n/**\n * Check if an IAM user exists\n */\nasync function userExists(iam: IAMClient, userName: string): Promise<boolean> {\n\ttry {\n\t\tawait iam.send(new GetUserCommand({ UserName: userName }));\n\t\treturn true;\n\t} catch (error) {\n\t\tconst errorName = (error as { name?: string }).name;\n\t\t// AWS returns 'NoSuchEntity', LocalStack returns 'NoSuchEntityException'\n\t\tif (errorName === 'NoSuchEntity' || errorName === 'NoSuchEntityException') {\n\t\t\treturn false;\n\t\t}\n\t\tthrow error;\n\t}\n}\n\n/**\n * Provision backup destination for a deployment.\n *\n * Creates AWS resources (S3 bucket, IAM user) and Dokploy destination if needed.\n * Reuses existing resources from state when possible.\n */\nexport async function provisionBackupDestination(\n\toptions: ProvisionBackupOptions,\n): Promise<BackupState> {\n\tconst {\n\t\tapi,\n\t\tprojectName,\n\t\tstage,\n\t\tconfig,\n\t\texistingState,\n\t\tlogger,\n\t\tawsEndpoint,\n\t} = options;\n\n\t// If we have existing state, verify the Dokploy destination still exists\n\tif (existingState?.destinationId) {\n\t\ttry {\n\t\t\tawait api.getDestination(existingState.destinationId);\n\t\t\tlogger.log(' Using existing backup destination');\n\t\t\treturn existingState;\n\t\t} catch {\n\t\t\tlogger.log(' Existing destination not found, recreating...');\n\t\t}\n\t}\n\n\t// Create AWS clients\n\tconst aws = await createAwsClients(\n\t\tconfig.region,\n\t\tconfig.profile,\n\t\tawsEndpoint,\n\t);\n\tconst sanitizedProject = sanitizeName(projectName);\n\n\t// 1. Create or verify S3 bucket\n\tconst bucketName =\n\t\texistingState?.bucketName ??\n\t\t`${sanitizedProject}-${stage}-backups-${randomSuffix()}`;\n\n\tconst bucketAlreadyExists = await bucketExists(aws.s3, bucketName);\n\tif (!bucketAlreadyExists) {\n\t\tlogger.log(` Creating S3 bucket: ${bucketName}`);\n\n\t\t// CreateBucket needs LocationConstraint for non-us-east-1 regions\n\t\tconst createBucketParams: {\n\t\t\tBucket: string;\n\t\t\tCreateBucketConfiguration?: {\n\t\t\t\tLocationConstraint: BucketLocationConstraint;\n\t\t\t};\n\t\t} = {\n\t\t\tBucket: bucketName,\n\t\t};\n\t\tif (config.region !== 'us-east-1') {\n\t\t\tcreateBucketParams.CreateBucketConfiguration = {\n\t\t\t\tLocationConstraint: config.region as BucketLocationConstraint,\n\t\t\t};\n\t\t}\n\n\t\tawait aws.s3.send(new CreateBucketCommand(createBucketParams));\n\n\t\t// Enable versioning for backup integrity\n\t\tawait aws.s3.send(\n\t\t\tnew PutBucketVersioningCommand({\n\t\t\t\tBucket: bucketName,\n\t\t\t\tVersioningConfiguration: { Status: 'Enabled' },\n\t\t\t}),\n\t\t);\n\t} else {\n\t\tlogger.log(` Using existing S3 bucket: ${bucketName}`);\n\t}\n\n\t// 2. Create or verify IAM user\n\tconst iamUserName =\n\t\texistingState?.iamUserName ?? `dokploy-backup-${sanitizedProject}-${stage}`;\n\n\tconst iamUserAlreadyExists = await userExists(aws.iam, iamUserName);\n\tif (!iamUserAlreadyExists) {\n\t\tlogger.log(` Creating IAM user: ${iamUserName}`);\n\t\tawait aws.iam.send(new CreateUserCommand({ UserName: iamUserName }));\n\t} else {\n\t\tlogger.log(` Using existing IAM user: ${iamUserName}`);\n\t}\n\n\t// 3. Attach bucket policy to IAM user\n\tconst policyDocument = {\n\t\tVersion: '2012-10-17',\n\t\tStatement: [\n\t\t\t{\n\t\t\t\tEffect: 'Allow',\n\t\t\t\tAction: [\n\t\t\t\t\t's3:GetObject',\n\t\t\t\t\t's3:PutObject',\n\t\t\t\t\t's3:DeleteObject',\n\t\t\t\t\t's3:ListBucket',\n\t\t\t\t\t's3:GetBucketLocation',\n\t\t\t\t],\n\t\t\t\tResource: [\n\t\t\t\t\t`arn:aws:s3:::${bucketName}`,\n\t\t\t\t\t`arn:aws:s3:::${bucketName}/*`,\n\t\t\t\t],\n\t\t\t},\n\t\t],\n\t};\n\n\tlogger.log(' Updating IAM policy');\n\tawait aws.iam.send(\n\t\tnew PutUserPolicyCommand({\n\t\t\tUserName: iamUserName,\n\t\t\tPolicyName: 'DokployBackupAccess',\n\t\t\tPolicyDocument: JSON.stringify(policyDocument),\n\t\t}),\n\t);\n\n\t// 4. Create access key (or reuse existing if state has it and destination needs recreation)\n\tlet accessKeyId: string;\n\tlet secretAccessKey: string;\n\n\tif (existingState?.iamAccessKeyId && existingState?.iamSecretAccessKey) {\n\t\t// Reuse existing credentials\n\t\tlogger.log(' Using existing IAM access key');\n\t\taccessKeyId = existingState.iamAccessKeyId;\n\t\tsecretAccessKey = existingState.iamSecretAccessKey;\n\t} else {\n\t\t// Create new access key\n\t\tlogger.log(' Creating IAM access key');\n\t\tconst accessKeyResult = await aws.iam.send(\n\t\t\tnew CreateAccessKeyCommand({ UserName: iamUserName }),\n\t\t);\n\n\t\tif (!accessKeyResult.AccessKey) {\n\t\t\tthrow new Error('Failed to create IAM access key');\n\t\t}\n\n\t\taccessKeyId = accessKeyResult.AccessKey.AccessKeyId!;\n\t\tsecretAccessKey = accessKeyResult.AccessKey.SecretAccessKey!;\n\t}\n\n\t// 5. Create Dokploy destination\n\tconst destinationName = `${sanitizedProject}-${stage}-s3`;\n\tlogger.log(` Creating Dokploy destination: ${destinationName}`);\n\n\tconst { destination, created } = await api.findOrCreateDestination(\n\t\tdestinationName,\n\t\t{\n\t\t\taccessKey: accessKeyId,\n\t\t\tsecretAccessKey: secretAccessKey,\n\t\t\tbucket: bucketName,\n\t\t\tregion: config.region,\n\t\t},\n\t);\n\n\tif (created) {\n\t\tlogger.log(' ✓ Dokploy destination created');\n\t} else {\n\t\tlogger.log(' ✓ Using existing Dokploy destination');\n\t}\n\n\t// 6. Test connection\n\ttry {\n\t\tawait api.testDestinationConnection(destination.destinationId);\n\t\tlogger.log(' ✓ Destination connection verified');\n\t} catch (error) {\n\t\tlogger.log(\n\t\t\t` ⚠ Warning: Could not verify destination connection: ${error}`,\n\t\t);\n\t}\n\n\treturn {\n\t\tbucketName,\n\t\tbucketArn: `arn:aws:s3:::${bucketName}`,\n\t\tiamUserName,\n\t\tiamAccessKeyId: accessKeyId,\n\t\tiamSecretAccessKey: secretAccessKey,\n\t\tdestinationId: destination.destinationId,\n\t\tregion: config.region,\n\t\tcreatedAt: existingState?.createdAt ?? new Date().toISOString(),\n\t};\n}\n"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  const require_chunk = require('./chunk-CUT6urMc.cjs');
2
- const require_workspace = require('./workspace-2Do2YcGZ.cjs');
2
+ const require_workspace = require('./workspace-D2ocAlpl.cjs');
3
3
  const node_fs = require_chunk.__toESM(require("node:fs"));
4
4
  const node_path = require_chunk.__toESM(require("node:path"));
5
5
 
@@ -184,6 +184,28 @@ async function loadAppConfig(cwd = process.cwd()) {
184
184
  appRoot: (0, node_path.join)(workspaceRoot, app.path)
185
185
  };
186
186
  }
187
+ /**
188
+ * Load workspace info for any app (frontend or backend).
189
+ * Unlike loadAppConfig, this does NOT require the app to have a gkm config
190
+ * (routes, entry, etc.), making it suitable for gkm exec/test from frontend apps.
191
+ */
192
+ async function loadWorkspaceAppInfo(cwd = process.cwd()) {
193
+ const appName = getAppNameFromCwd(cwd);
194
+ if (!appName) throw new Error("Could not determine app name. Ensure package.json exists with a \"name\" field.");
195
+ const { config, workspaceRoot } = await loadRawConfig(cwd);
196
+ const loadedConfig = require_workspace.processConfig(config, workspaceRoot);
197
+ const app = loadedConfig.workspace.apps[appName];
198
+ if (!app) {
199
+ const availableApps = Object.keys(loadedConfig.workspace.apps).join(", ");
200
+ throw new Error(`App "${appName}" not found in workspace config. Available apps: ${availableApps}. Ensure the package.json name matches the app key in gkm.config.ts.`);
201
+ }
202
+ return {
203
+ appName,
204
+ app,
205
+ workspace: loadedConfig.workspace,
206
+ workspaceRoot
207
+ };
208
+ }
187
209
 
188
210
  //#endregion
189
211
  Object.defineProperty(exports, 'defineConfig', {
@@ -210,6 +232,12 @@ Object.defineProperty(exports, 'loadConfig', {
210
232
  return loadConfig;
211
233
  }
212
234
  });
235
+ Object.defineProperty(exports, 'loadWorkspaceAppInfo', {
236
+ enumerable: true,
237
+ get: function () {
238
+ return loadWorkspaceAppInfo;
239
+ }
240
+ });
213
241
  Object.defineProperty(exports, 'loadWorkspaceConfig', {
214
242
  enumerable: true,
215
243
  get: function () {
@@ -222,4 +250,4 @@ Object.defineProperty(exports, 'parseModuleConfig', {
222
250
  return parseModuleConfig;
223
251
  }
224
252
  });
225
- //# sourceMappingURL=config-ZQM1vBoz.cjs.map
253
+ //# sourceMappingURL=config-6JHOwLCx.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"config-ZQM1vBoz.cjs","names":["config: GkmConfig","configString: string","defaultAlias: string","cwd: string"],"sources":["../src/config.ts"],"sourcesContent":["import { existsSync, readFileSync } from 'node:fs';\nimport { dirname, join, parse } from 'node:path';\nimport type { GkmConfig } from './types.js';\nimport {\n\tgetAppGkmConfig,\n\tisWorkspaceConfig,\n\ttype LoadedConfig,\n\ttype NormalizedAppConfig,\n\ttype NormalizedWorkspace,\n\tprocessConfig,\n\ttype WorkspaceConfig,\n} from './workspace/index.js';\n\nexport type { GkmConfig } from './types.js';\nexport type { LoadedConfig, WorkspaceConfig } from './workspace/index.js';\nexport { defineWorkspace } from './workspace/index.js';\n/**\n * Define GKM configuration with full TypeScript support.\n * This is an identity function that provides type safety and autocomplete.\n *\n * @example\n * ```ts\n * // gkm.config.ts\n * import { defineConfig } from '@geekmidas/cli/config';\n *\n * export default defineConfig({\n * routes: './src/endpoints/**\\/*.ts',\n * envParser: './src/config/env',\n * logger: './src/config/logger',\n * telescope: true,\n * });\n * ```\n */\nexport function defineConfig(config: GkmConfig): GkmConfig {\n\treturn config;\n}\n\nexport interface ParsedModuleConfig {\n\tpath: string;\n\timportPattern: string;\n}\n\n/**\n * Parse a module config string into path and import pattern.\n *\n * @param configString - Config string in format \"./path/to/module\" or \"./path/to/module#exportName\"\n * @param defaultAlias - The default alias name to use if no export name specified\n * @returns Object with path and import pattern\n *\n * @example\n * parseModuleConfig('./src/config/env', 'envParser')\n * // { path: './src/config/env', importPattern: 'envParser' }\n *\n * parseModuleConfig('./src/config/env#envParser', 'envParser')\n * // { path: './src/config/env', importPattern: '{ envParser }' }\n *\n * parseModuleConfig('./src/config/env#myEnv', 'envParser')\n * // { path: './src/config/env', importPattern: '{ myEnv as envParser }' }\n */\nexport function parseModuleConfig(\n\tconfigString: string,\n\tdefaultAlias: string,\n): ParsedModuleConfig {\n\tconst parts = configString.split('#');\n\tconst path = parts[0] ?? configString;\n\tconst exportName = parts[1];\n\tconst importPattern = !exportName\n\t\t? defaultAlias\n\t\t: exportName === defaultAlias\n\t\t\t? `{ ${defaultAlias} }`\n\t\t\t: `{ ${exportName} as ${defaultAlias} }`;\n\n\treturn { path, importPattern };\n}\n\nexport interface ConfigDiscoveryResult {\n\tconfigPath: string;\n\tworkspaceRoot: string;\n}\n\n/**\n * Find and return the path to the config file.\n *\n * Resolution order:\n * 1. GKM_CONFIG_PATH env var (set by workspace dev command)\n * 2. Walk up directory tree from cwd\n */\nfunction findConfigPath(cwd: string): ConfigDiscoveryResult {\n\tconst files = ['gkm.config.json', 'gkm.config.ts', 'gkm.config.js'];\n\n\t// Check GKM_CONFIG_PATH env var first (set by workspace dev command)\n\tconst envConfigPath = process.env.GKM_CONFIG_PATH;\n\tif (envConfigPath && existsSync(envConfigPath)) {\n\t\treturn {\n\t\t\tconfigPath: envConfigPath,\n\t\t\tworkspaceRoot: dirname(envConfigPath),\n\t\t};\n\t}\n\n\t// Walk up directory tree to find config\n\tlet currentDir = cwd;\n\tconst { root } = parse(currentDir);\n\n\twhile (currentDir !== root) {\n\t\tfor (const file of files) {\n\t\t\tconst configPath = join(currentDir, file);\n\t\t\tif (existsSync(configPath)) {\n\t\t\t\treturn {\n\t\t\t\t\tconfigPath,\n\t\t\t\t\tworkspaceRoot: currentDir,\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\t\tcurrentDir = dirname(currentDir);\n\t}\n\n\tthrow new Error(\n\t\t'Configuration file not found. Please create gkm.config.json, gkm.config.ts, or gkm.config.js in the project root.',\n\t);\n}\n\n/**\n * Get app name from package.json in the given directory.\n * Handles scoped packages by extracting the name after the scope.\n *\n * @example\n * getAppNameFromCwd('/path/to/apps/api')\n * // package.json: { \"name\": \"@myorg/api\" }\n * // Returns: 'api'\n */\nexport function getAppNameFromCwd(cwd: string = process.cwd()): string | null {\n\tconst packageJsonPath = join(cwd, 'package.json');\n\n\tif (!existsSync(packageJsonPath)) {\n\t\treturn null;\n\t}\n\n\ttry {\n\t\tconst packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));\n\t\tconst name = packageJson.name as string | undefined;\n\n\t\tif (!name) {\n\t\t\treturn null;\n\t\t}\n\n\t\t// Handle scoped packages: @scope/name -> name\n\t\tif (name.startsWith('@') && name.includes('/')) {\n\t\t\treturn name.split('/')[1] ?? null;\n\t\t}\n\n\t\treturn name;\n\t} catch {\n\t\treturn null;\n\t}\n}\n\ninterface RawConfigResult {\n\tconfig: GkmConfig | WorkspaceConfig;\n\tworkspaceRoot: string;\n}\n\n/**\n * Load raw configuration from file.\n */\nasync function loadRawConfig(cwd: string): Promise<RawConfigResult> {\n\tconst { configPath, workspaceRoot } = findConfigPath(cwd);\n\n\ttry {\n\t\tconst config = await import(configPath);\n\t\treturn {\n\t\t\tconfig: config.default,\n\t\t\tworkspaceRoot,\n\t\t};\n\t} catch (error) {\n\t\tthrow new Error(`Failed to load config: ${(error as Error).message}`);\n\t}\n}\n\n/**\n * Load configuration file (single-app format).\n * For backwards compatibility with existing code.\n *\n * @deprecated Use loadWorkspaceConfig for new code\n */\nexport async function loadConfig(\n\tcwd: string = process.cwd(),\n): Promise<GkmConfig> {\n\tconst { config } = await loadRawConfig(cwd);\n\n\t// If it's a workspace config, throw an error\n\tif (isWorkspaceConfig(config)) {\n\t\tthrow new Error(\n\t\t\t'Workspace configuration detected. Use loadWorkspaceConfig() instead.',\n\t\t);\n\t}\n\n\treturn config;\n}\n\n/**\n * Load configuration file and process it as a workspace.\n * Works with both single-app and workspace configurations.\n *\n * Single-app configs are automatically wrapped as a workspace with one app.\n *\n * @example\n * ```ts\n * const { type, workspace } = await loadWorkspaceConfig();\n *\n * if (type === 'workspace') {\n * console.log('Multi-app workspace:', workspace.apps);\n * } else {\n * console.log('Single app wrapped as workspace');\n * }\n * ```\n */\nexport async function loadWorkspaceConfig(\n\tcwd: string = process.cwd(),\n): Promise<LoadedConfig> {\n\tconst { config, workspaceRoot } = await loadRawConfig(cwd);\n\treturn processConfig(config, workspaceRoot);\n}\n\nexport interface AppConfigResult {\n\tappName: string;\n\tapp: NormalizedAppConfig;\n\tgkmConfig: GkmConfig;\n\tworkspace: NormalizedWorkspace;\n\tworkspaceRoot: string;\n\tappRoot: string;\n}\n\n/**\n * Load app-specific configuration from workspace.\n * Uses the app name from package.json to find the correct app config.\n *\n * @example\n * ```ts\n * // From apps/api directory with package.json: { \"name\": \"@myorg/api\" }\n * const { app, workspace, workspaceRoot } = await loadAppConfig();\n * console.log(app.routes); // './src/endpoints/**\\/*.ts'\n * ```\n */\nexport async function loadAppConfig(\n\tcwd: string = process.cwd(),\n): Promise<AppConfigResult> {\n\tconst appName = getAppNameFromCwd(cwd);\n\n\tif (!appName) {\n\t\tthrow new Error(\n\t\t\t'Could not determine app name. Ensure package.json exists with a \"name\" field.',\n\t\t);\n\t}\n\n\tconst { config, workspaceRoot } = await loadRawConfig(cwd);\n\tconst loadedConfig = processConfig(config, workspaceRoot);\n\n\t// Find the app in workspace (apps is a Record<string, NormalizedAppConfig>)\n\tconst app = loadedConfig.workspace.apps[appName];\n\n\tif (!app) {\n\t\tconst availableApps = Object.keys(loadedConfig.workspace.apps).join(', ');\n\t\tthrow new Error(\n\t\t\t`App \"${appName}\" not found in workspace config. Available apps: ${availableApps}. ` +\n\t\t\t\t`Ensure the package.json name matches the app key in gkm.config.ts.`,\n\t\t);\n\t}\n\n\t// Get the app's GKM config using the helper\n\tconst gkmConfig = getAppGkmConfig(loadedConfig.workspace, appName);\n\n\tif (!gkmConfig) {\n\t\tthrow new Error(\n\t\t\t`App \"${appName}\" is not a backend app and cannot be run with gkm dev.`,\n\t\t);\n\t}\n\n\treturn {\n\t\tappName,\n\t\tapp,\n\t\tgkmConfig,\n\t\tworkspace: loadedConfig.workspace,\n\t\tworkspaceRoot,\n\t\tappRoot: join(workspaceRoot, app.path),\n\t};\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"config-6JHOwLCx.cjs","names":["config: GkmConfig","configString: string","defaultAlias: string","cwd: string"],"sources":["../src/config.ts"],"sourcesContent":["import { existsSync, readFileSync } from 'node:fs';\nimport { dirname, join, parse } from 'node:path';\nimport type { GkmConfig } from './types.js';\nimport {\n\tgetAppGkmConfig,\n\tisWorkspaceConfig,\n\ttype LoadedConfig,\n\ttype NormalizedAppConfig,\n\ttype NormalizedWorkspace,\n\tprocessConfig,\n\ttype WorkspaceConfig,\n} from './workspace/index.js';\n\nexport type { GkmConfig } from './types.js';\nexport type { LoadedConfig, WorkspaceConfig } from './workspace/index.js';\nexport { defineWorkspace } from './workspace/index.js';\n/**\n * Define GKM configuration with full TypeScript support.\n * This is an identity function that provides type safety and autocomplete.\n *\n * @example\n * ```ts\n * // gkm.config.ts\n * import { defineConfig } from '@geekmidas/cli/config';\n *\n * export default defineConfig({\n * routes: './src/endpoints/**\\/*.ts',\n * envParser: './src/config/env',\n * logger: './src/config/logger',\n * telescope: true,\n * });\n * ```\n */\nexport function defineConfig(config: GkmConfig): GkmConfig {\n\treturn config;\n}\n\nexport interface ParsedModuleConfig {\n\tpath: string;\n\timportPattern: string;\n}\n\n/**\n * Parse a module config string into path and import pattern.\n *\n * @param configString - Config string in format \"./path/to/module\" or \"./path/to/module#exportName\"\n * @param defaultAlias - The default alias name to use if no export name specified\n * @returns Object with path and import pattern\n *\n * @example\n * parseModuleConfig('./src/config/env', 'envParser')\n * // { path: './src/config/env', importPattern: 'envParser' }\n *\n * parseModuleConfig('./src/config/env#envParser', 'envParser')\n * // { path: './src/config/env', importPattern: '{ envParser }' }\n *\n * parseModuleConfig('./src/config/env#myEnv', 'envParser')\n * // { path: './src/config/env', importPattern: '{ myEnv as envParser }' }\n */\nexport function parseModuleConfig(\n\tconfigString: string,\n\tdefaultAlias: string,\n): ParsedModuleConfig {\n\tconst parts = configString.split('#');\n\tconst path = parts[0] ?? configString;\n\tconst exportName = parts[1];\n\tconst importPattern = !exportName\n\t\t? defaultAlias\n\t\t: exportName === defaultAlias\n\t\t\t? `{ ${defaultAlias} }`\n\t\t\t: `{ ${exportName} as ${defaultAlias} }`;\n\n\treturn { path, importPattern };\n}\n\nexport interface ConfigDiscoveryResult {\n\tconfigPath: string;\n\tworkspaceRoot: string;\n}\n\n/**\n * Find and return the path to the config file.\n *\n * Resolution order:\n * 1. GKM_CONFIG_PATH env var (set by workspace dev command)\n * 2. Walk up directory tree from cwd\n */\nfunction findConfigPath(cwd: string): ConfigDiscoveryResult {\n\tconst files = ['gkm.config.json', 'gkm.config.ts', 'gkm.config.js'];\n\n\t// Check GKM_CONFIG_PATH env var first (set by workspace dev command)\n\tconst envConfigPath = process.env.GKM_CONFIG_PATH;\n\tif (envConfigPath && existsSync(envConfigPath)) {\n\t\treturn {\n\t\t\tconfigPath: envConfigPath,\n\t\t\tworkspaceRoot: dirname(envConfigPath),\n\t\t};\n\t}\n\n\t// Walk up directory tree to find config\n\tlet currentDir = cwd;\n\tconst { root } = parse(currentDir);\n\n\twhile (currentDir !== root) {\n\t\tfor (const file of files) {\n\t\t\tconst configPath = join(currentDir, file);\n\t\t\tif (existsSync(configPath)) {\n\t\t\t\treturn {\n\t\t\t\t\tconfigPath,\n\t\t\t\t\tworkspaceRoot: currentDir,\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\t\tcurrentDir = dirname(currentDir);\n\t}\n\n\tthrow new Error(\n\t\t'Configuration file not found. Please create gkm.config.json, gkm.config.ts, or gkm.config.js in the project root.',\n\t);\n}\n\n/**\n * Get app name from package.json in the given directory.\n * Handles scoped packages by extracting the name after the scope.\n *\n * @example\n * getAppNameFromCwd('/path/to/apps/api')\n * // package.json: { \"name\": \"@myorg/api\" }\n * // Returns: 'api'\n */\nexport function getAppNameFromCwd(cwd: string = process.cwd()): string | null {\n\tconst packageJsonPath = join(cwd, 'package.json');\n\n\tif (!existsSync(packageJsonPath)) {\n\t\treturn null;\n\t}\n\n\ttry {\n\t\tconst packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));\n\t\tconst name = packageJson.name as string | undefined;\n\n\t\tif (!name) {\n\t\t\treturn null;\n\t\t}\n\n\t\t// Handle scoped packages: @scope/name -> name\n\t\tif (name.startsWith('@') && name.includes('/')) {\n\t\t\treturn name.split('/')[1] ?? null;\n\t\t}\n\n\t\treturn name;\n\t} catch {\n\t\treturn null;\n\t}\n}\n\ninterface RawConfigResult {\n\tconfig: GkmConfig | WorkspaceConfig;\n\tworkspaceRoot: string;\n}\n\n/**\n * Load raw configuration from file.\n */\nasync function loadRawConfig(cwd: string): Promise<RawConfigResult> {\n\tconst { configPath, workspaceRoot } = findConfigPath(cwd);\n\n\ttry {\n\t\tconst config = await import(configPath);\n\t\treturn {\n\t\t\tconfig: config.default,\n\t\t\tworkspaceRoot,\n\t\t};\n\t} catch (error) {\n\t\tthrow new Error(`Failed to load config: ${(error as Error).message}`);\n\t}\n}\n\n/**\n * Load configuration file (single-app format).\n * For backwards compatibility with existing code.\n *\n * @deprecated Use loadWorkspaceConfig for new code\n */\nexport async function loadConfig(\n\tcwd: string = process.cwd(),\n): Promise<GkmConfig> {\n\tconst { config } = await loadRawConfig(cwd);\n\n\t// If it's a workspace config, throw an error\n\tif (isWorkspaceConfig(config)) {\n\t\tthrow new Error(\n\t\t\t'Workspace configuration detected. Use loadWorkspaceConfig() instead.',\n\t\t);\n\t}\n\n\treturn config;\n}\n\n/**\n * Load configuration file and process it as a workspace.\n * Works with both single-app and workspace configurations.\n *\n * Single-app configs are automatically wrapped as a workspace with one app.\n *\n * @example\n * ```ts\n * const { type, workspace } = await loadWorkspaceConfig();\n *\n * if (type === 'workspace') {\n * console.log('Multi-app workspace:', workspace.apps);\n * } else {\n * console.log('Single app wrapped as workspace');\n * }\n * ```\n */\nexport async function loadWorkspaceConfig(\n\tcwd: string = process.cwd(),\n): Promise<LoadedConfig> {\n\tconst { config, workspaceRoot } = await loadRawConfig(cwd);\n\treturn processConfig(config, workspaceRoot);\n}\n\nexport interface AppConfigResult {\n\tappName: string;\n\tapp: NormalizedAppConfig;\n\tgkmConfig: GkmConfig;\n\tworkspace: NormalizedWorkspace;\n\tworkspaceRoot: string;\n\tappRoot: string;\n}\n\n/**\n * Load app-specific configuration from workspace.\n * Uses the app name from package.json to find the correct app config.\n *\n * @example\n * ```ts\n * // From apps/api directory with package.json: { \"name\": \"@myorg/api\" }\n * const { app, workspace, workspaceRoot } = await loadAppConfig();\n * console.log(app.routes); // './src/endpoints/**\\/*.ts'\n * ```\n */\nexport async function loadAppConfig(\n\tcwd: string = process.cwd(),\n): Promise<AppConfigResult> {\n\tconst appName = getAppNameFromCwd(cwd);\n\n\tif (!appName) {\n\t\tthrow new Error(\n\t\t\t'Could not determine app name. Ensure package.json exists with a \"name\" field.',\n\t\t);\n\t}\n\n\tconst { config, workspaceRoot } = await loadRawConfig(cwd);\n\tconst loadedConfig = processConfig(config, workspaceRoot);\n\n\t// Find the app in workspace (apps is a Record<string, NormalizedAppConfig>)\n\tconst app = loadedConfig.workspace.apps[appName];\n\n\tif (!app) {\n\t\tconst availableApps = Object.keys(loadedConfig.workspace.apps).join(', ');\n\t\tthrow new Error(\n\t\t\t`App \"${appName}\" not found in workspace config. Available apps: ${availableApps}. ` +\n\t\t\t\t`Ensure the package.json name matches the app key in gkm.config.ts.`,\n\t\t);\n\t}\n\n\t// Get the app's GKM config using the helper\n\tconst gkmConfig = getAppGkmConfig(loadedConfig.workspace, appName);\n\n\tif (!gkmConfig) {\n\t\tthrow new Error(\n\t\t\t`App \"${appName}\" is not a backend app and cannot be run with gkm dev.`,\n\t\t);\n\t}\n\n\treturn {\n\t\tappName,\n\t\tapp,\n\t\tgkmConfig,\n\t\tworkspace: loadedConfig.workspace,\n\t\tworkspaceRoot,\n\t\tappRoot: join(workspaceRoot, app.path),\n\t};\n}\n\nexport interface WorkspaceAppInfo {\n\tappName: string;\n\tapp: NormalizedAppConfig;\n\tworkspace: NormalizedWorkspace;\n\tworkspaceRoot: string;\n}\n\n/**\n * Load workspace info for any app (frontend or backend).\n * Unlike loadAppConfig, this does NOT require the app to have a gkm config\n * (routes, entry, etc.), making it suitable for gkm exec/test from frontend apps.\n */\nexport async function loadWorkspaceAppInfo(\n\tcwd: string = process.cwd(),\n): Promise<WorkspaceAppInfo> {\n\tconst appName = getAppNameFromCwd(cwd);\n\n\tif (!appName) {\n\t\tthrow new Error(\n\t\t\t'Could not determine app name. Ensure package.json exists with a \"name\" field.',\n\t\t);\n\t}\n\n\tconst { config, workspaceRoot } = await loadRawConfig(cwd);\n\tconst loadedConfig = processConfig(config, workspaceRoot);\n\n\tconst app = loadedConfig.workspace.apps[appName];\n\n\tif (!app) {\n\t\tconst availableApps = Object.keys(loadedConfig.workspace.apps).join(', ');\n\t\tthrow new Error(\n\t\t\t`App \"${appName}\" not found in workspace config. Available apps: ${availableApps}. ` +\n\t\t\t\t`Ensure the package.json name matches the app key in gkm.config.ts.`,\n\t\t);\n\t}\n\n\treturn {\n\t\tappName,\n\t\tapp,\n\t\tworkspace: loadedConfig.workspace,\n\t\tworkspaceRoot,\n\t};\n}\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { getAppGkmConfig, isWorkspaceConfig, processConfig } from "./workspace-BW2iU37P.mjs";
1
+ import { getAppGkmConfig, isWorkspaceConfig, processConfig } from "./workspace-9IQIjwkQ.mjs";
2
2
  import { existsSync, readFileSync } from "node:fs";
3
3
  import { dirname, join, parse } from "node:path";
4
4
 
@@ -183,7 +183,29 @@ async function loadAppConfig(cwd = process.cwd()) {
183
183
  appRoot: join(workspaceRoot, app.path)
184
184
  };
185
185
  }
186
+ /**
187
+ * Load workspace info for any app (frontend or backend).
188
+ * Unlike loadAppConfig, this does NOT require the app to have a gkm config
189
+ * (routes, entry, etc.), making it suitable for gkm exec/test from frontend apps.
190
+ */
191
+ async function loadWorkspaceAppInfo(cwd = process.cwd()) {
192
+ const appName = getAppNameFromCwd(cwd);
193
+ if (!appName) throw new Error("Could not determine app name. Ensure package.json exists with a \"name\" field.");
194
+ const { config, workspaceRoot } = await loadRawConfig(cwd);
195
+ const loadedConfig = processConfig(config, workspaceRoot);
196
+ const app = loadedConfig.workspace.apps[appName];
197
+ if (!app) {
198
+ const availableApps = Object.keys(loadedConfig.workspace.apps).join(", ");
199
+ throw new Error(`App "${appName}" not found in workspace config. Available apps: ${availableApps}. Ensure the package.json name matches the app key in gkm.config.ts.`);
200
+ }
201
+ return {
202
+ appName,
203
+ app,
204
+ workspace: loadedConfig.workspace,
205
+ workspaceRoot
206
+ };
207
+ }
186
208
 
187
209
  //#endregion
188
- export { defineConfig, getAppNameFromCwd, loadAppConfig, loadConfig, loadWorkspaceConfig, parseModuleConfig };
189
- //# sourceMappingURL=config-DfCJ29PQ.mjs.map
210
+ export { defineConfig, getAppNameFromCwd, loadAppConfig, loadConfig, loadWorkspaceAppInfo, loadWorkspaceConfig, parseModuleConfig };
211
+ //# sourceMappingURL=config-DxASSNjr.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"config-DfCJ29PQ.mjs","names":["config: GkmConfig","configString: string","defaultAlias: string","cwd: string"],"sources":["../src/config.ts"],"sourcesContent":["import { existsSync, readFileSync } from 'node:fs';\nimport { dirname, join, parse } from 'node:path';\nimport type { GkmConfig } from './types.js';\nimport {\n\tgetAppGkmConfig,\n\tisWorkspaceConfig,\n\ttype LoadedConfig,\n\ttype NormalizedAppConfig,\n\ttype NormalizedWorkspace,\n\tprocessConfig,\n\ttype WorkspaceConfig,\n} from './workspace/index.js';\n\nexport type { GkmConfig } from './types.js';\nexport type { LoadedConfig, WorkspaceConfig } from './workspace/index.js';\nexport { defineWorkspace } from './workspace/index.js';\n/**\n * Define GKM configuration with full TypeScript support.\n * This is an identity function that provides type safety and autocomplete.\n *\n * @example\n * ```ts\n * // gkm.config.ts\n * import { defineConfig } from '@geekmidas/cli/config';\n *\n * export default defineConfig({\n * routes: './src/endpoints/**\\/*.ts',\n * envParser: './src/config/env',\n * logger: './src/config/logger',\n * telescope: true,\n * });\n * ```\n */\nexport function defineConfig(config: GkmConfig): GkmConfig {\n\treturn config;\n}\n\nexport interface ParsedModuleConfig {\n\tpath: string;\n\timportPattern: string;\n}\n\n/**\n * Parse a module config string into path and import pattern.\n *\n * @param configString - Config string in format \"./path/to/module\" or \"./path/to/module#exportName\"\n * @param defaultAlias - The default alias name to use if no export name specified\n * @returns Object with path and import pattern\n *\n * @example\n * parseModuleConfig('./src/config/env', 'envParser')\n * // { path: './src/config/env', importPattern: 'envParser' }\n *\n * parseModuleConfig('./src/config/env#envParser', 'envParser')\n * // { path: './src/config/env', importPattern: '{ envParser }' }\n *\n * parseModuleConfig('./src/config/env#myEnv', 'envParser')\n * // { path: './src/config/env', importPattern: '{ myEnv as envParser }' }\n */\nexport function parseModuleConfig(\n\tconfigString: string,\n\tdefaultAlias: string,\n): ParsedModuleConfig {\n\tconst parts = configString.split('#');\n\tconst path = parts[0] ?? configString;\n\tconst exportName = parts[1];\n\tconst importPattern = !exportName\n\t\t? defaultAlias\n\t\t: exportName === defaultAlias\n\t\t\t? `{ ${defaultAlias} }`\n\t\t\t: `{ ${exportName} as ${defaultAlias} }`;\n\n\treturn { path, importPattern };\n}\n\nexport interface ConfigDiscoveryResult {\n\tconfigPath: string;\n\tworkspaceRoot: string;\n}\n\n/**\n * Find and return the path to the config file.\n *\n * Resolution order:\n * 1. GKM_CONFIG_PATH env var (set by workspace dev command)\n * 2. Walk up directory tree from cwd\n */\nfunction findConfigPath(cwd: string): ConfigDiscoveryResult {\n\tconst files = ['gkm.config.json', 'gkm.config.ts', 'gkm.config.js'];\n\n\t// Check GKM_CONFIG_PATH env var first (set by workspace dev command)\n\tconst envConfigPath = process.env.GKM_CONFIG_PATH;\n\tif (envConfigPath && existsSync(envConfigPath)) {\n\t\treturn {\n\t\t\tconfigPath: envConfigPath,\n\t\t\tworkspaceRoot: dirname(envConfigPath),\n\t\t};\n\t}\n\n\t// Walk up directory tree to find config\n\tlet currentDir = cwd;\n\tconst { root } = parse(currentDir);\n\n\twhile (currentDir !== root) {\n\t\tfor (const file of files) {\n\t\t\tconst configPath = join(currentDir, file);\n\t\t\tif (existsSync(configPath)) {\n\t\t\t\treturn {\n\t\t\t\t\tconfigPath,\n\t\t\t\t\tworkspaceRoot: currentDir,\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\t\tcurrentDir = dirname(currentDir);\n\t}\n\n\tthrow new Error(\n\t\t'Configuration file not found. Please create gkm.config.json, gkm.config.ts, or gkm.config.js in the project root.',\n\t);\n}\n\n/**\n * Get app name from package.json in the given directory.\n * Handles scoped packages by extracting the name after the scope.\n *\n * @example\n * getAppNameFromCwd('/path/to/apps/api')\n * // package.json: { \"name\": \"@myorg/api\" }\n * // Returns: 'api'\n */\nexport function getAppNameFromCwd(cwd: string = process.cwd()): string | null {\n\tconst packageJsonPath = join(cwd, 'package.json');\n\n\tif (!existsSync(packageJsonPath)) {\n\t\treturn null;\n\t}\n\n\ttry {\n\t\tconst packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));\n\t\tconst name = packageJson.name as string | undefined;\n\n\t\tif (!name) {\n\t\t\treturn null;\n\t\t}\n\n\t\t// Handle scoped packages: @scope/name -> name\n\t\tif (name.startsWith('@') && name.includes('/')) {\n\t\t\treturn name.split('/')[1] ?? null;\n\t\t}\n\n\t\treturn name;\n\t} catch {\n\t\treturn null;\n\t}\n}\n\ninterface RawConfigResult {\n\tconfig: GkmConfig | WorkspaceConfig;\n\tworkspaceRoot: string;\n}\n\n/**\n * Load raw configuration from file.\n */\nasync function loadRawConfig(cwd: string): Promise<RawConfigResult> {\n\tconst { configPath, workspaceRoot } = findConfigPath(cwd);\n\n\ttry {\n\t\tconst config = await import(configPath);\n\t\treturn {\n\t\t\tconfig: config.default,\n\t\t\tworkspaceRoot,\n\t\t};\n\t} catch (error) {\n\t\tthrow new Error(`Failed to load config: ${(error as Error).message}`);\n\t}\n}\n\n/**\n * Load configuration file (single-app format).\n * For backwards compatibility with existing code.\n *\n * @deprecated Use loadWorkspaceConfig for new code\n */\nexport async function loadConfig(\n\tcwd: string = process.cwd(),\n): Promise<GkmConfig> {\n\tconst { config } = await loadRawConfig(cwd);\n\n\t// If it's a workspace config, throw an error\n\tif (isWorkspaceConfig(config)) {\n\t\tthrow new Error(\n\t\t\t'Workspace configuration detected. Use loadWorkspaceConfig() instead.',\n\t\t);\n\t}\n\n\treturn config;\n}\n\n/**\n * Load configuration file and process it as a workspace.\n * Works with both single-app and workspace configurations.\n *\n * Single-app configs are automatically wrapped as a workspace with one app.\n *\n * @example\n * ```ts\n * const { type, workspace } = await loadWorkspaceConfig();\n *\n * if (type === 'workspace') {\n * console.log('Multi-app workspace:', workspace.apps);\n * } else {\n * console.log('Single app wrapped as workspace');\n * }\n * ```\n */\nexport async function loadWorkspaceConfig(\n\tcwd: string = process.cwd(),\n): Promise<LoadedConfig> {\n\tconst { config, workspaceRoot } = await loadRawConfig(cwd);\n\treturn processConfig(config, workspaceRoot);\n}\n\nexport interface AppConfigResult {\n\tappName: string;\n\tapp: NormalizedAppConfig;\n\tgkmConfig: GkmConfig;\n\tworkspace: NormalizedWorkspace;\n\tworkspaceRoot: string;\n\tappRoot: string;\n}\n\n/**\n * Load app-specific configuration from workspace.\n * Uses the app name from package.json to find the correct app config.\n *\n * @example\n * ```ts\n * // From apps/api directory with package.json: { \"name\": \"@myorg/api\" }\n * const { app, workspace, workspaceRoot } = await loadAppConfig();\n * console.log(app.routes); // './src/endpoints/**\\/*.ts'\n * ```\n */\nexport async function loadAppConfig(\n\tcwd: string = process.cwd(),\n): Promise<AppConfigResult> {\n\tconst appName = getAppNameFromCwd(cwd);\n\n\tif (!appName) {\n\t\tthrow new Error(\n\t\t\t'Could not determine app name. Ensure package.json exists with a \"name\" field.',\n\t\t);\n\t}\n\n\tconst { config, workspaceRoot } = await loadRawConfig(cwd);\n\tconst loadedConfig = processConfig(config, workspaceRoot);\n\n\t// Find the app in workspace (apps is a Record<string, NormalizedAppConfig>)\n\tconst app = loadedConfig.workspace.apps[appName];\n\n\tif (!app) {\n\t\tconst availableApps = Object.keys(loadedConfig.workspace.apps).join(', ');\n\t\tthrow new Error(\n\t\t\t`App \"${appName}\" not found in workspace config. Available apps: ${availableApps}. ` +\n\t\t\t\t`Ensure the package.json name matches the app key in gkm.config.ts.`,\n\t\t);\n\t}\n\n\t// Get the app's GKM config using the helper\n\tconst gkmConfig = getAppGkmConfig(loadedConfig.workspace, appName);\n\n\tif (!gkmConfig) {\n\t\tthrow new Error(\n\t\t\t`App \"${appName}\" is not a backend app and cannot be run with gkm dev.`,\n\t\t);\n\t}\n\n\treturn {\n\t\tappName,\n\t\tapp,\n\t\tgkmConfig,\n\t\tworkspace: loadedConfig.workspace,\n\t\tworkspaceRoot,\n\t\tappRoot: join(workspaceRoot, app.path),\n\t};\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"config-DxASSNjr.mjs","names":["config: GkmConfig","configString: string","defaultAlias: string","cwd: string"],"sources":["../src/config.ts"],"sourcesContent":["import { existsSync, readFileSync } from 'node:fs';\nimport { dirname, join, parse } from 'node:path';\nimport type { GkmConfig } from './types.js';\nimport {\n\tgetAppGkmConfig,\n\tisWorkspaceConfig,\n\ttype LoadedConfig,\n\ttype NormalizedAppConfig,\n\ttype NormalizedWorkspace,\n\tprocessConfig,\n\ttype WorkspaceConfig,\n} from './workspace/index.js';\n\nexport type { GkmConfig } from './types.js';\nexport type { LoadedConfig, WorkspaceConfig } from './workspace/index.js';\nexport { defineWorkspace } from './workspace/index.js';\n/**\n * Define GKM configuration with full TypeScript support.\n * This is an identity function that provides type safety and autocomplete.\n *\n * @example\n * ```ts\n * // gkm.config.ts\n * import { defineConfig } from '@geekmidas/cli/config';\n *\n * export default defineConfig({\n * routes: './src/endpoints/**\\/*.ts',\n * envParser: './src/config/env',\n * logger: './src/config/logger',\n * telescope: true,\n * });\n * ```\n */\nexport function defineConfig(config: GkmConfig): GkmConfig {\n\treturn config;\n}\n\nexport interface ParsedModuleConfig {\n\tpath: string;\n\timportPattern: string;\n}\n\n/**\n * Parse a module config string into path and import pattern.\n *\n * @param configString - Config string in format \"./path/to/module\" or \"./path/to/module#exportName\"\n * @param defaultAlias - The default alias name to use if no export name specified\n * @returns Object with path and import pattern\n *\n * @example\n * parseModuleConfig('./src/config/env', 'envParser')\n * // { path: './src/config/env', importPattern: 'envParser' }\n *\n * parseModuleConfig('./src/config/env#envParser', 'envParser')\n * // { path: './src/config/env', importPattern: '{ envParser }' }\n *\n * parseModuleConfig('./src/config/env#myEnv', 'envParser')\n * // { path: './src/config/env', importPattern: '{ myEnv as envParser }' }\n */\nexport function parseModuleConfig(\n\tconfigString: string,\n\tdefaultAlias: string,\n): ParsedModuleConfig {\n\tconst parts = configString.split('#');\n\tconst path = parts[0] ?? configString;\n\tconst exportName = parts[1];\n\tconst importPattern = !exportName\n\t\t? defaultAlias\n\t\t: exportName === defaultAlias\n\t\t\t? `{ ${defaultAlias} }`\n\t\t\t: `{ ${exportName} as ${defaultAlias} }`;\n\n\treturn { path, importPattern };\n}\n\nexport interface ConfigDiscoveryResult {\n\tconfigPath: string;\n\tworkspaceRoot: string;\n}\n\n/**\n * Find and return the path to the config file.\n *\n * Resolution order:\n * 1. GKM_CONFIG_PATH env var (set by workspace dev command)\n * 2. Walk up directory tree from cwd\n */\nfunction findConfigPath(cwd: string): ConfigDiscoveryResult {\n\tconst files = ['gkm.config.json', 'gkm.config.ts', 'gkm.config.js'];\n\n\t// Check GKM_CONFIG_PATH env var first (set by workspace dev command)\n\tconst envConfigPath = process.env.GKM_CONFIG_PATH;\n\tif (envConfigPath && existsSync(envConfigPath)) {\n\t\treturn {\n\t\t\tconfigPath: envConfigPath,\n\t\t\tworkspaceRoot: dirname(envConfigPath),\n\t\t};\n\t}\n\n\t// Walk up directory tree to find config\n\tlet currentDir = cwd;\n\tconst { root } = parse(currentDir);\n\n\twhile (currentDir !== root) {\n\t\tfor (const file of files) {\n\t\t\tconst configPath = join(currentDir, file);\n\t\t\tif (existsSync(configPath)) {\n\t\t\t\treturn {\n\t\t\t\t\tconfigPath,\n\t\t\t\t\tworkspaceRoot: currentDir,\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\t\tcurrentDir = dirname(currentDir);\n\t}\n\n\tthrow new Error(\n\t\t'Configuration file not found. Please create gkm.config.json, gkm.config.ts, or gkm.config.js in the project root.',\n\t);\n}\n\n/**\n * Get app name from package.json in the given directory.\n * Handles scoped packages by extracting the name after the scope.\n *\n * @example\n * getAppNameFromCwd('/path/to/apps/api')\n * // package.json: { \"name\": \"@myorg/api\" }\n * // Returns: 'api'\n */\nexport function getAppNameFromCwd(cwd: string = process.cwd()): string | null {\n\tconst packageJsonPath = join(cwd, 'package.json');\n\n\tif (!existsSync(packageJsonPath)) {\n\t\treturn null;\n\t}\n\n\ttry {\n\t\tconst packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));\n\t\tconst name = packageJson.name as string | undefined;\n\n\t\tif (!name) {\n\t\t\treturn null;\n\t\t}\n\n\t\t// Handle scoped packages: @scope/name -> name\n\t\tif (name.startsWith('@') && name.includes('/')) {\n\t\t\treturn name.split('/')[1] ?? null;\n\t\t}\n\n\t\treturn name;\n\t} catch {\n\t\treturn null;\n\t}\n}\n\ninterface RawConfigResult {\n\tconfig: GkmConfig | WorkspaceConfig;\n\tworkspaceRoot: string;\n}\n\n/**\n * Load raw configuration from file.\n */\nasync function loadRawConfig(cwd: string): Promise<RawConfigResult> {\n\tconst { configPath, workspaceRoot } = findConfigPath(cwd);\n\n\ttry {\n\t\tconst config = await import(configPath);\n\t\treturn {\n\t\t\tconfig: config.default,\n\t\t\tworkspaceRoot,\n\t\t};\n\t} catch (error) {\n\t\tthrow new Error(`Failed to load config: ${(error as Error).message}`);\n\t}\n}\n\n/**\n * Load configuration file (single-app format).\n * For backwards compatibility with existing code.\n *\n * @deprecated Use loadWorkspaceConfig for new code\n */\nexport async function loadConfig(\n\tcwd: string = process.cwd(),\n): Promise<GkmConfig> {\n\tconst { config } = await loadRawConfig(cwd);\n\n\t// If it's a workspace config, throw an error\n\tif (isWorkspaceConfig(config)) {\n\t\tthrow new Error(\n\t\t\t'Workspace configuration detected. Use loadWorkspaceConfig() instead.',\n\t\t);\n\t}\n\n\treturn config;\n}\n\n/**\n * Load configuration file and process it as a workspace.\n * Works with both single-app and workspace configurations.\n *\n * Single-app configs are automatically wrapped as a workspace with one app.\n *\n * @example\n * ```ts\n * const { type, workspace } = await loadWorkspaceConfig();\n *\n * if (type === 'workspace') {\n * console.log('Multi-app workspace:', workspace.apps);\n * } else {\n * console.log('Single app wrapped as workspace');\n * }\n * ```\n */\nexport async function loadWorkspaceConfig(\n\tcwd: string = process.cwd(),\n): Promise<LoadedConfig> {\n\tconst { config, workspaceRoot } = await loadRawConfig(cwd);\n\treturn processConfig(config, workspaceRoot);\n}\n\nexport interface AppConfigResult {\n\tappName: string;\n\tapp: NormalizedAppConfig;\n\tgkmConfig: GkmConfig;\n\tworkspace: NormalizedWorkspace;\n\tworkspaceRoot: string;\n\tappRoot: string;\n}\n\n/**\n * Load app-specific configuration from workspace.\n * Uses the app name from package.json to find the correct app config.\n *\n * @example\n * ```ts\n * // From apps/api directory with package.json: { \"name\": \"@myorg/api\" }\n * const { app, workspace, workspaceRoot } = await loadAppConfig();\n * console.log(app.routes); // './src/endpoints/**\\/*.ts'\n * ```\n */\nexport async function loadAppConfig(\n\tcwd: string = process.cwd(),\n): Promise<AppConfigResult> {\n\tconst appName = getAppNameFromCwd(cwd);\n\n\tif (!appName) {\n\t\tthrow new Error(\n\t\t\t'Could not determine app name. Ensure package.json exists with a \"name\" field.',\n\t\t);\n\t}\n\n\tconst { config, workspaceRoot } = await loadRawConfig(cwd);\n\tconst loadedConfig = processConfig(config, workspaceRoot);\n\n\t// Find the app in workspace (apps is a Record<string, NormalizedAppConfig>)\n\tconst app = loadedConfig.workspace.apps[appName];\n\n\tif (!app) {\n\t\tconst availableApps = Object.keys(loadedConfig.workspace.apps).join(', ');\n\t\tthrow new Error(\n\t\t\t`App \"${appName}\" not found in workspace config. Available apps: ${availableApps}. ` +\n\t\t\t\t`Ensure the package.json name matches the app key in gkm.config.ts.`,\n\t\t);\n\t}\n\n\t// Get the app's GKM config using the helper\n\tconst gkmConfig = getAppGkmConfig(loadedConfig.workspace, appName);\n\n\tif (!gkmConfig) {\n\t\tthrow new Error(\n\t\t\t`App \"${appName}\" is not a backend app and cannot be run with gkm dev.`,\n\t\t);\n\t}\n\n\treturn {\n\t\tappName,\n\t\tapp,\n\t\tgkmConfig,\n\t\tworkspace: loadedConfig.workspace,\n\t\tworkspaceRoot,\n\t\tappRoot: join(workspaceRoot, app.path),\n\t};\n}\n\nexport interface WorkspaceAppInfo {\n\tappName: string;\n\tapp: NormalizedAppConfig;\n\tworkspace: NormalizedWorkspace;\n\tworkspaceRoot: string;\n}\n\n/**\n * Load workspace info for any app (frontend or backend).\n * Unlike loadAppConfig, this does NOT require the app to have a gkm config\n * (routes, entry, etc.), making it suitable for gkm exec/test from frontend apps.\n */\nexport async function loadWorkspaceAppInfo(\n\tcwd: string = process.cwd(),\n): Promise<WorkspaceAppInfo> {\n\tconst appName = getAppNameFromCwd(cwd);\n\n\tif (!appName) {\n\t\tthrow new Error(\n\t\t\t'Could not determine app name. Ensure package.json exists with a \"name\" field.',\n\t\t);\n\t}\n\n\tconst { config, workspaceRoot } = await loadRawConfig(cwd);\n\tconst loadedConfig = processConfig(config, workspaceRoot);\n\n\tconst app = loadedConfig.workspace.apps[appName];\n\n\tif (!app) {\n\t\tconst availableApps = Object.keys(loadedConfig.workspace.apps).join(', ');\n\t\tthrow new Error(\n\t\t\t`App \"${appName}\" not found in workspace config. Available apps: ${availableApps}. ` +\n\t\t\t\t`Ensure the package.json name matches the app key in gkm.config.ts.`,\n\t\t);\n\t}\n\n\treturn {\n\t\tappName,\n\t\tapp,\n\t\tworkspace: loadedConfig.workspace,\n\t\tworkspaceRoot,\n\t};\n}\n"],"mappings":"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"}
package/dist/config.cjs CHANGED
@@ -1,10 +1,11 @@
1
- const require_workspace = require('./workspace-2Do2YcGZ.cjs');
2
- const require_config = require('./config-ZQM1vBoz.cjs');
1
+ const require_workspace = require('./workspace-D2ocAlpl.cjs');
2
+ const require_config = require('./config-6JHOwLCx.cjs');
3
3
 
4
4
  exports.defineConfig = require_config.defineConfig;
5
5
  exports.defineWorkspace = require_workspace.defineWorkspace;
6
6
  exports.getAppNameFromCwd = require_config.getAppNameFromCwd;
7
7
  exports.loadAppConfig = require_config.loadAppConfig;
8
8
  exports.loadConfig = require_config.loadConfig;
9
+ exports.loadWorkspaceAppInfo = require_config.loadWorkspaceAppInfo;
9
10
  exports.loadWorkspaceConfig = require_config.loadWorkspaceConfig;
10
11
  exports.parseModuleConfig = require_config.parseModuleConfig;
package/dist/config.d.cts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { GkmConfig } from "./types-l53qUmGt.cjs";
2
- import { LoadedConfig, NormalizedAppConfig, NormalizedWorkspace, WorkspaceConfig, defineWorkspace } from "./index-B58qjyBd.cjs";
2
+ import { LoadedConfig, NormalizedAppConfig, NormalizedWorkspace, WorkspaceConfig, defineWorkspace } from "./index-Cyk2rTyj.cjs";
3
3
 
4
4
  //#region src/config.d.ts
5
5
 
@@ -102,7 +102,19 @@ interface AppConfigResult {
102
102
  * ```
103
103
  */
104
104
  declare function loadAppConfig(cwd?: string): Promise<AppConfigResult>;
105
+ interface WorkspaceAppInfo {
106
+ appName: string;
107
+ app: NormalizedAppConfig;
108
+ workspace: NormalizedWorkspace;
109
+ workspaceRoot: string;
110
+ }
111
+ /**
112
+ * Load workspace info for any app (frontend or backend).
113
+ * Unlike loadAppConfig, this does NOT require the app to have a gkm config
114
+ * (routes, entry, etc.), making it suitable for gkm exec/test from frontend apps.
115
+ */
116
+ declare function loadWorkspaceAppInfo(cwd?: string): Promise<WorkspaceAppInfo>;
105
117
  //# sourceMappingURL=config.d.ts.map
106
118
  //#endregion
107
- export { AppConfigResult, ConfigDiscoveryResult, GkmConfig, LoadedConfig, ParsedModuleConfig, WorkspaceConfig, defineConfig, defineWorkspace, getAppNameFromCwd, loadAppConfig, loadConfig, loadWorkspaceConfig, parseModuleConfig };
119
+ export { AppConfigResult, ConfigDiscoveryResult, GkmConfig, LoadedConfig, ParsedModuleConfig, WorkspaceAppInfo, WorkspaceConfig, defineConfig, defineWorkspace, getAppNameFromCwd, loadAppConfig, loadConfig, loadWorkspaceAppInfo, loadWorkspaceConfig, parseModuleConfig };
108
120
  //# sourceMappingURL=config.d.cts.map
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.cts","names":[],"sources":["../src/config.ts"],"sourcesContent":[],"mappings":";;;;;;;;AAiC0D;AAI1D;AAsBA;AAgBA;AAuDA;AAsDA;;;;AAEU;AA8BV;;;;AAEU,iBAzLM,YAAA,CAyLN,MAAA,EAzL2B,SAyL3B,CAAA,EAzLuC,SAyLvC;AAKO,UA1LA,kBAAA,CA0Le;EAAA,IAAA,EAAA,MAAA;EAAA,aAE1B,EAAA,MAAA;;;AAEyB;AAgB/B;;;;AAEU;;;;;;;;;;;iBA1LM,iBAAA,8CAGb;UAac,qBAAA;;;;;;;;;;;;;iBAuDD,iBAAA;;;;;;;iBAsDM,UAAA,gBAEnB,QAAQ;;;;;;;;;;;;;;;;;;iBA8BW,mBAAA,gBAEnB,QAAQ;UAKM,eAAA;;OAEX;aACM;aACA;;;;;;;;;;;;;;;iBAgBU,aAAA,gBAEnB,QAAQ"}
1
+ {"version":3,"file":"config.d.cts","names":[],"sources":["../src/config.ts"],"sourcesContent":[],"mappings":";;;;;;;;AAiC0D;AAI1D;AAsBA;AAgBA;AAuDA;AAsDA;;;;AAEU;AA8BV;;;;AAEU,iBAzLM,YAAA,CAyLN,MAAA,EAzL2B,SAyL3B,CAAA,EAzLuC,SAyLvC;AAKO,UA1LA,kBAAA,CA0Le;EAAA,IAAA,EAAA,MAAA;EAAA,aAE1B,EAAA,MAAA;;;AAEyB;AAgB/B;;;;AAEU;AA0CV;;;;AAG+B;AAS/B;;;;AAEU;iBAlPM,iBAAA,8CAGb;UAac,qBAAA;;;;;;;;;;;;;iBAuDD,iBAAA;;;;;;;iBAsDM,UAAA,gBAEnB,QAAQ;;;;;;;;;;;;;;;;;;iBA8BW,mBAAA,gBAEnB,QAAQ;UAKM,eAAA;;OAEX;aACM;aACA;;;;;;;;;;;;;;;iBAgBU,aAAA,gBAEnB,QAAQ;UA0CM,gBAAA;;OAEX;aACM;;;;;;;;iBASU,oBAAA,gBAEnB,QAAQ"}
package/dist/config.d.mts CHANGED
@@ -1,5 +1,5 @@
1
- import { GkmConfig } from "./types-B9UZ7fOG.mjs";
2
- import { LoadedConfig, NormalizedAppConfig, NormalizedWorkspace, WorkspaceConfig, defineWorkspace } from "./index-C0SpUT9Y.mjs";
1
+ import { GkmConfig } from "./types-CZg5iUgD.mjs";
2
+ import { LoadedConfig, NormalizedAppConfig, NormalizedWorkspace, WorkspaceConfig, defineWorkspace } from "./index-C-KxSGGK.mjs";
3
3
 
4
4
  //#region src/config.d.ts
5
5
 
@@ -102,7 +102,19 @@ interface AppConfigResult {
102
102
  * ```
103
103
  */
104
104
  declare function loadAppConfig(cwd?: string): Promise<AppConfigResult>;
105
+ interface WorkspaceAppInfo {
106
+ appName: string;
107
+ app: NormalizedAppConfig;
108
+ workspace: NormalizedWorkspace;
109
+ workspaceRoot: string;
110
+ }
111
+ /**
112
+ * Load workspace info for any app (frontend or backend).
113
+ * Unlike loadAppConfig, this does NOT require the app to have a gkm config
114
+ * (routes, entry, etc.), making it suitable for gkm exec/test from frontend apps.
115
+ */
116
+ declare function loadWorkspaceAppInfo(cwd?: string): Promise<WorkspaceAppInfo>;
105
117
  //# sourceMappingURL=config.d.ts.map
106
118
  //#endregion
107
- export { AppConfigResult, ConfigDiscoveryResult, GkmConfig, LoadedConfig, ParsedModuleConfig, WorkspaceConfig, defineConfig, defineWorkspace, getAppNameFromCwd, loadAppConfig, loadConfig, loadWorkspaceConfig, parseModuleConfig };
119
+ export { AppConfigResult, ConfigDiscoveryResult, GkmConfig, LoadedConfig, ParsedModuleConfig, WorkspaceAppInfo, WorkspaceConfig, defineConfig, defineWorkspace, getAppNameFromCwd, loadAppConfig, loadConfig, loadWorkspaceAppInfo, loadWorkspaceConfig, parseModuleConfig };
108
120
  //# sourceMappingURL=config.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.mts","names":[],"sources":["../src/config.ts"],"sourcesContent":[],"mappings":";;;;;;;;AAiC0D;AAI1D;AAsBA;AAgBA;AAuDA;AAsDA;;;;AAEU;AA8BV;;;;AAEU,iBAzLM,YAAA,CAyLN,MAAA,EAzL2B,SAyL3B,CAAA,EAzLuC,SAyLvC;AAKO,UA1LA,kBAAA,CA0Le;EAAA,IAAA,EAAA,MAAA;EAAA,aAE1B,EAAA,MAAA;;;AAEyB;AAgB/B;;;;AAEU;;;;;;;;;;;iBA1LM,iBAAA,8CAGb;UAac,qBAAA;;;;;;;;;;;;;iBAuDD,iBAAA;;;;;;;iBAsDM,UAAA,gBAEnB,QAAQ;;;;;;;;;;;;;;;;;;iBA8BW,mBAAA,gBAEnB,QAAQ;UAKM,eAAA;;OAEX;aACM;aACA;;;;;;;;;;;;;;;iBAgBU,aAAA,gBAEnB,QAAQ"}
1
+ {"version":3,"file":"config.d.mts","names":[],"sources":["../src/config.ts"],"sourcesContent":[],"mappings":";;;;;;;;AAiC0D;AAI1D;AAsBA;AAgBA;AAuDA;AAsDA;;;;AAEU;AA8BV;;;;AAEU,iBAzLM,YAAA,CAyLN,MAAA,EAzL2B,SAyL3B,CAAA,EAzLuC,SAyLvC;AAKO,UA1LA,kBAAA,CA0Le;EAAA,IAAA,EAAA,MAAA;EAAA,aAE1B,EAAA,MAAA;;;AAEyB;AAgB/B;;;;AAEU;AA0CV;;;;AAG+B;AAS/B;;;;AAEU;iBAlPM,iBAAA,8CAGb;UAac,qBAAA;;;;;;;;;;;;;iBAuDD,iBAAA;;;;;;;iBAsDM,UAAA,gBAEnB,QAAQ;;;;;;;;;;;;;;;;;;iBA8BW,mBAAA,gBAEnB,QAAQ;UAKM,eAAA;;OAEX;aACM;aACA;;;;;;;;;;;;;;;iBAgBU,aAAA,gBAEnB,QAAQ;UA0CM,gBAAA;;OAEX;aACM;;;;;;;;iBASU,oBAAA,gBAEnB,QAAQ"}
package/dist/config.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { defineWorkspace } from "./workspace-BW2iU37P.mjs";
2
- import { defineConfig, getAppNameFromCwd, loadAppConfig, loadConfig, loadWorkspaceConfig, parseModuleConfig } from "./config-DfCJ29PQ.mjs";
1
+ import { defineWorkspace } from "./workspace-9IQIjwkQ.mjs";
2
+ import { defineConfig, getAppNameFromCwd, loadAppConfig, loadConfig, loadWorkspaceAppInfo, loadWorkspaceConfig, parseModuleConfig } from "./config-DxASSNjr.mjs";
3
3
 
4
- export { defineConfig, defineWorkspace, getAppNameFromCwd, loadAppConfig, loadConfig, loadWorkspaceConfig, parseModuleConfig };
4
+ export { defineConfig, defineWorkspace, getAppNameFromCwd, loadAppConfig, loadConfig, loadWorkspaceAppInfo, loadWorkspaceConfig, parseModuleConfig };