1
1
import * as clc from "colorette" ;
2
2
import { marked } from "marked" ;
3
+ import * as semver from "semver" ;
3
4
import * as TerminalRenderer from "marked-terminal" ;
4
5
5
- import { displayExtInfo } from "../extensions/displayExtensionInfo" ;
6
+ import { displayExtensionVersionInfo } from "../extensions/displayExtensionInfo" ;
6
7
import * as askUserForEventsConfig from "../extensions/askUserForEventsConfig" ;
7
8
import { checkMinRequiredVersion } from "../checkMinRequiredVersion" ;
8
9
import { Command } from "../command" ;
9
10
import { FirebaseError } from "../error" ;
11
+ import { logger } from "../logger" ;
10
12
import { getProjectId , needProjectId } from "../projectUtils" ;
11
13
import * as extensionsApi from "../extensions/extensionsApi" ;
12
14
import { ExtensionVersion , ExtensionSource } from "../extensions/types" ;
@@ -17,13 +19,11 @@ import {
17
19
createSourceFromLocation ,
18
20
ensureExtensionsApiEnabled ,
19
21
logPrefix ,
20
- promptForOfficialExtension ,
21
22
promptForValidInstanceId ,
22
23
diagnoseAndFixProject ,
23
- isUrlPath ,
24
24
isLocalPath ,
25
- canonicalizeRefInput ,
26
25
} from "../extensions/extensionsHelper" ;
26
+ import { resolveVersion } from "../deploy/extensions/planner" ;
27
27
import { getRandomString } from "../extensions/utils" ;
28
28
import { requirePermissions } from "../requirePermissions" ;
29
29
import * as utils from "../utils" ;
@@ -40,7 +40,7 @@ marked.setOptions({
40
40
/**
41
41
* Command for installing an extension
42
42
*/
43
- export const command = new Command ( "ext:install [extensionName ]" )
43
+ export const command = new Command ( "ext:install [extensionRef ]" )
44
44
. description (
45
45
"add an uploaded extension to firebase.json if [publisherId/extensionId] is provided;" +
46
46
"or, add a local extension if [localPath] is provided"
@@ -51,67 +51,80 @@ export const command = new Command("ext:install [extensionName]")
51
51
. before ( ensureExtensionsApiEnabled )
52
52
. before ( checkMinRequiredVersion , "extMinVersion" )
53
53
. before ( diagnoseAndFixProject )
54
- . action ( async ( extensionName : string , options : Options ) => {
55
- const projectId = getProjectId ( options ) ;
56
- // TODO(b/230598656): Clean up paramsEnvPath after v11 launch.
57
- const paramsEnvPath = "" ;
58
- let learnMore = false ;
59
- if ( ! extensionName ) {
60
- if ( options . interactive ) {
61
- learnMore = true ;
62
- extensionName = await promptForOfficialExtension (
63
- "Which official extension do you wish to install?\n" +
64
- " Select an extension, then press Enter to learn more."
65
- ) ;
66
- } else {
67
- throw new FirebaseError (
68
- `Unable to find published extension '${ clc . bold ( extensionName ) } '. ` +
69
- `Run ${ clc . bold (
70
- "firebase ext:install -i"
71
- ) } to select from the list of all available published extensions.`
72
- ) ;
73
- }
74
- }
75
- let source ;
76
- let extensionVersion ;
77
-
78
- // TODO(b/220900194): Remove when deprecating old install flow.
79
- // --local doesn't support urlPath so this will become dead codepath.
80
- if ( isUrlPath ( extensionName ) ) {
81
- throw new FirebaseError (
82
- `Installing with a source url is no longer supported in the CLI. Please use Firebase Console instead.`
83
- ) ;
84
- }
54
+ . action ( async ( extensionRef : string , options : Options ) => {
85
55
if ( options . local ) {
86
56
utils . logLabeledWarning (
87
57
logPrefix ,
88
58
"As of firebase-tools@11.0.0, the `--local` flag is no longer required, as it is the default behavior."
89
59
) ;
90
60
}
91
-
61
+ if ( ! extensionRef ) {
62
+ throw new FirebaseError (
63
+ "Extension ref is required to install. To see a full list of available extensions, go to Extensions Hub (https://extensions.dev/extensions)."
64
+ ) ;
65
+ }
66
+ let source : ExtensionSource | undefined ;
67
+ let extensionVersion : ExtensionVersion | undefined ;
68
+ const projectId = getProjectId ( options ) ;
92
69
// If the user types in a local path (prefixed with ~/, ../, or ./), install from local source.
93
70
// Otherwise, treat the input as an extension reference and proceed with reference-based installation.
94
- if ( isLocalPath ( extensionName ) ) {
71
+ if ( isLocalPath ( extensionRef ) ) {
95
72
// TODO(b/228444119): Create source should happen at deploy time.
96
73
// Should parse spec locally so we don't need project ID.
97
- source = await createSourceFromLocation ( needProjectId ( { projectId } ) , extensionName ) ;
98
- await displayExtInfo ( extensionName , "" , source . spec ) ;
74
+ source = await createSourceFromLocation ( needProjectId ( { projectId } ) , extensionRef ) ;
75
+ await displayExtensionVersionInfo ( { spec : source . spec } ) ;
99
76
void trackGA4 ( "extension_added_to_manifest" , {
100
77
published : "local" ,
101
78
interactive : options . nonInteractive ? "false" : "true" ,
102
79
} ) ;
103
80
} else {
104
- extensionName = await canonicalizeRefInput ( extensionName ) ;
105
- extensionVersion = await extensionsApi . getExtensionVersion ( extensionName ) ;
106
-
81
+ const extension = await extensionsApi . getExtension ( extensionRef ) ;
82
+ const ref = refs . parse ( extensionRef ) ;
83
+ ref . version = await resolveVersion ( ref , extension ) ;
84
+ const extensionVersionRef = refs . toExtensionVersionRef ( ref ) ;
85
+ extensionVersion = await extensionsApi . getExtensionVersion ( extensionVersionRef ) ;
107
86
void trackGA4 ( "extension_added_to_manifest" , {
108
87
published : extensionVersion . listing ?. state === "APPROVED" ? "published" : "uploaded" ,
109
88
interactive : options . nonInteractive ? "false" : "true" ,
110
89
} ) ;
111
- await infoExtensionVersion ( {
112
- extensionName ,
90
+ await displayExtensionVersionInfo ( {
91
+ spec : extensionVersion . spec ,
113
92
extensionVersion,
93
+ latestApprovedVersion : extension . latestApprovedVersion ,
94
+ latestVersion : extension . latestVersion ,
114
95
} ) ;
96
+ if ( extensionVersion . state === "DEPRECATED" ) {
97
+ throw new FirebaseError (
98
+ `Extension version ${ clc . bold (
99
+ extensionVersionRef
100
+ ) } is deprecated and cannot be installed. To install the latest non-deprecated version, omit the version in the extension ref.`
101
+ ) ;
102
+ }
103
+ logger . info ( ) ;
104
+ // Check if selected version is older than the latest approved version, or the latest version only if there is no approved version.
105
+ if (
106
+ ( extension . latestApprovedVersion &&
107
+ semver . gt ( extension . latestApprovedVersion , extensionVersion . spec . version ) ) ||
108
+ ( ! extension . latestApprovedVersion &&
109
+ extension . latestVersion &&
110
+ semver . gt ( extension . latestVersion , extensionVersion . spec . version ) )
111
+ ) {
112
+ const version = extension . latestApprovedVersion || extension . latestVersion ;
113
+ logger . info (
114
+ `You are about to install extension version ${ clc . bold (
115
+ extensionVersion . spec . version
116
+ ) } which is older than the latest ${
117
+ extension . latestApprovedVersion ? "accepted version" : "version"
118
+ } ${ clc . bold ( version ! ) } .`
119
+ ) ;
120
+ }
121
+ }
122
+ if ( ! source && ! extensionVersion ) {
123
+ throw new FirebaseError (
124
+ `Failed to parse ${ clc . bold (
125
+ extensionRef
126
+ ) } as an extension version or a path to a local extension. Please specify a valid reference.`
127
+ ) ;
115
128
}
116
129
if (
117
130
! ( await confirm ( {
@@ -122,33 +135,18 @@ export const command = new Command("ext:install [extensionName]")
122
135
) {
123
136
return ;
124
137
}
125
- if ( ! source && ! extensionVersion ) {
126
- throw new FirebaseError (
127
- "Could not find a source. Please specify a valid source to continue."
128
- ) ;
129
- }
130
138
const spec = source ?. spec ?? extensionVersion ?. spec ;
131
139
if ( ! spec ) {
132
140
throw new FirebaseError (
133
141
`Could not find the extension.yaml for extension '${ clc . bold (
134
- extensionName
142
+ extensionRef
135
143
) } '. Please make sure this is a valid extension and try again.`
136
144
) ;
137
145
}
138
- if ( learnMore ) {
139
- utils . logLabeledBullet (
140
- logPrefix ,
141
- `You selected: ${ clc . bold ( spec . displayName || "" ) } .\n` +
142
- `${ spec . description } \n` +
143
- `View details: https://firebase.google.com/products/extensions/${ spec . name } \n`
144
- ) ;
145
- }
146
-
147
146
try {
148
147
return installToManifest ( {
149
- paramsEnvPath,
150
148
projectId,
151
- extensionName ,
149
+ extensionRef ,
152
150
source,
153
151
extVersion : extensionVersion ,
154
152
nonInteractive : options . nonInteractive ,
@@ -164,18 +162,9 @@ export const command = new Command("ext:install [extensionName]")
164
162
}
165
163
} ) ;
166
164
167
- async function infoExtensionVersion ( args : {
168
- extensionName : string ;
169
- extensionVersion : ExtensionVersion ;
170
- } ) : Promise < void > {
171
- const ref = refs . parse ( args . extensionName ) ;
172
- await displayExtInfo ( args . extensionName , ref . publisherId , args . extensionVersion . spec , true ) ;
173
- }
174
-
175
165
interface InstallExtensionOptions {
176
- paramsEnvPath ?: string ;
177
166
projectId ?: string ;
178
- extensionName : string ;
167
+ extensionRef : string ;
179
168
source ?: ExtensionSource ;
180
169
extVersion ?: ExtensionVersion ;
181
170
nonInteractive : boolean ;
@@ -189,14 +178,13 @@ interface InstallExtensionOptions {
189
178
* @param options
190
179
*/
191
180
async function installToManifest ( options : InstallExtensionOptions ) : Promise < void > {
192
- const { projectId, extensionName, extVersion, source, paramsEnvPath, nonInteractive, force } =
193
- options ;
194
- const isLocalSource = isLocalPath ( extensionName ) ;
181
+ const { projectId, extensionRef, extVersion, source, nonInteractive, force } = options ;
182
+ const isLocalSource = isLocalPath ( extensionRef ) ;
195
183
196
184
const spec = extVersion ?. spec ?? source ?. spec ;
197
185
if ( ! spec ) {
198
186
throw new FirebaseError (
199
- `Could not find the extension.yaml for ${ extensionName } . Please make sure this is a valid extension and try again.`
187
+ `Could not find the extension.yaml for ${ extensionRef } . Please make sure this is a valid extension and try again.`
200
188
) ;
201
189
}
202
190
@@ -215,7 +203,6 @@ async function installToManifest(options: InstallExtensionOptions): Promise<void
215
203
projectId,
216
204
paramSpecs : ( spec . params ?? [ ] ) . concat ( spec . systemParams ?? [ ] ) ,
217
205
nonInteractive,
218
- paramsEnvPath,
219
206
instanceId,
220
207
} ) ;
221
208
const eventsConfig = spec . events
@@ -237,7 +224,7 @@ async function installToManifest(options: InstallExtensionOptions): Promise<void
237
224
{
238
225
instanceId,
239
226
ref : ! isLocalSource ? ref : undefined ,
240
- localPath : isLocalSource ? extensionName : undefined ,
227
+ localPath : isLocalSource ? extensionRef : undefined ,
241
228
params : paramBindingOptions ,
242
229
extensionSpec : spec ,
243
230
} ,
0 commit comments