@launch77/cli 1.4.4 → 1.5.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 (141) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/dist/cli.js +1 -4
  3. package/dist/cli.js.map +1 -1
  4. package/dist/infrastructure/package-resolver.d.ts +12 -2
  5. package/dist/infrastructure/package-resolver.d.ts.map +1 -1
  6. package/dist/infrastructure/package-resolver.js +29 -2
  7. package/dist/infrastructure/package-resolver.js.map +1 -1
  8. package/dist/infrastructure/package-resolver.test.js +3 -3
  9. package/dist/infrastructure/package-resolver.test.js.map +1 -1
  10. package/dist/infrastructure/template.d.ts +0 -1
  11. package/dist/infrastructure/template.d.ts.map +1 -1
  12. package/dist/infrastructure/template.js.map +1 -1
  13. package/dist/modules/app/commands/create-app.d.ts.map +1 -1
  14. package/dist/modules/app/commands/create-app.js +8 -13
  15. package/dist/modules/app/commands/create-app.js.map +1 -1
  16. package/dist/modules/app/commands/delete-app.js +1 -1
  17. package/dist/modules/app/commands/delete-app.js.map +1 -1
  18. package/dist/modules/app/commands/validate-manifest.d.ts.map +1 -1
  19. package/dist/modules/app/commands/validate-manifest.js +0 -1
  20. package/dist/modules/app/commands/validate-manifest.js.map +1 -1
  21. package/dist/modules/app/index.d.ts +1 -2
  22. package/dist/modules/app/index.d.ts.map +1 -1
  23. package/dist/modules/app/index.js +0 -1
  24. package/dist/modules/app/index.js.map +1 -1
  25. package/dist/modules/app/lib/app-template-resolver.d.ts.map +1 -1
  26. package/dist/modules/app/lib/app-template-resolver.js +15 -0
  27. package/dist/modules/app/lib/app-template-resolver.js.map +1 -1
  28. package/dist/modules/app/lib/manifest-schema.d.ts +0 -7
  29. package/dist/modules/app/lib/manifest-schema.d.ts.map +1 -1
  30. package/dist/modules/app/lib/manifest-schema.js +0 -2
  31. package/dist/modules/app/lib/manifest-schema.js.map +1 -1
  32. package/dist/modules/app/services/app-svc.d.ts.map +1 -1
  33. package/dist/modules/app/services/app-svc.js +36 -6
  34. package/dist/modules/app/services/app-svc.js.map +1 -1
  35. package/dist/modules/app/services/manifest-svc.d.ts +2 -2
  36. package/dist/modules/app/services/manifest-svc.d.ts.map +1 -1
  37. package/dist/modules/app/services/manifest-svc.js +9 -50
  38. package/dist/modules/app/services/manifest-svc.js.map +1 -1
  39. package/dist/modules/app/types/app-types.d.ts +2 -5
  40. package/dist/modules/app/types/app-types.d.ts.map +1 -1
  41. package/dist/modules/app/types/app-types.js +1 -4
  42. package/dist/modules/app/types/app-types.js.map +1 -1
  43. package/dist/modules/catalog/commands/scan.js +3 -3
  44. package/dist/modules/catalog/commands/scan.js.map +1 -1
  45. package/dist/modules/catalog/services/catalog-svc.d.ts +2 -2
  46. package/dist/modules/catalog/services/catalog-svc.d.ts.map +1 -1
  47. package/dist/modules/catalog/services/catalog-svc.js +2 -2
  48. package/dist/modules/catalog/services/catalog-svc.js.map +1 -1
  49. package/dist/modules/deploy/commands/deploy-init-action.d.ts.map +1 -1
  50. package/dist/modules/deploy/commands/deploy-init-action.js +21 -20
  51. package/dist/modules/deploy/commands/deploy-init-action.js.map +1 -1
  52. package/dist/modules/deploy/commands/deploy-logs-action.d.ts.map +1 -1
  53. package/dist/modules/deploy/commands/deploy-logs-action.js +16 -13
  54. package/dist/modules/deploy/commands/deploy-logs-action.js.map +1 -1
  55. package/dist/modules/deploy/commands/deploy-status-action.d.ts.map +1 -1
  56. package/dist/modules/deploy/commands/deploy-status-action.js +16 -13
  57. package/dist/modules/deploy/commands/deploy-status-action.js.map +1 -1
  58. package/dist/modules/plugin/lib/plugin-resolver.d.ts.map +1 -1
  59. package/dist/modules/plugin/lib/plugin-resolver.js +13 -2
  60. package/dist/modules/plugin/lib/plugin-resolver.js.map +1 -1
  61. package/dist/modules/plugin/lib/plugin-resolver.test.js +2 -0
  62. package/dist/modules/plugin/lib/plugin-resolver.test.js.map +1 -1
  63. package/dist/modules/plugin/services/plugin-svc.d.ts.map +1 -1
  64. package/dist/modules/plugin/services/plugin-svc.js +10 -2
  65. package/dist/modules/plugin/services/plugin-svc.js.map +1 -1
  66. package/dist/modules/plugin/services/plugin-svc.test.js +12 -6
  67. package/dist/modules/plugin/services/plugin-svc.test.js.map +1 -1
  68. package/dist/templates/plugin/package.json.hbs +1 -1
  69. package/dist/templates/plugin/plugin.json.hbs +1 -3
  70. package/dist/utils/validation.d.ts +3 -7
  71. package/dist/utils/validation.d.ts.map +1 -1
  72. package/dist/utils/validation.js +7 -31
  73. package/dist/utils/validation.js.map +1 -1
  74. package/package.json +1 -1
  75. package/src/cli.ts +1 -4
  76. package/src/infrastructure/package-resolver.test.ts +3 -3
  77. package/src/infrastructure/package-resolver.ts +31 -2
  78. package/src/infrastructure/template.ts +0 -1
  79. package/src/modules/app/commands/create-app.ts +8 -16
  80. package/src/modules/app/commands/delete-app.ts +1 -1
  81. package/src/modules/app/commands/validate-manifest.ts +0 -1
  82. package/src/modules/app/index.ts +1 -2
  83. package/src/modules/app/lib/app-template-resolver.ts +16 -0
  84. package/src/modules/app/lib/manifest-schema.ts +0 -5
  85. package/src/modules/app/services/app-svc.ts +46 -7
  86. package/src/modules/app/services/manifest-svc.ts +8 -57
  87. package/src/modules/app/types/app-types.ts +2 -9
  88. package/src/modules/catalog/commands/scan.ts +3 -3
  89. package/src/modules/catalog/services/catalog-svc.ts +4 -4
  90. package/src/modules/deploy/commands/deploy-init-action.ts +23 -20
  91. package/src/modules/deploy/commands/deploy-logs-action.ts +19 -13
  92. package/src/modules/deploy/commands/deploy-status-action.ts +19 -13
  93. package/src/modules/plugin/lib/plugin-resolver.test.ts +2 -0
  94. package/src/modules/plugin/lib/plugin-resolver.ts +13 -2
  95. package/src/modules/plugin/services/plugin-svc.test.ts +12 -6
  96. package/src/modules/plugin/services/plugin-svc.ts +12 -3
  97. package/src/utils/validation.ts +10 -38
  98. package/templates/plugin/package.json.hbs +1 -1
  99. package/templates/plugin/plugin.json.hbs +1 -3
  100. package/dist/modules/app/commands/generate-manifest.d.ts +0 -3
  101. package/dist/modules/app/commands/generate-manifest.d.ts.map +0 -1
  102. package/dist/modules/app/commands/generate-manifest.js +0 -62
  103. package/dist/modules/app/commands/generate-manifest.js.map +0 -1
  104. package/dist/modules/startup/commands/create-startup.d.ts +0 -3
  105. package/dist/modules/startup/commands/create-startup.d.ts.map +0 -1
  106. package/dist/modules/startup/commands/create-startup.js +0 -43
  107. package/dist/modules/startup/commands/create-startup.js.map +0 -1
  108. package/dist/modules/startup/errors/startup-errors.d.ts +0 -13
  109. package/dist/modules/startup/errors/startup-errors.d.ts.map +0 -1
  110. package/dist/modules/startup/errors/startup-errors.js +0 -25
  111. package/dist/modules/startup/errors/startup-errors.js.map +0 -1
  112. package/dist/modules/startup/index.d.ts +0 -5
  113. package/dist/modules/startup/index.d.ts.map +0 -1
  114. package/dist/modules/startup/index.js +0 -7
  115. package/dist/modules/startup/index.js.map +0 -1
  116. package/dist/modules/startup/services/startup-service.d.ts +0 -7
  117. package/dist/modules/startup/services/startup-service.d.ts.map +0 -1
  118. package/dist/modules/startup/services/startup-service.js +0 -43
  119. package/dist/modules/startup/services/startup-service.js.map +0 -1
  120. package/dist/modules/startup/types/startup-types.d.ts +0 -8
  121. package/dist/modules/startup/types/startup-types.d.ts.map +0 -1
  122. package/dist/modules/startup/types/startup-types.js +0 -2
  123. package/dist/modules/startup/types/startup-types.js.map +0 -1
  124. package/dist/modules/startup/utils/startup-validators.d.ts +0 -8
  125. package/dist/modules/startup/utils/startup-validators.d.ts.map +0 -1
  126. package/dist/modules/startup/utils/startup-validators.js +0 -17
  127. package/dist/modules/startup/utils/startup-validators.js.map +0 -1
  128. package/dist/templates/startup/apps/.gitkeep +0 -8
  129. package/dist/utils/monorepo.d.ts +0 -19
  130. package/dist/utils/monorepo.d.ts.map +0 -1
  131. package/dist/utils/monorepo.js +0 -100
  132. package/dist/utils/monorepo.js.map +0 -1
  133. package/src/modules/app/commands/generate-manifest.ts +0 -75
  134. package/src/modules/startup/commands/create-startup.ts +0 -53
  135. package/src/modules/startup/errors/startup-errors.ts +0 -23
  136. package/src/modules/startup/index.ts +0 -11
  137. package/src/modules/startup/services/startup-service.ts +0 -57
  138. package/src/modules/startup/types/startup-types.ts +0 -8
  139. package/src/modules/startup/utils/startup-validators.ts +0 -19
  140. package/src/utils/monorepo.ts +0 -137
  141. package/templates/startup/apps/.gitkeep +0 -8
@@ -17,6 +17,8 @@ export interface PackageResolution {
17
17
  localPath?: string
18
18
  /** The npm package name if source is 'npm' */
19
19
  npmPackage?: string
20
+ /** The version from package.json (required for local packages, undefined for npm until installed) */
21
+ version?: string
20
22
  }
21
23
 
22
24
  /**
@@ -127,6 +129,30 @@ export abstract class PackageResolver {
127
129
  return `${this.getPackagePrefix()}${trimmedName}`
128
130
  }
129
131
 
132
+ /**
133
+ * Read version from package.json
134
+ * @param packagePath - The path to the package directory
135
+ * @returns The version string from package.json
136
+ * @throws If package.json doesn't exist, can't be read, or is missing the version field
137
+ */
138
+ private async readVersion(packagePath: string): Promise<string> {
139
+ const packageJsonPath = path.join(packagePath, 'package.json')
140
+ try {
141
+ const packageJson = await fs.readJson(packageJsonPath)
142
+ if (!packageJson.version) {
143
+ throw new Error(`Invalid package structure: package.json at ${packagePath} is missing required version field. ` + `All Launch77 packages must include a valid package.json with a version field.`)
144
+ }
145
+ return packageJson.version
146
+ } catch (error) {
147
+ // Re-throw our own error messages
148
+ if (error instanceof Error && error.message.includes('Invalid package structure')) {
149
+ throw error
150
+ }
151
+ // File not found or invalid JSON
152
+ throw new Error(`Invalid package structure: package.json not found or invalid at ${packagePath}. ` + `All Launch77 packages must include a valid package.json with a version field.`)
153
+ }
154
+ }
155
+
130
156
  /**
131
157
  * Resolve package location from name
132
158
  *
@@ -134,15 +160,16 @@ export abstract class PackageResolver {
134
160
  * 1. Check local workspace directory (configured by getFolderName())
135
161
  * 2. Verify local package is valid (using verify())
136
162
  * 3. Fall back to npm package name (with configured prefix)
163
+ * 4. Read version from package.json (if available)
137
164
  *
138
165
  * @param name - The package name to resolve
139
166
  * @param workspaceRoot - The workspace root directory
140
- * @returns PackageResolution with source and resolved location
167
+ * @returns PackageResolution with source, resolved location, and version
141
168
  *
142
169
  * @example
143
170
  * // Local package found
144
171
  * await resolveLocation('my-package', '/workspace')
145
- * // { source: 'local', resolvedName: 'my-package', localPath: '/workspace/plugins/my-package' }
172
+ * // { source: 'local', resolvedName: 'my-package', localPath: '/workspace/plugins/my-package', version: '1.0.0' }
146
173
  *
147
174
  * // Not found locally, resolve to npm
148
175
  * await resolveLocation('release', '/workspace')
@@ -174,10 +201,12 @@ export abstract class PackageResolver {
174
201
  const isValid = await this.verify(localPath)
175
202
 
176
203
  if (isValid) {
204
+ const version = await this.readVersion(localPath)
177
205
  return {
178
206
  source: 'local',
179
207
  resolvedName: trimmedName,
180
208
  localPath,
209
+ version,
181
210
  }
182
211
  }
183
212
  }
@@ -6,7 +6,6 @@ import Handlebars from 'handlebars'
6
6
 
7
7
  export interface TemplateContext {
8
8
  appName: string
9
- startupName?: string
10
9
  port?: string
11
10
  [key: string]: string | number | boolean | undefined
12
11
  }
@@ -7,27 +7,19 @@ import ora from 'ora'
7
7
 
8
8
  import { detectLaunch77Context } from '@launch77/plugin-runtime'
9
9
  import { AppService } from '../services/app-svc.js'
10
- import { APP_TYPES, APP_TYPES_LIST } from '../types/app-types.js'
11
-
12
- import type { AppType } from '../types/app-types.js'
13
10
 
14
11
  export function createAppCommand(): Command {
15
12
  const command = new Command('app:create')
16
- .argument('<type>', 'App type (api, webapp, marketing-site)')
13
+ .argument('<template>', 'App template name (e.g., webapp, api, marketing-site)')
17
14
  .argument('<app-name>', 'Name of the application')
18
- .description('Create a new application')
19
- .option('-p, --port <port>', 'Default port for the app')
20
- .action(async (type: string, appName: string, options) => {
15
+ .description('Create a new application from a template')
16
+ .option('-p, --port <port>', 'Default port for the app (default: 3000)')
17
+ .action(async (template: string, appName: string, options) => {
21
18
  try {
22
- // Validate app type
23
- if (!APP_TYPES.includes(type as AppType)) {
24
- throw new Error(`Invalid app type: ${type}\n\nSupported types: ${APP_TYPES_LIST}`)
25
- }
26
-
27
- console.log(chalk.blue(`\n🚀 Creating ${type} app: ${appName}\n`))
19
+ console.log(chalk.blue(`\n🚀 Creating app: ${appName} from template: ${template}\n`))
28
20
 
29
- // Set default port based on app type
30
- const port = options.port || (type === 'marketing-site' || type === 'webapp' ? '3000' : '4000')
21
+ // Set default port
22
+ const port = options.port || '3000'
31
23
 
32
24
  // Detect context
33
25
  const context = await detectLaunch77Context(process.cwd())
@@ -39,7 +31,7 @@ export function createAppCommand(): Command {
39
31
  const templateSpinner = ora('Generating app structure...').start()
40
32
  let result
41
33
  try {
42
- result = await appService.createApp({ type: type as AppType, appName, port }, context)
34
+ result = await appService.createApp({ type: template, appName, port }, context)
43
35
  templateSpinner.succeed('App structure generated')
44
36
  } catch (error) {
45
37
  templateSpinner.fail('Failed to generate app structure')
@@ -12,7 +12,7 @@ import { AppService } from '../services/app-svc.js'
12
12
  export function deleteAppCommand(): Command {
13
13
  const command = new Command('app:delete')
14
14
  .argument('<app-name>', 'Name of the app to delete')
15
- .description('Delete an app from the startup (requires confirmation)')
15
+ .description('Delete an app from the workspace (requires confirmation)')
16
16
  .action(async (appName: string) => {
17
17
  try {
18
18
  console.log(chalk.yellow(`\n⚠️ WARNING: You are about to delete the app '${appName}'\n`))
@@ -25,7 +25,6 @@ export function validateManifestCommand(): Command {
25
25
  console.log()
26
26
  console.log(chalk.cyan('Manifest details:'))
27
27
  console.log(chalk.gray(' Schema version:'), manifest.schemaVersion)
28
- console.log(chalk.gray(' App type:'), manifest.type)
29
28
  console.log(chalk.gray(' Package:'), manifest.package)
30
29
 
31
30
  if ('name' in manifest && manifest.name) {
@@ -3,7 +3,7 @@ export { AppService } from './services/app-svc.js'
3
3
  export { ManifestService } from './services/manifest-svc.js'
4
4
 
5
5
  // Types
6
- export type { CreateAppRequest, CreateAppResult, DeleteAppRequest, DeleteAppResult, AppType } from './types/app-types.js'
6
+ export type { CreateAppRequest, CreateAppResult, DeleteAppRequest, DeleteAppResult } from './types/app-types.js'
7
7
  export type { AppManifest, DeploymentConfig } from './lib/manifest-schema.js'
8
8
 
9
9
  // Errors
@@ -12,5 +12,4 @@ export { AppAlreadyExistsError, AppNotFoundError, InvalidAppNameError, TemplateN
12
12
  // Commands
13
13
  export { createAppCommand } from './commands/create-app.js'
14
14
  export { deleteAppCommand } from './commands/delete-app.js'
15
- export { generateManifestCommand } from './commands/generate-manifest.js'
16
15
  export { validateManifestCommand } from './commands/validate-manifest.js'
@@ -21,6 +21,22 @@ export class AppTemplateResolver extends PackageResolver {
21
21
  }
22
22
 
23
23
  protected async verify(localPath: string): Promise<boolean> {
24
+ // All Launch77 packages must have package.json with version
25
+ const hasPackageJson = await fs.pathExists(path.join(localPath, 'package.json'))
26
+ if (!hasPackageJson) {
27
+ return false
28
+ }
29
+
30
+ // Verify package.json has a version field
31
+ try {
32
+ const packageJson = await fs.readJson(path.join(localPath, 'package.json'))
33
+ if (!packageJson.version) {
34
+ return false
35
+ }
36
+ } catch {
37
+ return false
38
+ }
39
+
24
40
  // Check for either:
25
41
  // 1. A 'template' subdirectory (preferred structure for npm packages)
26
42
  // 2. Any files at the root (simple local templates)
@@ -1,7 +1,5 @@
1
1
  import { z } from 'zod'
2
2
 
3
- import type { AppType } from '../types/app-types.js'
4
-
5
3
  // Deployment configuration
6
4
  export type DeploymentConfig = {
7
5
  platform: 'vercel'
@@ -14,7 +12,6 @@ export type DeploymentConfig = {
14
12
  export type AppManifest = {
15
13
  schemaVersion: 1
16
14
  name: string
17
- type: AppType | 'marketing' // Support both 'marketing' and 'marketing-site'
18
15
  package: string
19
16
  deployment?: DeploymentConfig
20
17
  }
@@ -28,12 +25,10 @@ const deploymentConfigZ = z.object({
28
25
  })
29
26
 
30
27
  // Main manifest schema - strict mode to reject extra fields
31
- // Note: Manifests store 'marketing' but we accept 'marketing-site' in commands
32
28
  export const manifestZ = z
33
29
  .object({
34
30
  schemaVersion: z.literal(1),
35
31
  name: z.string().min(1),
36
- type: z.enum(['marketing', 'webapp', 'api']),
37
32
  package: z.string().min(1),
38
33
  deployment: deploymentConfigZ.optional(),
39
34
  })
@@ -1,6 +1,7 @@
1
1
  import * as path from 'path'
2
2
 
3
3
  import fs from 'fs-extra'
4
+ import { readTemplateMetadata } from '@launch77/plugin-runtime'
4
5
 
5
6
  import { ManifestService } from './manifest-svc.js'
6
7
  import * as filesystem from '../../../infrastructure/filesystem.js'
@@ -12,7 +13,7 @@ import { validateAppName } from '../../../utils/validation.js'
12
13
  import { AppTemplateResolver } from '../lib/app-template-resolver.js'
13
14
 
14
15
  import type { CreateAppRequest, CreateAppResult, DeleteAppRequest, DeleteAppResult } from '../types/app-types.js'
15
- import type { Launch77Context } from '@launch77/plugin-runtime'
16
+ import type { Launch77Context, TemplateReference, Launch77PackageManifest } from '@launch77/plugin-runtime'
16
17
 
17
18
  export class AppService {
18
19
  private manifestService: ManifestService
@@ -51,9 +52,14 @@ export class AppService {
51
52
  const templateResolution = await this.appTemplateResolver.resolveLocation(type, context.workspaceRoot)
52
53
 
53
54
  let templatePath: string
55
+ let templateVersion: string
56
+ let packageRootPath: string
57
+
54
58
  if (templateResolution.source === 'local') {
55
59
  // Use local template
56
- templatePath = templateResolution.localPath!
60
+ packageRootPath = templateResolution.localPath!
61
+ templatePath = packageRootPath
62
+ templateVersion = templateResolution.version! // Local templates always have version after verification
57
63
  } else {
58
64
  // Download npm template
59
65
  const downloadResult = await downloadNpmPackage({
@@ -61,13 +67,20 @@ export class AppService {
61
67
  workspaceRoot: context.workspaceRoot,
62
68
  })
63
69
 
70
+ packageRootPath = downloadResult.packagePath
71
+
64
72
  // Check if template has a 'template' subdirectory, otherwise use package root
65
- const templateSubdir = path.join(downloadResult.packagePath, 'template')
73
+ const templateSubdir = path.join(packageRootPath, 'template')
66
74
  if (await fs.pathExists(templateSubdir)) {
67
75
  templatePath = templateSubdir
68
76
  } else {
69
- templatePath = downloadResult.packagePath
77
+ templatePath = packageRootPath
70
78
  }
79
+
80
+ // Read version from downloaded package
81
+ const packageJsonPath = path.join(packageRootPath, 'package.json')
82
+ const packageJson = await fs.readJson(packageJsonPath)
83
+ templateVersion = packageJson.version
71
84
  }
72
85
 
73
86
  // 5. Determine app path
@@ -91,14 +104,40 @@ export class AppService {
91
104
  port,
92
105
  })
93
106
 
94
- // 9. Generate and write app manifest
95
- const manifest = this.manifestService.generateManifest(type, appName, context, { port })
107
+ // 9. Read template metadata (currently empty, placeholder for future config)
108
+ //const templateMetadata = await readTemplateMetadata(templatePath)
109
+
110
+ // 10. Write template reference to app's package.json
111
+ const appPackageJsonPath = path.join(appPath, 'package.json')
112
+ const appPackageJson = await fs.readJson(appPackageJsonPath)
113
+
114
+ // Build template reference - version read from package.json
115
+ const templatePackage = templateResolution.source === 'npm' ? templateResolution.npmPackage! : templateResolution.resolvedName
116
+ const templateRef: TemplateReference = {
117
+ package: templatePackage,
118
+ version: templateVersion,
119
+ source: templateResolution.source,
120
+ createdAt: new Date().toISOString(),
121
+ }
122
+
123
+ // Initialize launch77 manifest structure
124
+ if (!appPackageJson.launch77) {
125
+ appPackageJson.launch77 = {}
126
+ }
127
+ const launch77Manifest = appPackageJson.launch77 as Launch77PackageManifest
128
+ launch77Manifest.template = templateRef
129
+
130
+ // Write updated package.json
131
+ await fs.writeJson(appPackageJsonPath, appPackageJson, { spaces: 2 })
132
+
133
+ // 11. Generate and write app manifest
134
+ const manifest = this.manifestService.generateManifest(appName, context, { port })
96
135
  const launchDir = path.join(appPath, '.launch')
97
136
  await filesystem.ensureDir(launchDir)
98
137
  const manifestPath = path.join(launchDir, 'app.json')
99
138
  await filesystem.writeJSON(manifestPath, manifest)
100
139
 
101
- // 10. Install dependencies
140
+ // 12. Install dependencies
102
141
  await npm.install(context.workspaceRoot)
103
142
 
104
143
  return {
@@ -8,7 +8,7 @@ import type { AppManifest } from '../lib/manifest-schema.js'
8
8
 
9
9
  /**
10
10
  * Convert kebab-case to Title Case
11
- * e.g., "my-startup-name" -> "My Startup Name"
11
+ * e.g., "my-workspace-name" -> "My Workspace Name"
12
12
  */
13
13
  function toTitleCase(str: string): string {
14
14
  return str
@@ -17,66 +17,17 @@ function toTitleCase(str: string): string {
17
17
  .join(' ')
18
18
  }
19
19
 
20
- /**
21
- * Generate a manifest for a marketing app
22
- */
23
- function generateMarketingManifest(appName: string, context: Launch77Context): AppManifest {
24
- const workspaceName = context.workspaceName
25
-
26
- return {
27
- schemaVersion: 1,
28
- name: `${toTitleCase(workspaceName)} Marketing`,
29
- type: 'marketing',
30
- package: `@${workspaceName}/${appName}`,
31
- }
32
- }
33
-
34
- /**
35
- * Generate a manifest for a webapp
36
- */
37
- function generateWebappManifest(appName: string, context: Launch77Context): AppManifest {
38
- const workspaceName = context.workspaceName
39
-
40
- return {
41
- schemaVersion: 1,
42
- name: `${toTitleCase(workspaceName)} Web`,
43
- type: 'webapp',
44
- package: `@${workspaceName}/${appName}`,
45
- }
46
- }
47
-
48
- /**
49
- * Generate a manifest for an API app
50
- */
51
- function generateApiManifest(appName: string, context: Launch77Context): AppManifest {
52
- const workspaceName = context.workspaceName
53
-
54
- return {
55
- schemaVersion: 1,
56
- name: `${toTitleCase(workspaceName)} API`,
57
- type: 'api',
58
- package: `@${workspaceName}/${appName}`,
59
- }
60
- }
61
-
62
20
  export class ManifestService {
63
21
  /**
64
- * Generate an app manifest based on app type
22
+ * Generate an app manifest
65
23
  */
66
- generateManifest(type: string, appName: string, context: Launch77Context, _options: { port?: string }): AppManifest {
67
- switch (type) {
68
- case 'marketing-site':
69
- case 'marketing':
70
- return generateMarketingManifest(appName, context)
71
-
72
- case 'webapp':
73
- return generateWebappManifest(appName, context)
74
-
75
- case 'api':
76
- return generateApiManifest(appName, context)
24
+ generateManifest(appName: string, context: Launch77Context, _options: { port?: string }): AppManifest {
25
+ const workspaceName = context.workspaceName
77
26
 
78
- default:
79
- throw new Error(`Unsupported app type for manifest generation: ${type}. Supported types: api, webapp, marketing-site`)
27
+ return {
28
+ schemaVersion: 1,
29
+ name: toTitleCase(workspaceName),
30
+ package: `@${workspaceName}/${appName}`,
80
31
  }
81
32
  }
82
33
 
@@ -1,12 +1,5 @@
1
- // Supported app types
2
- export const APP_TYPES = ['api', 'webapp', 'marketing-site'] as const
3
- export type AppType = (typeof APP_TYPES)[number]
4
-
5
- // Display list for error messages
6
- export const APP_TYPES_LIST = APP_TYPES.join(', ')
7
-
8
1
  export interface CreateAppRequest {
9
- type: AppType
2
+ type: string
10
3
  appName: string
11
4
  port: string
12
5
  }
@@ -14,7 +7,7 @@ export interface CreateAppRequest {
14
7
  export interface CreateAppResult {
15
8
  appPath: string
16
9
  appName: string
17
- type: AppType
10
+ type: string
18
11
  }
19
12
 
20
13
  export interface DeleteAppRequest {
@@ -1,7 +1,7 @@
1
1
  import chalk from 'chalk'
2
2
  import { Command } from 'commander'
3
+ import { detectLaunch77Context } from '@launch77/plugin-runtime'
3
4
 
4
- import { detectMonorepoContext } from '../../../utils/monorepo.js'
5
5
  import { CatalogService } from '../services/catalog-svc.js'
6
6
 
7
7
  /**
@@ -17,10 +17,10 @@ export function scanCommand(): Command {
17
17
  .option('--quiet', 'Suppress quality report output')
18
18
  .action(async (options) => {
19
19
  try {
20
- const context = await detectMonorepoContext(process.cwd())
20
+ const context = await detectLaunch77Context(process.cwd())
21
21
 
22
22
  if (!context.isValid) {
23
- console.error(chalk.red('\n✖ Must be run from within a Launch77 monorepo\n'))
23
+ console.error(chalk.red('\n✖ Must be run from within a Launch77 workspace\n'))
24
24
  process.exit(1)
25
25
  }
26
26
 
@@ -9,7 +9,7 @@ import { scanUILibrary } from '../scanners/ui-component-scanner.js'
9
9
  import { generateExamplesFiles } from '../utils/examples-generator.js'
10
10
  import { generateQualityReport, printQualityReport } from '../utils/quality-reporter.js'
11
11
 
12
- import type { MonorepoContext } from '../../../utils/monorepo.js'
12
+ import type { Launch77Context } from '@launch77/plugin-runtime'
13
13
  import type { ValidationResult, UiComponent } from '../types/catalog-types.js'
14
14
 
15
15
  export interface ScanCatalogOptions {
@@ -32,13 +32,13 @@ export class CatalogService {
32
32
  /**
33
33
  * Scan Launch77 codebase and generate catalog.json
34
34
  */
35
- async scanCatalog(context: MonorepoContext, options: ScanCatalogOptions = {}): Promise<ScanCatalogResult> {
35
+ async scanCatalog(context: Launch77Context, options: ScanCatalogOptions = {}): Promise<ScanCatalogResult> {
36
36
  const strict = options.strict !== false // Default to true (strict mode)
37
37
  const quiet = options.quiet || false
38
38
 
39
39
  console.log(chalk.blue('\n🔍 Scanning Launch77 artifacts...\n'))
40
40
 
41
- const launch77Root = path.join(context.monorepoRoot, 'launch77')
41
+ const launch77Root = path.join(context.workspaceRoot, 'launch77')
42
42
 
43
43
  const spinner = ora('Scanning codebase...').start()
44
44
 
@@ -109,7 +109,7 @@ export class CatalogService {
109
109
  console.log(chalk.green(` ✓ Saved to ${path.relative(process.cwd(), catalogPath)}`))
110
110
 
111
111
  // Sync catalog to Launch77 marketing site
112
- const marketingSiteCatalogPath = path.join(context.monorepoRoot, 'startups/launch77/apps/marketing/src/catalog.json')
112
+ const marketingSiteCatalogPath = path.join(context.workspaceRoot, 'apps/marketing/src/catalog.json')
113
113
 
114
114
  if (await fs.pathExists(path.dirname(marketingSiteCatalogPath))) {
115
115
  await fs.writeFile(marketingSiteCatalogPath, formatted)
@@ -6,10 +6,10 @@
6
6
  import * as path from 'path'
7
7
 
8
8
  import chalk from 'chalk'
9
+ import { detectLaunch77Context } from '@launch77/plugin-runtime'
9
10
  import fs from 'fs-extra'
10
11
  import ora from 'ora'
11
12
 
12
- import { detectMonorepoContext } from '../../../utils/monorepo.js'
13
13
  import { DeployService } from '../services/deploy-svc.js'
14
14
  import { connectGitRepository, getPackageName, getVercelInstallInstructions, isVercelCliInstalled, loadVercelProject, runVercelCommand, updateProjectRootDirectory, verifyVercelProjectExists } from '../utils/vercel-extended.js'
15
15
 
@@ -23,16 +23,15 @@ interface DeployInitOptions {
23
23
  export async function deployInit(options: DeployInitOptions = {}) {
24
24
  const deployService = new DeployService()
25
25
 
26
- // Detect monorepo context
27
- const context = await detectMonorepoContext(process.cwd())
26
+ // Detect Launch77 context
27
+ const context = await detectLaunch77Context(process.cwd())
28
28
 
29
- if (context.location !== 'startup-app') {
30
- throw new Error('Must be run from within an app directory (e.g., startups/mycompany/apps/myapp)')
29
+ if (context.locationType !== 'workspace-app') {
30
+ throw new Error('Must be run from within an app directory (e.g., apps/myapp)')
31
31
  }
32
32
 
33
33
  const appPath = process.cwd()
34
34
  const appName = context.appName!
35
- const startupName = context.startupName!
36
35
 
37
36
  console.log(chalk.blue(`\n🚀 Initializing Vercel deployment for ${appName}...\n`))
38
37
 
@@ -116,23 +115,27 @@ export async function deployInit(options: DeployInitOptions = {}) {
116
115
  throw new Error('Could not read package name from package.json')
117
116
  }
118
117
 
118
+ // Check template type from app's package.json
119
+ const appPackageJsonPath = path.join(appPath, 'package.json')
120
+ const appPackageJson = await fs.readJson(appPackageJsonPath)
121
+ const templatePackage = appPackageJson.launch77?.template?.package
122
+
119
123
  console.log(chalk.gray(`App details:`))
120
124
  console.log(chalk.gray(` Name: ${manifest.name}`))
121
- console.log(chalk.gray(` Type: ${manifest.type}`))
122
125
  console.log(chalk.gray(` Package: ${packageName}`))
126
+ if (templatePackage) {
127
+ console.log(chalk.gray(` Template: ${templatePackage}`))
128
+ }
123
129
  console.log()
124
130
 
125
- // Check if this is an API app - Railway deployment coming soon
126
- if (manifest.type === 'api') {
127
- console.log(chalk.yellow('⚠️ Railway deployments for API apps are not yet supported'))
128
- console.log()
129
- console.log(chalk.cyan('Coming soon! For now, you can deploy manually to:'))
130
- console.log(chalk.white(' Railway.app'), chalk.gray('- Simple deployment with databases'))
131
- console.log(chalk.white(' • Fly.io'), chalk.gray('- Global edge deployment'))
132
- console.log(chalk.white(' • Render.com'), chalk.gray('- Managed cloud hosting'))
131
+ // Only allow webapp apps to deploy to Vercel
132
+ const isWebapp = templatePackage === 'webapp' || templatePackage === '@launch77-shared/app-template-webapp'
133
+
134
+ if (!isWebapp) {
135
+ console.log(chalk.yellow('⚠️ Vercel deployment only supports webapp apps'))
136
+ console.log(chalk.gray('Other app types coming soon'))
133
137
  console.log()
134
- console.log(chalk.gray('Stay tuned for automated Railway deployment support!'))
135
- process.exit(0)
138
+ process.exit(1)
136
139
  }
137
140
 
138
141
  // Check if vercel.json exists
@@ -160,7 +163,7 @@ export async function deployInit(options: DeployInitOptions = {}) {
160
163
  console.log()
161
164
 
162
165
  try {
163
- const suggestedProjectName = `${startupName}--${appName}`
166
+ const suggestedProjectName = appName
164
167
  await runVercelCommand(['link', '--project', suggestedProjectName, '--yes'], appPath)
165
168
 
166
169
  console.log()
@@ -172,7 +175,7 @@ export async function deployInit(options: DeployInitOptions = {}) {
172
175
 
173
176
  // Read the created .vercel/project.json to get project info
174
177
  const vercelProject = await loadVercelProject(appPath)
175
- const projectName = vercelProject?.projectName || `${startupName}--${appName}`
178
+ const projectName = vercelProject?.projectName || appName
176
179
 
177
180
  // Update manifest with deployment configuration
178
181
  manifest.deployment = {
@@ -187,7 +190,7 @@ export async function deployInit(options: DeployInitOptions = {}) {
187
190
 
188
191
  // Set Root Directory for monorepo deployments
189
192
  console.log()
190
- const rootDirectory = `startups/${startupName}/apps/${appName}`
193
+ const rootDirectory = `apps/${appName}`
191
194
  await updateProjectRootDirectory(projectName, rootDirectory)
192
195
 
193
196
  // Automatically connect to Git repository
@@ -3,9 +3,12 @@
3
3
  */
4
4
 
5
5
  /* eslint-disable no-console */
6
+ import * as path from 'path'
7
+
6
8
  import chalk from 'chalk'
9
+ import { detectLaunch77Context } from '@launch77/plugin-runtime'
10
+ import fs from 'fs-extra'
7
11
 
8
- import { detectMonorepoContext } from '../../../utils/monorepo.js'
9
12
  import { DeployService } from '../services/deploy-svc.js'
10
13
  import { getVercelInstallInstructions, isVercelCliInstalled, runVercelCommand } from '../utils/vercel-extended.js'
11
14
 
@@ -20,11 +23,11 @@ export interface DeployLogsOptions {
20
23
  export async function deployLogs(options: DeployLogsOptions) {
21
24
  const deployService = new DeployService()
22
25
 
23
- // Detect monorepo context
24
- const context = await detectMonorepoContext(process.cwd())
26
+ // Detect Launch77 context
27
+ const context = await detectLaunch77Context(process.cwd())
25
28
 
26
- if (context.location !== 'startup-app') {
27
- throw new Error('Must be run from within an app directory (e.g., startups/mycompany/apps/myapp)')
29
+ if (context.locationType !== 'workspace-app') {
30
+ throw new Error('Must be run from within an app directory (e.g., apps/myapp)')
28
31
  }
29
32
 
30
33
  const appPath = process.cwd()
@@ -34,14 +37,17 @@ export async function deployLogs(options: DeployLogsOptions) {
34
37
  // Load manifest
35
38
  const manifest = await deployService.loadManifest()
36
39
 
37
- // Check if this is an API app
38
- if (manifest.type === 'api') {
39
- console.log(chalk.yellow('⚠️ Railway deployments for API apps are not yet supported'))
40
- console.log()
41
- console.log(chalk.cyan('Coming soon! For now, you can view logs at:'))
42
- console.log(chalk.white(' Railway.app dashboard'))
43
- console.log(chalk.white(' Fly.io dashboard'))
44
- console.log(chalk.white(' • Render.com dashboard'))
40
+ // Check template type from app's package.json
41
+ const appPackageJsonPath = path.join(appPath, 'package.json')
42
+ const appPackageJson = await fs.readJson(appPackageJsonPath)
43
+ const templatePackage = appPackageJson.launch77?.template?.package
44
+
45
+ // Only allow webapp apps to deploy to Vercel
46
+ const isWebapp = templatePackage === 'webapp' || templatePackage === '@launch77-shared/app-template-webapp'
47
+
48
+ if (!isWebapp) {
49
+ console.log(chalk.yellow('⚠️ Vercel deployment only supports webapp apps'))
50
+ console.log(chalk.gray('Other app types coming soon'))
45
51
  console.log()
46
52
  return
47
53
  }