@friggframework/devtools 2.0.0--canary.545.88cf983.0 → 2.0.0--canary.545.c870571.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.
- package/frigg-cli/build-command/index.js +105 -11
- package/frigg-cli/deploy-command/index.js +83 -1
- package/frigg-cli/doctor-command/index.js +20 -0
- package/frigg-cli/generate-iam-command.js +21 -1
- package/frigg-cli/repair-command/index.js +20 -0
- package/frigg-cli/start-command/index.js +77 -0
- package/frigg-cli/utils/__tests__/provider-helper.test.js +55 -0
- package/frigg-cli/utils/provider-helper.js +75 -0
- package/package.json +7 -7
|
@@ -1,7 +1,15 @@
|
|
|
1
1
|
const { spawnSync } = require('child_process');
|
|
2
2
|
const path = require('path');
|
|
3
|
+
const fs = require('fs');
|
|
3
4
|
|
|
4
5
|
async function buildCommand(options) {
|
|
6
|
+
// Check if the app uses a non-AWS provider
|
|
7
|
+
const providerResult = loadProviderIfConfigured();
|
|
8
|
+
if (providerResult) {
|
|
9
|
+
return buildWithProvider(providerResult, options);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// Default: AWS build via serverless framework
|
|
5
13
|
console.log('Building the serverless application...');
|
|
6
14
|
|
|
7
15
|
// Suppress AWS SDK warning message about maintenance mode
|
|
@@ -10,13 +18,13 @@ async function buildCommand(options) {
|
|
|
10
18
|
// Skip AWS discovery for local builds (unless --production flag is set)
|
|
11
19
|
if (!options.production) {
|
|
12
20
|
process.env.FRIGG_SKIP_AWS_DISCOVERY = 'true';
|
|
13
|
-
console.log('
|
|
21
|
+
console.log('Building in local mode (use --production flag for production builds with AWS discovery)');
|
|
14
22
|
} else {
|
|
15
|
-
console.log('
|
|
23
|
+
console.log('Building in production mode with AWS discovery enabled');
|
|
16
24
|
}
|
|
17
25
|
|
|
18
26
|
// AWS discovery is now handled directly in serverless-template.js
|
|
19
|
-
console.log('
|
|
27
|
+
console.log('Packaging serverless application...');
|
|
20
28
|
const backendPath = path.resolve(process.cwd());
|
|
21
29
|
const infrastructurePath = 'infrastructure.js';
|
|
22
30
|
const command = 'osls'; // OSS-Serverless (drop-in replacement for serverless v3)
|
|
@@ -51,16 +59,102 @@ async function buildCommand(options) {
|
|
|
51
59
|
console.error(`Serverless build failed with code ${result.status}`);
|
|
52
60
|
process.exit(1);
|
|
53
61
|
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Check if the appDefinition specifies a non-AWS provider and resolve it.
|
|
66
|
+
*/
|
|
67
|
+
function loadProviderIfConfigured() {
|
|
68
|
+
try {
|
|
69
|
+
const { loadProviderForCli } = require('../utils/provider-helper');
|
|
70
|
+
const result = loadProviderForCli();
|
|
71
|
+
if (result && result.provider) {
|
|
72
|
+
return result;
|
|
73
|
+
}
|
|
74
|
+
} catch {
|
|
75
|
+
// Provider helper not available or appDefinition not found — fall through
|
|
76
|
+
}
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Build using a provider plugin.
|
|
82
|
+
* Provider build steps:
|
|
83
|
+
* 1. generateConfig() — generate platform-specific config (netlify.toml, etc.)
|
|
84
|
+
* 2. getFunctionEntryPoints() — copy function files to the project
|
|
85
|
+
*/
|
|
86
|
+
async function buildWithProvider({ provider, appDefinition, providerName }, options) {
|
|
87
|
+
console.log(`Building for ${providerName} provider...`);
|
|
88
|
+
const projectDir = path.resolve(process.cwd());
|
|
89
|
+
|
|
90
|
+
// 1. Validate
|
|
91
|
+
if (typeof provider.validate === 'function') {
|
|
92
|
+
const validation = provider.validate(appDefinition);
|
|
93
|
+
if (validation.errors?.length > 0) {
|
|
94
|
+
console.error(`\nValidation errors for ${providerName}:`);
|
|
95
|
+
for (const error of validation.errors) {
|
|
96
|
+
console.error(` - ${error}`);
|
|
97
|
+
}
|
|
98
|
+
process.exit(1);
|
|
99
|
+
}
|
|
100
|
+
if (validation.warnings?.length > 0) {
|
|
101
|
+
for (const warning of validation.warnings) {
|
|
102
|
+
console.warn(` Warning: ${warning}`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// 2. Generate platform config (e.g., netlify.toml)
|
|
108
|
+
if (typeof provider.generateConfig === 'function') {
|
|
109
|
+
console.log(`Generating ${providerName} configuration...`);
|
|
110
|
+
const config = provider.generateConfig(appDefinition);
|
|
54
111
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
112
|
+
const configFileNames = {
|
|
113
|
+
netlify: 'netlify.toml',
|
|
114
|
+
};
|
|
115
|
+
const configFileName = configFileNames[providerName] || `${providerName}.config`;
|
|
116
|
+
const configPath = path.join(projectDir, configFileName);
|
|
117
|
+
|
|
118
|
+
fs.writeFileSync(configPath, config, 'utf-8');
|
|
119
|
+
console.log(` Written ${configFileName}`);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// 3. Copy function entry points
|
|
123
|
+
if (typeof provider.getFunctionEntryPoints === 'function') {
|
|
124
|
+
console.log('Generating function entry points...');
|
|
125
|
+
const entryPoints = provider.getFunctionEntryPoints(appDefinition);
|
|
126
|
+
const functionsDir = path.join(projectDir, 'netlify', 'functions');
|
|
127
|
+
|
|
128
|
+
fs.mkdirSync(functionsDir, { recursive: true });
|
|
129
|
+
|
|
130
|
+
for (const [filename, content] of Object.entries(entryPoints)) {
|
|
131
|
+
const filePath = path.join(functionsDir, filename);
|
|
132
|
+
fs.writeFileSync(filePath, content, 'utf-8');
|
|
133
|
+
if (options.verbose) {
|
|
134
|
+
console.log(` Written ${path.relative(projectDir, filePath)}`);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
console.log(` Generated ${Object.keys(entryPoints).length} function entry points`);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// 4. Generate env template (informational)
|
|
142
|
+
if (typeof provider.generateEnvTemplate === 'function') {
|
|
143
|
+
const envTemplate = provider.generateEnvTemplate(appDefinition);
|
|
144
|
+
const missingEnvVars = Object.entries(envTemplate)
|
|
145
|
+
.filter(([key]) => !process.env[key])
|
|
146
|
+
.map(([key, desc]) => `${key}: ${desc}`);
|
|
147
|
+
|
|
148
|
+
if (missingEnvVars.length > 0) {
|
|
149
|
+
console.log(`\n Required environment variables not set locally:`);
|
|
150
|
+
for (const entry of missingEnvVars) {
|
|
151
|
+
console.log(` - ${entry}`);
|
|
152
|
+
}
|
|
153
|
+
console.log(` Configure these in your ${providerName} dashboard.`);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
58
156
|
|
|
59
|
-
|
|
60
|
-
// if (code !== 0) {
|
|
61
|
-
// console.log(`Child process exited with code ${code}`);
|
|
62
|
-
// }
|
|
63
|
-
// });
|
|
157
|
+
console.log(`\nBuild complete for ${providerName}.`);
|
|
64
158
|
}
|
|
65
159
|
|
|
66
160
|
module.exports = { buildCommand };
|
|
@@ -268,9 +268,17 @@ async function runPostDeploymentHealthCheck(stackName, options) {
|
|
|
268
268
|
}
|
|
269
269
|
|
|
270
270
|
async function deployCommand(options) {
|
|
271
|
+
const appDefinition = loadAppDefinition();
|
|
272
|
+
|
|
273
|
+
// Check if the app uses a non-AWS provider
|
|
274
|
+
const providerResult = loadProviderIfConfigured(appDefinition);
|
|
275
|
+
if (providerResult) {
|
|
276
|
+
return deployWithProvider(providerResult, options);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Default: AWS deployment via serverless framework
|
|
271
280
|
console.log('Deploying the serverless application...');
|
|
272
281
|
|
|
273
|
-
const appDefinition = loadAppDefinition();
|
|
274
282
|
const environment = validateAndBuildEnvironment(appDefinition, options);
|
|
275
283
|
|
|
276
284
|
// Execute deployment
|
|
@@ -302,4 +310,78 @@ async function deployCommand(options) {
|
|
|
302
310
|
}
|
|
303
311
|
}
|
|
304
312
|
|
|
313
|
+
/**
|
|
314
|
+
* Check if the appDefinition specifies a non-AWS provider and resolve it.
|
|
315
|
+
* Returns null for AWS (default) so the caller falls through to existing behavior.
|
|
316
|
+
*
|
|
317
|
+
* @param {Object|null} appDefinition
|
|
318
|
+
* @returns {{ provider: Object, appDefinition: Object, providerName: string } | null}
|
|
319
|
+
*/
|
|
320
|
+
function loadProviderIfConfigured(appDefinition) {
|
|
321
|
+
const providerName = appDefinition?.provider;
|
|
322
|
+
if (!providerName || providerName === 'aws') {
|
|
323
|
+
return null;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
try {
|
|
327
|
+
const { resolveProvider } = require('@friggframework/core/providers/resolve-provider');
|
|
328
|
+
const provider = resolveProvider(appDefinition);
|
|
329
|
+
return { provider, appDefinition, providerName };
|
|
330
|
+
} catch (error) {
|
|
331
|
+
console.error(`Failed to load provider '${providerName}': ${error.message}`);
|
|
332
|
+
process.exit(1);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Deploy using a provider plugin (Netlify, etc.).
|
|
338
|
+
* Delegates entirely to the provider's deploy lifecycle:
|
|
339
|
+
* 1. validate() — check appDefinition for provider-specific issues
|
|
340
|
+
* 2. preflightCheck() — verify prerequisites (CLI tools, credentials)
|
|
341
|
+
* 3. deploy() — execute the deployment
|
|
342
|
+
*/
|
|
343
|
+
async function deployWithProvider({ provider, appDefinition, providerName }, options) {
|
|
344
|
+
console.log(`Deploying with ${providerName} provider...`);
|
|
345
|
+
|
|
346
|
+
// 1. Validate appDefinition for this provider
|
|
347
|
+
if (typeof provider.validate === 'function') {
|
|
348
|
+
const validation = provider.validate(appDefinition);
|
|
349
|
+
if (validation.errors?.length > 0) {
|
|
350
|
+
console.error(`\nValidation errors for ${providerName}:`);
|
|
351
|
+
for (const error of validation.errors) {
|
|
352
|
+
console.error(` - ${error}`);
|
|
353
|
+
}
|
|
354
|
+
process.exit(1);
|
|
355
|
+
}
|
|
356
|
+
if (validation.warnings?.length > 0) {
|
|
357
|
+
for (const warning of validation.warnings) {
|
|
358
|
+
console.warn(` Warning: ${warning}`);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// 2. Deploy via provider
|
|
364
|
+
try {
|
|
365
|
+
const result = await provider.deploy(appDefinition, {
|
|
366
|
+
stage: options.stage,
|
|
367
|
+
prod: options.stage === 'production' || options.stage === 'prod',
|
|
368
|
+
dryRun: options.dryRun || false,
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
console.log(`\n✓ Deployment completed successfully!`);
|
|
372
|
+
if (result.url) {
|
|
373
|
+
console.log(` URL: ${result.url}`);
|
|
374
|
+
}
|
|
375
|
+
} catch (error) {
|
|
376
|
+
console.error(`\n✗ Deployment failed: ${error.message}`);
|
|
377
|
+
if (error.missing) {
|
|
378
|
+
console.error(' Missing prerequisites:');
|
|
379
|
+
for (const item of error.missing) {
|
|
380
|
+
console.error(` - ${item}`);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
process.exit(1);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
305
387
|
module.exports = { deployCommand };
|
|
@@ -247,6 +247,13 @@ async function promptForStackSelection(region) {
|
|
|
247
247
|
*/
|
|
248
248
|
async function doctorCommand(stackName, options = {}) {
|
|
249
249
|
try {
|
|
250
|
+
// Guard: doctor only works with AWS (CloudFormation stacks)
|
|
251
|
+
if (isNonAwsProvider()) {
|
|
252
|
+
output.error('The doctor command is only available for AWS deployments.');
|
|
253
|
+
output.log('Your appDefinition uses a non-AWS provider.');
|
|
254
|
+
process.exit(1);
|
|
255
|
+
}
|
|
256
|
+
|
|
250
257
|
// Extract options with defaults
|
|
251
258
|
const region = options.region || process.env.AWS_REGION || 'us-east-1';
|
|
252
259
|
const format = options.format || 'console';
|
|
@@ -333,4 +340,17 @@ async function doctorCommand(stackName, options = {}) {
|
|
|
333
340
|
}
|
|
334
341
|
}
|
|
335
342
|
|
|
343
|
+
/**
|
|
344
|
+
* Check if the current appDefinition uses a non-AWS provider.
|
|
345
|
+
*/
|
|
346
|
+
function isNonAwsProvider() {
|
|
347
|
+
try {
|
|
348
|
+
const { loadProviderForCli } = require('../utils/provider-helper');
|
|
349
|
+
const result = loadProviderForCli();
|
|
350
|
+
return result && result.providerName !== 'aws';
|
|
351
|
+
} catch {
|
|
352
|
+
return false;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
336
356
|
module.exports = { doctorCommand };
|
|
@@ -9,7 +9,14 @@ const { generateIAMCloudFormation, getFeatureSummary } = require('../infrastruct
|
|
|
9
9
|
*/
|
|
10
10
|
async function generateIamCommand(options = {}) {
|
|
11
11
|
try {
|
|
12
|
-
|
|
12
|
+
// Guard: generate-iam only works with AWS (IAM / CloudFormation)
|
|
13
|
+
if (isNonAwsProvider()) {
|
|
14
|
+
console.error('The generate-iam command is only available for AWS deployments.');
|
|
15
|
+
console.log('Your appDefinition uses a non-AWS provider.');
|
|
16
|
+
process.exit(1);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
console.log('Finding Frigg application...');
|
|
13
20
|
|
|
14
21
|
// Find the backend package.json
|
|
15
22
|
const backendPath = findNearestBackendPackageJson();
|
|
@@ -115,4 +122,17 @@ async function generateIamCommand(options = {}) {
|
|
|
115
122
|
}
|
|
116
123
|
}
|
|
117
124
|
|
|
125
|
+
/**
|
|
126
|
+
* Check if the current appDefinition uses a non-AWS provider.
|
|
127
|
+
*/
|
|
128
|
+
function isNonAwsProvider() {
|
|
129
|
+
try {
|
|
130
|
+
const { loadProviderForCli } = require('./utils/provider-helper');
|
|
131
|
+
const result = loadProviderForCli();
|
|
132
|
+
return result && result.providerName !== 'aws';
|
|
133
|
+
} catch {
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
118
138
|
module.exports = { generateIamCommand };
|
|
@@ -426,6 +426,13 @@ async function handleReconcileRepair(stackIdentifier, report, options) {
|
|
|
426
426
|
*/
|
|
427
427
|
async function repairCommand(stackName, options = {}) {
|
|
428
428
|
try {
|
|
429
|
+
// Guard: repair only works with AWS (CloudFormation stacks)
|
|
430
|
+
if (isNonAwsProvider()) {
|
|
431
|
+
output.error('The repair command is only available for AWS deployments.');
|
|
432
|
+
output.log('Your appDefinition uses a non-AWS provider.');
|
|
433
|
+
process.exit(1);
|
|
434
|
+
}
|
|
435
|
+
|
|
429
436
|
// Validate required parameter
|
|
430
437
|
if (!stackName) {
|
|
431
438
|
output.error('Error: Stack name is required');
|
|
@@ -534,4 +541,17 @@ async function repairCommand(stackName, options = {}) {
|
|
|
534
541
|
}
|
|
535
542
|
}
|
|
536
543
|
|
|
544
|
+
/**
|
|
545
|
+
* Check if the current appDefinition uses a non-AWS provider.
|
|
546
|
+
*/
|
|
547
|
+
function isNonAwsProvider() {
|
|
548
|
+
try {
|
|
549
|
+
const { loadProviderForCli } = require('../utils/provider-helper');
|
|
550
|
+
const result = loadProviderForCli();
|
|
551
|
+
return result && result.providerName !== 'aws';
|
|
552
|
+
} catch {
|
|
553
|
+
return false;
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
|
|
537
557
|
module.exports = { repairCommand };
|
|
@@ -63,6 +63,13 @@ async function startCommand(options) {
|
|
|
63
63
|
console.log(chalk.green('✓ Pre-flight checks passed\n'));
|
|
64
64
|
console.log('Starting backend and optional frontend...');
|
|
65
65
|
|
|
66
|
+
// Check if the app uses a non-AWS provider
|
|
67
|
+
const providerResult = loadProviderIfConfigured();
|
|
68
|
+
if (providerResult) {
|
|
69
|
+
return startWithProvider(providerResult, options);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Default: AWS local development via serverless-offline
|
|
66
73
|
// Suppress AWS SDK warning message about maintenance mode
|
|
67
74
|
process.env.AWS_SDK_JS_SUPPRESS_MAINTENANCE_MODE_MESSAGE = '1';
|
|
68
75
|
// Skip AWS discovery for local development
|
|
@@ -109,6 +116,76 @@ async function startCommand(options) {
|
|
|
109
116
|
});
|
|
110
117
|
}
|
|
111
118
|
|
|
119
|
+
/**
|
|
120
|
+
* Check if the appDefinition specifies a non-AWS provider and resolve it.
|
|
121
|
+
* Returns null for AWS (default) so the caller falls through to existing behavior.
|
|
122
|
+
*/
|
|
123
|
+
function loadProviderIfConfigured() {
|
|
124
|
+
try {
|
|
125
|
+
const { loadProviderForCli } = require('../utils/provider-helper');
|
|
126
|
+
const result = loadProviderForCli();
|
|
127
|
+
if (result && result.provider) {
|
|
128
|
+
return result;
|
|
129
|
+
}
|
|
130
|
+
} catch {
|
|
131
|
+
// Provider helper not available or appDefinition not found — fall through
|
|
132
|
+
}
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Start local dev server using the provider's recommended approach.
|
|
138
|
+
* Each provider has a different local dev story:
|
|
139
|
+
* - Netlify: `netlify dev` (reads netlify.toml, serves functions locally)
|
|
140
|
+
* - AWS: `osls offline` (serverless-offline, handled by default path above)
|
|
141
|
+
*/
|
|
142
|
+
function startWithProvider({ provider, providerName }, options) {
|
|
143
|
+
const backendPath = path.resolve(process.cwd());
|
|
144
|
+
|
|
145
|
+
// Provider-specific dev server commands
|
|
146
|
+
const DEV_COMMANDS = {
|
|
147
|
+
netlify: { command: 'netlify', args: ['dev'] },
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
const devCmd = DEV_COMMANDS[providerName];
|
|
151
|
+
if (!devCmd) {
|
|
152
|
+
console.error(chalk.red(
|
|
153
|
+
`Provider '${providerName}' does not have a local dev server configured.\n` +
|
|
154
|
+
` Supported providers for local dev: ${Object.keys(DEV_COMMANDS).join(', ')}, aws`
|
|
155
|
+
));
|
|
156
|
+
process.exit(1);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
console.log(chalk.blue(`Starting local dev server (${providerName})...`));
|
|
160
|
+
|
|
161
|
+
const args = [...devCmd.args];
|
|
162
|
+
if (options.verbose) {
|
|
163
|
+
console.log(`Executing command: ${devCmd.command} ${args.join(' ')}`);
|
|
164
|
+
console.log(`Working directory: ${backendPath}`);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const childProcess = spawn(devCmd.command, args, {
|
|
168
|
+
cwd: backendPath,
|
|
169
|
+
stdio: 'inherit',
|
|
170
|
+
env: { ...process.env },
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
childProcess.on('error', (error) => {
|
|
174
|
+
if (error.code === 'ENOENT') {
|
|
175
|
+
console.error(chalk.red(
|
|
176
|
+
`'${devCmd.command}' not found. Install it with: npm install -g ${devCmd.command}-cli`
|
|
177
|
+
));
|
|
178
|
+
} else {
|
|
179
|
+
console.error(`Error executing command: ${error.message}`);
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
childProcess.on('close', (code) => {
|
|
184
|
+
if (code !== 0) {
|
|
185
|
+
console.log(`Child process exited with code ${code}`);
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
|
|
112
189
|
/**
|
|
113
190
|
* Run interactive pre-flight checks with resolution prompts
|
|
114
191
|
* @param {string} projectPath - Path to the Frigg project
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const { loadProviderForCli, loadCliAppDefinition } = require('../provider-helper');
|
|
3
|
+
|
|
4
|
+
describe('provider-helper', () => {
|
|
5
|
+
describe('loadCliAppDefinition', () => {
|
|
6
|
+
it('returns null when no index.js exists', () => {
|
|
7
|
+
const result = loadCliAppDefinition('/nonexistent/path');
|
|
8
|
+
expect(result).toBeNull();
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it('returns null when index.js has no Definition export', () => {
|
|
12
|
+
// Use a directory that has an index.js but no Definition
|
|
13
|
+
const result = loadCliAppDefinition(
|
|
14
|
+
path.join(__dirname, '..', '..')
|
|
15
|
+
);
|
|
16
|
+
// frigg-cli/index.js doesn't export Definition
|
|
17
|
+
expect(result).toBeNull();
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
describe('loadProviderForCli', () => {
|
|
22
|
+
it('returns null when no appDefinition found', () => {
|
|
23
|
+
// Run from a directory with no Frigg app
|
|
24
|
+
const originalCwd = process.cwd();
|
|
25
|
+
try {
|
|
26
|
+
process.chdir('/tmp');
|
|
27
|
+
const result = loadProviderForCli();
|
|
28
|
+
expect(result).toBeNull();
|
|
29
|
+
} finally {
|
|
30
|
+
process.chdir(originalCwd);
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('returns null provider for aws (default)', () => {
|
|
35
|
+
// Mock: loadCliAppDefinition returns an appDef with provider: 'aws'
|
|
36
|
+
jest.mock('../provider-helper', () => {
|
|
37
|
+
const original = jest.requireActual('../provider-helper');
|
|
38
|
+
return {
|
|
39
|
+
...original,
|
|
40
|
+
loadProviderForCli: (options) => {
|
|
41
|
+
// Simulate AWS provider (no provider field defaults to aws)
|
|
42
|
+
return { appDefinition: { provider: 'aws' }, provider: null, providerName: 'aws' };
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
const { loadProviderForCli: mocked } = require('../provider-helper');
|
|
48
|
+
const result = mocked();
|
|
49
|
+
expect(result.providerName).toBe('aws');
|
|
50
|
+
expect(result.provider).toBeNull();
|
|
51
|
+
|
|
52
|
+
jest.restoreAllMocks();
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
});
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI Provider Helper
|
|
3
|
+
*
|
|
4
|
+
* Loads the appDefinition from the user's project and resolves the
|
|
5
|
+
* corresponding provider plugin package. Used by CLI commands to
|
|
6
|
+
* delegate to the correct provider (AWS, Netlify, etc.).
|
|
7
|
+
*/
|
|
8
|
+
const path = require('path');
|
|
9
|
+
const fs = require('fs');
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Load the appDefinition and resolve the provider plugin for CLI commands.
|
|
13
|
+
*
|
|
14
|
+
* This is a lightweight loader for the CLI context — it reads the backend
|
|
15
|
+
* index.js directly (no need for the full core app-definition-loader which
|
|
16
|
+
* searches for the nearest backend package.json at runtime).
|
|
17
|
+
*
|
|
18
|
+
* @param {Object} [options]
|
|
19
|
+
* @param {string} [options.cwd] - Working directory (default: process.cwd())
|
|
20
|
+
* @returns {{ appDefinition: Object, provider: Object, providerName: string } | null}
|
|
21
|
+
* Returns null if no appDefinition is found (caller should fall back to default behavior).
|
|
22
|
+
*/
|
|
23
|
+
function loadProviderForCli(options = {}) {
|
|
24
|
+
const cwd = options.cwd || process.cwd();
|
|
25
|
+
const appDefinition = loadCliAppDefinition(cwd);
|
|
26
|
+
|
|
27
|
+
if (!appDefinition) {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const providerName = appDefinition.provider || 'aws';
|
|
32
|
+
|
|
33
|
+
// For 'aws', return null provider — CLI commands fall back to existing behavior
|
|
34
|
+
if (providerName === 'aws') {
|
|
35
|
+
return { appDefinition, provider: null, providerName: 'aws' };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// For other providers, resolve the package
|
|
39
|
+
const {
|
|
40
|
+
resolveProvider,
|
|
41
|
+
} = require('@friggframework/core/providers/resolve-provider');
|
|
42
|
+
|
|
43
|
+
const provider = resolveProvider(appDefinition);
|
|
44
|
+
return { appDefinition, provider, providerName };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Load the appDefinition from the backend directory.
|
|
49
|
+
* Tries backend/index.js then index.js in the current directory.
|
|
50
|
+
*
|
|
51
|
+
* @param {string} cwd
|
|
52
|
+
* @returns {Object|null}
|
|
53
|
+
*/
|
|
54
|
+
function loadCliAppDefinition(cwd) {
|
|
55
|
+
// Try backend/index.js first (standard Frigg app structure)
|
|
56
|
+
const backendIndexPath = path.join(cwd, 'backend', 'index.js');
|
|
57
|
+
const rootIndexPath = path.join(cwd, 'index.js');
|
|
58
|
+
|
|
59
|
+
for (const indexPath of [backendIndexPath, rootIndexPath]) {
|
|
60
|
+
if (fs.existsSync(indexPath)) {
|
|
61
|
+
try {
|
|
62
|
+
const exported = require(indexPath);
|
|
63
|
+
if (exported.Definition) {
|
|
64
|
+
return exported.Definition;
|
|
65
|
+
}
|
|
66
|
+
} catch {
|
|
67
|
+
// Failed to load, try next
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
module.exports = { loadProviderForCli, loadCliAppDefinition };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@friggframework/devtools",
|
|
3
3
|
"prettier": "@friggframework/prettier-config",
|
|
4
|
-
"version": "2.0.0--canary.545.
|
|
4
|
+
"version": "2.0.0--canary.545.c870571.0",
|
|
5
5
|
"bin": {
|
|
6
6
|
"frigg": "./frigg-cli/index.js"
|
|
7
7
|
},
|
|
@@ -25,9 +25,9 @@
|
|
|
25
25
|
"@babel/eslint-parser": "^7.18.9",
|
|
26
26
|
"@babel/parser": "^7.25.3",
|
|
27
27
|
"@babel/traverse": "^7.25.3",
|
|
28
|
-
"@friggframework/core": "2.0.0--canary.545.
|
|
29
|
-
"@friggframework/schemas": "2.0.0--canary.545.
|
|
30
|
-
"@friggframework/test": "2.0.0--canary.545.
|
|
28
|
+
"@friggframework/core": "2.0.0--canary.545.c870571.0",
|
|
29
|
+
"@friggframework/schemas": "2.0.0--canary.545.c870571.0",
|
|
30
|
+
"@friggframework/test": "2.0.0--canary.545.c870571.0",
|
|
31
31
|
"@hapi/boom": "^10.0.1",
|
|
32
32
|
"@inquirer/prompts": "^5.3.8",
|
|
33
33
|
"axios": "^1.7.2",
|
|
@@ -55,8 +55,8 @@
|
|
|
55
55
|
"validate-npm-package-name": "^5.0.0"
|
|
56
56
|
},
|
|
57
57
|
"devDependencies": {
|
|
58
|
-
"@friggframework/eslint-config": "2.0.0--canary.545.
|
|
59
|
-
"@friggframework/prettier-config": "2.0.0--canary.545.
|
|
58
|
+
"@friggframework/eslint-config": "2.0.0--canary.545.c870571.0",
|
|
59
|
+
"@friggframework/prettier-config": "2.0.0--canary.545.c870571.0",
|
|
60
60
|
"aws-sdk-client-mock": "^4.1.0",
|
|
61
61
|
"aws-sdk-client-mock-jest": "^4.1.0",
|
|
62
62
|
"exit-x": "^0.2.2",
|
|
@@ -89,5 +89,5 @@
|
|
|
89
89
|
"publishConfig": {
|
|
90
90
|
"access": "public"
|
|
91
91
|
},
|
|
92
|
-
"gitHead": "
|
|
92
|
+
"gitHead": "c8705715da2a28da19d7543735088d87660d0655"
|
|
93
93
|
}
|