@contentful/app-scripts 1.33.0-alpha.0 → 2.0.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/lib/bin.js +10 -0
- package/lib/generate-function/build-generate-function-settings.d.ts +3 -0
- package/lib/generate-function/build-generate-function-settings.js +153 -0
- package/lib/generate-function/clone.d.ts +14 -0
- package/lib/generate-function/clone.js +144 -0
- package/lib/generate-function/constants.d.ts +9 -0
- package/lib/generate-function/constants.js +14 -0
- package/lib/generate-function/create-function.d.ts +2 -0
- package/lib/generate-function/create-function.js +10 -0
- package/lib/generate-function/get-github-folder-names.d.ts +1 -0
- package/lib/generate-function/get-github-folder-names.js +25 -0
- package/lib/generate-function/index.d.ts +5 -0
- package/lib/generate-function/index.js +17 -0
- package/lib/generate-function/logger.d.ts +8 -0
- package/lib/generate-function/logger.js +54 -0
- package/lib/generate-function/types.d.ts +4 -0
- package/lib/generate-function/types.js +9 -0
- package/lib/generate-function/utils/file.d.ts +15 -0
- package/lib/generate-function/utils/file.js +51 -0
- package/lib/generate-function/utils/package.d.ts +5 -0
- package/lib/generate-function/utils/package.js +23 -0
- package/lib/index.d.ts +1 -0
- package/lib/index.js +3 -1
- package/lib/types.d.ts +18 -0
- package/lib/upload/create-zip-from-directory.d.ts +1 -1
- package/package.json +10 -7
package/lib/bin.js
CHANGED
|
@@ -83,6 +83,16 @@ async function runCommand(command, options) {
|
|
|
83
83
|
.action(async (options) => {
|
|
84
84
|
await runCommand(index_1.buildFunctions, options);
|
|
85
85
|
});
|
|
86
|
+
commander_1.program
|
|
87
|
+
.command('generate-function')
|
|
88
|
+
.description('Generate a new Contentful Function')
|
|
89
|
+
.option('--name <name>', 'Name of the function')
|
|
90
|
+
.option('--template <language>', 'Select a template and language for the function')
|
|
91
|
+
.option('--example <example_name>', 'Select an example function to generate')
|
|
92
|
+
.option('--language <language>', 'Select a language for the function')
|
|
93
|
+
.action(async (options) => {
|
|
94
|
+
await runCommand(index_1.generateFunction, options);
|
|
95
|
+
});
|
|
86
96
|
commander_1.program
|
|
87
97
|
.command('upsert-actions')
|
|
88
98
|
.description('Upsert Action(s) for an App')
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { GenerateFunctionSettings, GenerateFunctionOptions } from '../types';
|
|
2
|
+
export declare function buildGenerateFunctionSettings(): Promise<GenerateFunctionSettings>;
|
|
3
|
+
export declare function buildGenerateFunctionSettingsFromOptions(options: GenerateFunctionOptions): Promise<GenerateFunctionSettings>;
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.buildGenerateFunctionSettings = buildGenerateFunctionSettings;
|
|
7
|
+
exports.buildGenerateFunctionSettingsFromOptions = buildGenerateFunctionSettingsFromOptions;
|
|
8
|
+
const inquirer_1 = __importDefault(require("inquirer"));
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
10
|
+
const get_github_folder_names_1 = require("./get-github-folder-names");
|
|
11
|
+
const constants_1 = require("./constants");
|
|
12
|
+
const ora_1 = __importDefault(require("ora"));
|
|
13
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
14
|
+
const logger_1 = require("./logger");
|
|
15
|
+
async function buildGenerateFunctionSettings() {
|
|
16
|
+
const baseSettings = await inquirer_1.default.prompt([
|
|
17
|
+
{
|
|
18
|
+
name: 'name',
|
|
19
|
+
message: `Function name (${path_1.default.basename(process.cwd())}):`,
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
name: 'sourceType',
|
|
23
|
+
message: 'Do you want to start with a blank template or use one of our examples?',
|
|
24
|
+
type: 'list',
|
|
25
|
+
choices: [
|
|
26
|
+
{ name: 'Template', value: 'template' },
|
|
27
|
+
{ name: 'Example', value: 'example' },
|
|
28
|
+
],
|
|
29
|
+
default: 'template',
|
|
30
|
+
}
|
|
31
|
+
]);
|
|
32
|
+
if (constants_1.BANNED_FUNCTION_NAMES.includes(baseSettings.name)) {
|
|
33
|
+
throw new Error(`Invalid function name: ${baseSettings.name}`);
|
|
34
|
+
}
|
|
35
|
+
let sourceSpecificSettings;
|
|
36
|
+
if (baseSettings.sourceType === 'template') {
|
|
37
|
+
sourceSpecificSettings = await inquirer_1.default.prompt([
|
|
38
|
+
{
|
|
39
|
+
name: 'language',
|
|
40
|
+
message: 'Pick a template',
|
|
41
|
+
type: 'list',
|
|
42
|
+
choices: [
|
|
43
|
+
{ name: 'TypeScript', value: 'typescript' },
|
|
44
|
+
{ name: 'JavaScript', value: 'javascript' },
|
|
45
|
+
],
|
|
46
|
+
default: 'typescript',
|
|
47
|
+
}
|
|
48
|
+
]);
|
|
49
|
+
sourceSpecificSettings.sourceName = sourceSpecificSettings.language.toLowerCase();
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
const availableExamples = await (0, get_github_folder_names_1.getGithubFolderNames)();
|
|
53
|
+
const filteredExamples = availableExamples.filter((template) => constants_1.ACCEPTED_EXAMPLE_FOLDERS.includes(template));
|
|
54
|
+
sourceSpecificSettings = await inquirer_1.default.prompt([
|
|
55
|
+
{
|
|
56
|
+
name: 'sourceName',
|
|
57
|
+
message: 'Select an example:',
|
|
58
|
+
type: 'list',
|
|
59
|
+
choices: filteredExamples,
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
name: 'language',
|
|
63
|
+
message: 'Pick a template',
|
|
64
|
+
type: 'list',
|
|
65
|
+
choices: [
|
|
66
|
+
{ name: 'TypeScript', value: 'typescript' },
|
|
67
|
+
{ name: 'JavaScript', value: 'javascript' },
|
|
68
|
+
],
|
|
69
|
+
default: 'typescript',
|
|
70
|
+
}
|
|
71
|
+
]);
|
|
72
|
+
}
|
|
73
|
+
baseSettings.sourceName = sourceSpecificSettings.sourceName;
|
|
74
|
+
baseSettings.language = sourceSpecificSettings.language;
|
|
75
|
+
return baseSettings;
|
|
76
|
+
}
|
|
77
|
+
function validateArguments(options) {
|
|
78
|
+
const templateRequired = ['name', 'template'];
|
|
79
|
+
const exampleRequired = ['name', 'example', 'language'];
|
|
80
|
+
if (constants_1.BANNED_FUNCTION_NAMES.includes(options.name)) {
|
|
81
|
+
throw new Error(`Invalid function name: ${options.name}`);
|
|
82
|
+
}
|
|
83
|
+
if ('template' in options) {
|
|
84
|
+
if (!templateRequired.every((key) => key in options)) {
|
|
85
|
+
throw new Error('You must specify a function name and a template');
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
else if ('example' in options) {
|
|
89
|
+
if (!exampleRequired.every((key) => key in options)) {
|
|
90
|
+
throw new Error('You must specify a function name, an example, and a language');
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
throw new Error('You must specify either --template or --example');
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
async function buildGenerateFunctionSettingsFromOptions(options) {
|
|
98
|
+
const validateSpinner = (0, ora_1.default)('Validating your input\n').start();
|
|
99
|
+
const settings = {};
|
|
100
|
+
try {
|
|
101
|
+
validateArguments(options);
|
|
102
|
+
for (const key in options) { // convert all options to lowercase and trim
|
|
103
|
+
const optionKey = key;
|
|
104
|
+
options[optionKey] = options[optionKey].toLowerCase().trim();
|
|
105
|
+
}
|
|
106
|
+
if ('example' in options) {
|
|
107
|
+
if ('template' in options) {
|
|
108
|
+
throw new Error('Cannot specify both --template and --example');
|
|
109
|
+
}
|
|
110
|
+
if (!constants_1.ACCEPTED_EXAMPLE_FOLDERS.includes(options.example)) {
|
|
111
|
+
throw new Error(`Invalid example name: ${options.example}`);
|
|
112
|
+
}
|
|
113
|
+
if (!constants_1.ACCEPTED_LANGUAGES.includes(options.language)) {
|
|
114
|
+
(0, logger_1.warn)(`Invalid language: ${options.language}. Defaulting to TypeScript.`);
|
|
115
|
+
settings.language = 'typescript';
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
settings.language = options.language;
|
|
119
|
+
}
|
|
120
|
+
settings.sourceType = 'example';
|
|
121
|
+
settings.sourceName = options.example;
|
|
122
|
+
settings.name = options.name;
|
|
123
|
+
}
|
|
124
|
+
else if ('template' in options) {
|
|
125
|
+
if ('language' in options && options.language && options.language != options.template) {
|
|
126
|
+
console.warn(`Ignoring language option: ${options.language}. Defaulting to ${options.template}.`);
|
|
127
|
+
}
|
|
128
|
+
if (!constants_1.ACCEPTED_LANGUAGES.includes(options.template)) {
|
|
129
|
+
console.warn(`Invalid language: ${options.template}. Defaulting to TypeScript.`);
|
|
130
|
+
settings.language = 'typescript';
|
|
131
|
+
settings.sourceName = 'typescript';
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
settings.language = options.template;
|
|
135
|
+
settings.sourceName = options.template;
|
|
136
|
+
}
|
|
137
|
+
settings.sourceType = 'template';
|
|
138
|
+
settings.name = options.name;
|
|
139
|
+
}
|
|
140
|
+
return settings;
|
|
141
|
+
}
|
|
142
|
+
catch (err) {
|
|
143
|
+
console.log(`
|
|
144
|
+
${chalk_1.default.red('Validation failed')}
|
|
145
|
+
${err.message}
|
|
146
|
+
`);
|
|
147
|
+
// eslint-disable-next-line no-process-exit
|
|
148
|
+
process.exit(1);
|
|
149
|
+
}
|
|
150
|
+
finally {
|
|
151
|
+
validateSpinner.stop();
|
|
152
|
+
}
|
|
153
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { GenerateFunctionSettings } from '../types';
|
|
2
|
+
export declare function cloneFunction(localPath: string, settings: GenerateFunctionSettings): Promise<void>;
|
|
3
|
+
export declare function getCloneURL(settings: GenerateFunctionSettings): string;
|
|
4
|
+
export declare function touchupAppManifest(localPath: string, settings: GenerateFunctionSettings, renameFunctionFile: string): Promise<void>;
|
|
5
|
+
export declare function moveFilesToFinalDirectory(localTmpPath: string, localFunctionsPath: string): void;
|
|
6
|
+
export declare function renameClonedFiles(localTmpPath: string, settings: GenerateFunctionSettings): string;
|
|
7
|
+
export declare function resolvePaths(localPath: string): {
|
|
8
|
+
localTmpPath: string;
|
|
9
|
+
localFunctionsPath: string;
|
|
10
|
+
};
|
|
11
|
+
export declare function cloneAndResolveManifests(cloneURL: string, localTmpPath: string, localPath: string, localFunctionsPath: string): Promise<void>;
|
|
12
|
+
export declare function clone(cloneURL: string, localFunctionsPath: string): Promise<any>;
|
|
13
|
+
export declare function mergeAppManifest(localPath: string, localTmpPath: string): Promise<void>;
|
|
14
|
+
export declare function updatePackageJsonWithBuild(localPath: string, localTmpPath: string): Promise<void>;
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.cloneFunction = cloneFunction;
|
|
7
|
+
exports.getCloneURL = getCloneURL;
|
|
8
|
+
exports.touchupAppManifest = touchupAppManifest;
|
|
9
|
+
exports.moveFilesToFinalDirectory = moveFilesToFinalDirectory;
|
|
10
|
+
exports.renameClonedFiles = renameClonedFiles;
|
|
11
|
+
exports.resolvePaths = resolvePaths;
|
|
12
|
+
exports.cloneAndResolveManifests = cloneAndResolveManifests;
|
|
13
|
+
exports.clone = clone;
|
|
14
|
+
exports.mergeAppManifest = mergeAppManifest;
|
|
15
|
+
exports.updatePackageJsonWithBuild = updatePackageJsonWithBuild;
|
|
16
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
17
|
+
const tiged = require('tiged');
|
|
18
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
19
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
20
|
+
const path_1 = require("path");
|
|
21
|
+
const constants_1 = require("./constants");
|
|
22
|
+
const logger_1 = require("./logger");
|
|
23
|
+
const file_1 = require("./utils/file");
|
|
24
|
+
const package_1 = require("./utils/package");
|
|
25
|
+
const addBuildCommand = (0, package_1.getAddBuildCommandFn)({
|
|
26
|
+
name: 'build:functions',
|
|
27
|
+
command: 'contentful-app-scripts build-functions --ci',
|
|
28
|
+
});
|
|
29
|
+
async function cloneFunction(localPath, settings) {
|
|
30
|
+
try {
|
|
31
|
+
console.log((0, logger_1.highlight)(`---- Cloning function ${chalk_1.default.cyan(settings.name)}...`));
|
|
32
|
+
const { localTmpPath, localFunctionsPath } = resolvePaths(localPath);
|
|
33
|
+
const cloneURL = getCloneURL(settings);
|
|
34
|
+
await cloneAndResolveManifests(cloneURL, localTmpPath, localPath, localFunctionsPath);
|
|
35
|
+
// now rename the function file. Find the file with a .ts or .js extension
|
|
36
|
+
const renameFunctionFile = renameClonedFiles(localTmpPath, settings);
|
|
37
|
+
// copy the cloned files to the functions directory
|
|
38
|
+
moveFilesToFinalDirectory(localTmpPath, localFunctionsPath);
|
|
39
|
+
// now alter the app-manifest.json to point to the new function file
|
|
40
|
+
await touchupAppManifest(localPath, settings, renameFunctionFile);
|
|
41
|
+
}
|
|
42
|
+
catch (e) {
|
|
43
|
+
(0, logger_1.error)(`Failed to clone function ${(0, logger_1.highlight)(chalk_1.default.cyan(settings.name))}`, e);
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
function getCloneURL(settings) {
|
|
48
|
+
let cloneURL = `${constants_1.REPO_URL}/${settings.sourceName}`; // this is the default for template
|
|
49
|
+
if (settings.sourceType === 'example') {
|
|
50
|
+
cloneURL = `${constants_1.REPO_URL}/${settings.sourceName}/${settings.language}`;
|
|
51
|
+
}
|
|
52
|
+
return cloneURL;
|
|
53
|
+
}
|
|
54
|
+
async function touchupAppManifest(localPath, settings, renameFunctionFile) {
|
|
55
|
+
const appManifest = JSON.parse(node_fs_1.default.readFileSync(`${localPath}/${constants_1.CONTENTFUL_APP_MANIFEST}`, 'utf-8'));
|
|
56
|
+
const entry = appManifest["functions"][appManifest["functions"].length - 1];
|
|
57
|
+
entry.id = settings.name;
|
|
58
|
+
// the path always has a .js extension
|
|
59
|
+
entry.path = `./functions/${renameFunctionFile.replace('.ts', '.js')}`;
|
|
60
|
+
entry.entryFile = `./functions/${renameFunctionFile}`;
|
|
61
|
+
await node_fs_1.default.writeFileSync(`${localPath}/${constants_1.CONTENTFUL_APP_MANIFEST}`, JSON.stringify(appManifest, null, 2));
|
|
62
|
+
}
|
|
63
|
+
function moveFilesToFinalDirectory(localTmpPath, localFunctionsPath) {
|
|
64
|
+
node_fs_1.default.cpSync(localTmpPath, localFunctionsPath, { recursive: true });
|
|
65
|
+
node_fs_1.default.rmSync(localTmpPath, { recursive: true, force: true });
|
|
66
|
+
}
|
|
67
|
+
function renameClonedFiles(localTmpPath, settings) {
|
|
68
|
+
const files = node_fs_1.default.readdirSync(localTmpPath);
|
|
69
|
+
const functionFile = files.find((file) => file.endsWith('.ts') || file.endsWith('.js'));
|
|
70
|
+
if (!functionFile) {
|
|
71
|
+
throw new Error(`No function file found in ${localTmpPath}`);
|
|
72
|
+
}
|
|
73
|
+
const newFunctionFile = `${settings.name}.${settings.language === 'typescript' ? 'ts' : 'js'}`;
|
|
74
|
+
node_fs_1.default.renameSync(`${localTmpPath}/${functionFile}`, `${localTmpPath}/${newFunctionFile}`);
|
|
75
|
+
return newFunctionFile;
|
|
76
|
+
}
|
|
77
|
+
function resolvePaths(localPath) {
|
|
78
|
+
const localTmpPath = (0, path_1.resolve)(`${localPath}/tmp`); // we require a tmp directory because tiged overwrites all files in the target directory
|
|
79
|
+
const localFunctionsPath = (0, path_1.resolve)(`${localPath}/functions`);
|
|
80
|
+
return { localTmpPath, localFunctionsPath };
|
|
81
|
+
}
|
|
82
|
+
async function cloneAndResolveManifests(cloneURL, localTmpPath, localPath, localFunctionsPath) {
|
|
83
|
+
const tigedInstance = await clone(cloneURL, localTmpPath);
|
|
84
|
+
// merge the manifest from the template folder to the root folder
|
|
85
|
+
await mergeAppManifest(localPath, localTmpPath);
|
|
86
|
+
// modify package.json build commands
|
|
87
|
+
await updatePackageJsonWithBuild(localPath, localTmpPath);
|
|
88
|
+
// check if a tsconfig.json file exists already
|
|
89
|
+
const ignoredFiles = constants_1.IGNORED_CLONED_FILES;
|
|
90
|
+
const tsconfigExists = await (0, file_1.exists)(`${localFunctionsPath}/tsconfig.json`);
|
|
91
|
+
if (tsconfigExists) {
|
|
92
|
+
ignoredFiles.push('tsconfig.json');
|
|
93
|
+
}
|
|
94
|
+
// remove the cloned files that we've already merged
|
|
95
|
+
await tigedInstance.remove("unused_param", localTmpPath, {
|
|
96
|
+
action: 'remove',
|
|
97
|
+
files: ignoredFiles.map((fileName) => `${localTmpPath}/${fileName}`),
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
async function clone(cloneURL, localFunctionsPath) {
|
|
101
|
+
const tigedInstance = tiged(cloneURL, { mode: 'tar', disableCache: true, force: true });
|
|
102
|
+
await tigedInstance.clone(localFunctionsPath);
|
|
103
|
+
return tigedInstance;
|
|
104
|
+
}
|
|
105
|
+
async function mergeAppManifest(localPath, localTmpPath) {
|
|
106
|
+
const finalAppManifestType = await (0, file_1.exists)(`${localPath}/${constants_1.CONTENTFUL_APP_MANIFEST}`);
|
|
107
|
+
const tmpAppManifestType = await (0, file_1.whichExists)(localTmpPath, [constants_1.CONTENTFUL_APP_MANIFEST, constants_1.APP_MANIFEST]); // find the app manifest in the cloned files
|
|
108
|
+
if (!finalAppManifestType) {
|
|
109
|
+
await (0, file_1.mergeJsonIntoFile)({
|
|
110
|
+
source: `${localTmpPath}/${tmpAppManifestType}`,
|
|
111
|
+
destination: `${localPath}/${constants_1.CONTENTFUL_APP_MANIFEST}`, // always save as contentful-app-manifest.json
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
// add the function to the json's "functions" array
|
|
116
|
+
await (0, file_1.mergeJsonIntoFile)({
|
|
117
|
+
source: `${localTmpPath}/${tmpAppManifestType}`,
|
|
118
|
+
destination: `${localPath}/${constants_1.CONTENTFUL_APP_MANIFEST}`,
|
|
119
|
+
mergeFn: (destinationJson = {}, sourceJson = {}) => {
|
|
120
|
+
if (!destinationJson.functions) {
|
|
121
|
+
destinationJson.functions = [];
|
|
122
|
+
}
|
|
123
|
+
if (sourceJson.functions && sourceJson.functions.length > 0) {
|
|
124
|
+
destinationJson.functions.push(sourceJson.functions[0]);
|
|
125
|
+
}
|
|
126
|
+
return destinationJson;
|
|
127
|
+
},
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
async function updatePackageJsonWithBuild(localPath, localTmpPath) {
|
|
132
|
+
const packageJsonLocation = (0, path_1.resolve)(`${localPath}/package.json`);
|
|
133
|
+
const packageJsonExists = await (0, file_1.exists)(packageJsonLocation);
|
|
134
|
+
if (packageJsonExists) {
|
|
135
|
+
await (0, file_1.mergeJsonIntoFile)({
|
|
136
|
+
source: `${localTmpPath}/package.json`,
|
|
137
|
+
destination: packageJsonLocation,
|
|
138
|
+
mergeFn: addBuildCommand,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
(0, logger_1.warn)("Failed to add function build commands: ${packageJsonLocation} does not exist.");
|
|
143
|
+
}
|
|
144
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare const EXAMPLES_PATH = "contentful/apps/function-examples/";
|
|
2
|
+
export declare const APP_MANIFEST = "app-manifest.json";
|
|
3
|
+
export declare const CONTENTFUL_APP_MANIFEST = "contentful-app-manifest.json";
|
|
4
|
+
export declare const IGNORED_CLONED_FILES: string[];
|
|
5
|
+
export declare const REPO_URL = "https://github.com/contentful/apps/function-examples";
|
|
6
|
+
export declare const ACCEPTED_EXAMPLE_FOLDERS: string[];
|
|
7
|
+
export declare const ACCEPTED_LANGUAGES: string[];
|
|
8
|
+
export declare const CONTENTFUL_FUNCTIONS_EXAMPLE_REPO_PATH = "https://api.github.com/repos/contentful/apps/contents/function-examples";
|
|
9
|
+
export declare const BANNED_FUNCTION_NAMES: string[];
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.BANNED_FUNCTION_NAMES = exports.CONTENTFUL_FUNCTIONS_EXAMPLE_REPO_PATH = exports.ACCEPTED_LANGUAGES = exports.ACCEPTED_EXAMPLE_FOLDERS = exports.REPO_URL = exports.IGNORED_CLONED_FILES = exports.CONTENTFUL_APP_MANIFEST = exports.APP_MANIFEST = exports.EXAMPLES_PATH = void 0;
|
|
4
|
+
exports.EXAMPLES_PATH = 'contentful/apps/function-examples/';
|
|
5
|
+
exports.APP_MANIFEST = 'app-manifest.json';
|
|
6
|
+
exports.CONTENTFUL_APP_MANIFEST = 'contentful-app-manifest.json';
|
|
7
|
+
exports.IGNORED_CLONED_FILES = [exports.APP_MANIFEST, `package.json`];
|
|
8
|
+
exports.REPO_URL = 'https://github.com/contentful/apps/function-examples';
|
|
9
|
+
exports.ACCEPTED_EXAMPLE_FOLDERS = [
|
|
10
|
+
'appevent-handler',
|
|
11
|
+
];
|
|
12
|
+
exports.ACCEPTED_LANGUAGES = ['javascript', 'typescript'];
|
|
13
|
+
exports.CONTENTFUL_FUNCTIONS_EXAMPLE_REPO_PATH = 'https://api.github.com/repos/contentful/apps/contents/function-examples';
|
|
14
|
+
exports.BANNED_FUNCTION_NAMES = ['example', ''];
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.create = create;
|
|
4
|
+
const path_1 = require("path");
|
|
5
|
+
const clone_1 = require("./clone");
|
|
6
|
+
async function create(settings) {
|
|
7
|
+
const localPath = (0, path_1.resolve)(process.cwd());
|
|
8
|
+
await (0, clone_1.cloneFunction)(localPath, settings);
|
|
9
|
+
console.log(`Function "${settings.name}" created successfully`);
|
|
10
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function getGithubFolderNames(): Promise<string[]>;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.getGithubFolderNames = getGithubFolderNames;
|
|
7
|
+
const constants_1 = require("./constants");
|
|
8
|
+
const axios_1 = __importDefault(require("axios"));
|
|
9
|
+
const types_1 = require("./types");
|
|
10
|
+
async function getGithubFolderNames() {
|
|
11
|
+
try {
|
|
12
|
+
const response = await axios_1.default.get(constants_1.CONTENTFUL_FUNCTIONS_EXAMPLE_REPO_PATH);
|
|
13
|
+
const contents = response.data;
|
|
14
|
+
const filteredContents = contents.filter((content) => content.type === 'dir');
|
|
15
|
+
return filteredContents.map((content) => content.name);
|
|
16
|
+
}
|
|
17
|
+
catch (err) {
|
|
18
|
+
if (err instanceof types_1.HTTPResponseError) {
|
|
19
|
+
throw err;
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
throw new Error(`Failed to fetch Contentful app templates: ${err}`);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.generateFunction = void 0;
|
|
4
|
+
const build_generate_function_settings_1 = require("./build-generate-function-settings");
|
|
5
|
+
const create_function_1 = require("./create-function");
|
|
6
|
+
const interactive = async () => {
|
|
7
|
+
const generateFunctionSettings = await (0, build_generate_function_settings_1.buildGenerateFunctionSettings)();
|
|
8
|
+
return (0, create_function_1.create)(generateFunctionSettings);
|
|
9
|
+
};
|
|
10
|
+
const nonInteractive = async (options) => {
|
|
11
|
+
const generateFunctionSettings = await (0, build_generate_function_settings_1.buildGenerateFunctionSettingsFromOptions)(options);
|
|
12
|
+
return (0, create_function_1.create)(generateFunctionSettings);
|
|
13
|
+
};
|
|
14
|
+
exports.generateFunction = {
|
|
15
|
+
interactive,
|
|
16
|
+
nonInteractive,
|
|
17
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
export declare function warn(message: string): void;
|
|
3
|
+
export declare function error(message: string, error: unknown): void;
|
|
4
|
+
export declare function wrapInBlanks(message: string | chalk.Chalk): void;
|
|
5
|
+
export declare function highlight(str: string): string;
|
|
6
|
+
export declare function choice(str: string): string;
|
|
7
|
+
export declare function success(str: string): string;
|
|
8
|
+
export declare function code(str: string): string;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.warn = warn;
|
|
7
|
+
exports.error = error;
|
|
8
|
+
exports.wrapInBlanks = wrapInBlanks;
|
|
9
|
+
exports.highlight = highlight;
|
|
10
|
+
exports.choice = choice;
|
|
11
|
+
exports.success = success;
|
|
12
|
+
exports.code = code;
|
|
13
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
14
|
+
const types_1 = require("./types");
|
|
15
|
+
function warn(message) {
|
|
16
|
+
console.log(`${chalk_1.default.yellow('Warning:')} ${message}`);
|
|
17
|
+
}
|
|
18
|
+
function error(message, error) {
|
|
19
|
+
console.log(`${chalk_1.default.red('Error:')} ${message}`);
|
|
20
|
+
if (error === undefined) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
else if (error instanceof types_1.InvalidTemplateError || error instanceof types_1.HTTPResponseError) {
|
|
24
|
+
// for known errors, we just want to show the message
|
|
25
|
+
console.log();
|
|
26
|
+
console.log(error.message);
|
|
27
|
+
}
|
|
28
|
+
else if (error instanceof Error) {
|
|
29
|
+
console.log();
|
|
30
|
+
console.log(error);
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
const strigifiedError = String(error);
|
|
34
|
+
console.log();
|
|
35
|
+
console.log(`${strigifiedError.startsWith('Error: ') ? strigifiedError.substring(7) : strigifiedError}`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
function wrapInBlanks(message) {
|
|
39
|
+
console.log(' ');
|
|
40
|
+
console.log(message);
|
|
41
|
+
console.log(' ');
|
|
42
|
+
}
|
|
43
|
+
function highlight(str) {
|
|
44
|
+
return chalk_1.default.bold(str);
|
|
45
|
+
}
|
|
46
|
+
function choice(str) {
|
|
47
|
+
return chalk_1.default.cyan(str);
|
|
48
|
+
}
|
|
49
|
+
function success(str) {
|
|
50
|
+
return chalk_1.default.greenBright(str);
|
|
51
|
+
}
|
|
52
|
+
function code(str) {
|
|
53
|
+
return chalk_1.default.bold(str);
|
|
54
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.InvalidTemplateError = exports.HTTPResponseError = void 0;
|
|
4
|
+
class HTTPResponseError extends Error {
|
|
5
|
+
}
|
|
6
|
+
exports.HTTPResponseError = HTTPResponseError;
|
|
7
|
+
class InvalidTemplateError extends Error {
|
|
8
|
+
}
|
|
9
|
+
exports.InvalidTemplateError = InvalidTemplateError;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export declare function getJsonData(path: string | undefined): Promise<Record<string, any> | undefined>;
|
|
2
|
+
export type MergeJsonIntoFileOptions = {
|
|
3
|
+
destination: string;
|
|
4
|
+
source?: string;
|
|
5
|
+
mergeFn?: (destinationJson?: Record<string, any>, sourceJson?: Record<string, any>) => Record<string, any>;
|
|
6
|
+
};
|
|
7
|
+
export declare function mergeJsonIntoFile({ source, destination, mergeFn, }: MergeJsonIntoFileOptions): Promise<void>;
|
|
8
|
+
export declare function exists(path: string): Promise<boolean>;
|
|
9
|
+
/**
|
|
10
|
+
* Check a directory if two files exist, returning the first one that exists or "None"
|
|
11
|
+
* @param basePath Base path
|
|
12
|
+
* @param paths List of paths, in order of preference
|
|
13
|
+
* @returns First subpath if it exists, otherwise the second if it exists, otherwise "None"
|
|
14
|
+
*/
|
|
15
|
+
export declare function whichExists(basePath: string, paths: string[]): Promise<string>;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.getJsonData = getJsonData;
|
|
7
|
+
exports.mergeJsonIntoFile = mergeJsonIntoFile;
|
|
8
|
+
exports.exists = exists;
|
|
9
|
+
exports.whichExists = whichExists;
|
|
10
|
+
const merge_options_1 = __importDefault(require("merge-options"));
|
|
11
|
+
const promises_1 = require("fs/promises");
|
|
12
|
+
const path_1 = require("path");
|
|
13
|
+
async function getJsonData(path) {
|
|
14
|
+
if (!path) {
|
|
15
|
+
return undefined;
|
|
16
|
+
}
|
|
17
|
+
const normalizedPath = (0, path_1.resolve)(path);
|
|
18
|
+
if (!(await exists(normalizedPath))) {
|
|
19
|
+
return undefined;
|
|
20
|
+
}
|
|
21
|
+
return JSON.parse(await (0, promises_1.readFile)(normalizedPath, { encoding: 'utf-8' }));
|
|
22
|
+
}
|
|
23
|
+
async function mergeJsonIntoFile({ source, destination, mergeFn = merge_options_1.default.bind({ concatArrays: false }), }) {
|
|
24
|
+
const sourceJson = await getJsonData(source);
|
|
25
|
+
const destinationJson = await getJsonData(destination);
|
|
26
|
+
const mergedJson = mergeFn(destinationJson, sourceJson);
|
|
27
|
+
await (0, promises_1.writeFile)((0, path_1.resolve)(destination), JSON.stringify(mergedJson, null, ' '));
|
|
28
|
+
}
|
|
29
|
+
function exists(path) {
|
|
30
|
+
return (0, promises_1.access)(path)
|
|
31
|
+
.then(() => true)
|
|
32
|
+
.catch(() => false);
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Check a directory if two files exist, returning the first one that exists or "None"
|
|
36
|
+
* @param basePath Base path
|
|
37
|
+
* @param paths List of paths, in order of preference
|
|
38
|
+
* @returns First subpath if it exists, otherwise the second if it exists, otherwise "None"
|
|
39
|
+
*/
|
|
40
|
+
async function whichExists(basePath, paths) {
|
|
41
|
+
for (const path of paths) {
|
|
42
|
+
try {
|
|
43
|
+
await (0, promises_1.access)((0, path_1.join)(basePath, path));
|
|
44
|
+
return path;
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
// Ignore and continue checking the next path
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return "None";
|
|
51
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.getAddBuildCommandFn = getAddBuildCommandFn;
|
|
7
|
+
const merge_options_1 = __importDefault(require("merge-options"));
|
|
8
|
+
function getAddBuildCommandFn({ name, command }) {
|
|
9
|
+
return (packageJson, additionalProperties) => {
|
|
10
|
+
let destBuildCommand = packageJson?.scripts?.build ?? '';
|
|
11
|
+
const sourceBuildCommand = additionalProperties?.scripts?.build ?? command;
|
|
12
|
+
const triggerCommand = `npm run ${name}`;
|
|
13
|
+
if (destBuildCommand === '') {
|
|
14
|
+
destBuildCommand = triggerCommand;
|
|
15
|
+
}
|
|
16
|
+
else if (!destBuildCommand.split(/\s*&+\s*/).includes(triggerCommand)) {
|
|
17
|
+
destBuildCommand += ` && ${triggerCommand}`;
|
|
18
|
+
}
|
|
19
|
+
return (0, merge_options_1.default)({}, packageJson, additionalProperties, {
|
|
20
|
+
scripts: { [name]: sourceBuildCommand, build: destBuildCommand },
|
|
21
|
+
});
|
|
22
|
+
};
|
|
23
|
+
}
|
package/lib/index.d.ts
CHANGED
|
@@ -7,4 +7,5 @@ export { track } from './analytics';
|
|
|
7
7
|
export { feedback } from './feedback';
|
|
8
8
|
export { install } from './install';
|
|
9
9
|
export { buildFunctions } from './build-functions';
|
|
10
|
+
export { generateFunction } from './generate-function';
|
|
10
11
|
export { upsertActions } from './upsert-actions';
|
package/lib/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.upsertActions = exports.buildFunctions = exports.install = exports.feedback = exports.track = exports.open = exports.cleanup = exports.activate = exports.upload = exports.createAppDefinition = void 0;
|
|
3
|
+
exports.upsertActions = exports.generateFunction = exports.buildFunctions = exports.install = exports.feedback = exports.track = exports.open = exports.cleanup = exports.activate = exports.upload = exports.createAppDefinition = void 0;
|
|
4
4
|
var create_app_definition_1 = require("./create-app-definition");
|
|
5
5
|
Object.defineProperty(exports, "createAppDefinition", { enumerable: true, get: function () { return create_app_definition_1.createAppDefinition; } });
|
|
6
6
|
var upload_1 = require("./upload");
|
|
@@ -19,5 +19,7 @@ var install_1 = require("./install");
|
|
|
19
19
|
Object.defineProperty(exports, "install", { enumerable: true, get: function () { return install_1.install; } });
|
|
20
20
|
var build_functions_1 = require("./build-functions");
|
|
21
21
|
Object.defineProperty(exports, "buildFunctions", { enumerable: true, get: function () { return build_functions_1.buildFunctions; } });
|
|
22
|
+
var generate_function_1 = require("./generate-function");
|
|
23
|
+
Object.defineProperty(exports, "generateFunction", { enumerable: true, get: function () { return generate_function_1.generateFunction; } });
|
|
22
24
|
var upsert_actions_1 = require("./upsert-actions");
|
|
23
25
|
Object.defineProperty(exports, "upsertActions", { enumerable: true, get: function () { return upsert_actions_1.upsertActions; } });
|
package/lib/types.d.ts
CHANGED
|
@@ -71,3 +71,21 @@ export interface BuildFunctionsOptions {
|
|
|
71
71
|
esbuildConfig?: string;
|
|
72
72
|
watch?: boolean;
|
|
73
73
|
}
|
|
74
|
+
export type SourceType = 'template' | 'example';
|
|
75
|
+
export type Language = 'javascript' | 'typescript';
|
|
76
|
+
export type AcceptedFunctionExamples = 'appevent-handler';
|
|
77
|
+
export type SourceName = Language | AcceptedFunctionExamples;
|
|
78
|
+
export interface GenerateFunctionSettings {
|
|
79
|
+
name: string;
|
|
80
|
+
sourceType: SourceType;
|
|
81
|
+
sourceName: SourceName;
|
|
82
|
+
language: Language;
|
|
83
|
+
}
|
|
84
|
+
export type GenerateFunctionOptions = {
|
|
85
|
+
name: string;
|
|
86
|
+
} & ({
|
|
87
|
+
example: AcceptedFunctionExamples;
|
|
88
|
+
language: Language;
|
|
89
|
+
} | {
|
|
90
|
+
template: Language;
|
|
91
|
+
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare function createZipFileFromDirectory(path: string): Promise<Buffer | null>;
|
|
1
|
+
export declare function createZipFileFromDirectory(path: string): Promise<Buffer<ArrayBufferLike> | null>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@contentful/app-scripts",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "A collection of scripts for building Contentful Apps",
|
|
5
5
|
"author": "Contentful GmbH",
|
|
6
6
|
"license": "MIT",
|
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
"directory": "packages/contentful--app-scripts"
|
|
11
11
|
},
|
|
12
12
|
"engines": {
|
|
13
|
-
"node": ">=
|
|
14
|
-
"npm": ">=
|
|
13
|
+
"node": ">=18",
|
|
14
|
+
"npm": ">=9"
|
|
15
15
|
},
|
|
16
16
|
"main": "lib/index.js",
|
|
17
17
|
"types": "lib/index.d.ts",
|
|
@@ -51,20 +51,23 @@
|
|
|
51
51
|
"@esbuild-plugins/node-modules-polyfill": "^0.2.2",
|
|
52
52
|
"@segment/analytics-node": "^2.0.0",
|
|
53
53
|
"adm-zip": "0.5.16",
|
|
54
|
+
"axios": "^1.7.9",
|
|
54
55
|
"bottleneck": "2.19.5",
|
|
55
56
|
"chalk": "4.1.2",
|
|
56
57
|
"commander": "12.1.0",
|
|
57
|
-
"contentful-management": "11.47.
|
|
58
|
+
"contentful-management": "11.47.2",
|
|
58
59
|
"dotenv": "16.4.7",
|
|
59
60
|
"esbuild": "^0.25.0",
|
|
60
61
|
"ignore": "7.0.3",
|
|
61
62
|
"inquirer": "8.2.6",
|
|
62
63
|
"lodash": "4.17.21",
|
|
64
|
+
"merge-options": "^3.0.4",
|
|
63
65
|
"open": "8.4.2",
|
|
64
66
|
"ora": "5.4.1",
|
|
67
|
+
"tiged": "^2.12.7",
|
|
65
68
|
"zod": "^3.24.1"
|
|
66
69
|
},
|
|
67
|
-
"gitHead": "
|
|
70
|
+
"gitHead": "b978a5b14bd327dafbd4349fbc6b624eaffb5e05",
|
|
68
71
|
"devDependencies": {
|
|
69
72
|
"@types/adm-zip": "0.5.7",
|
|
70
73
|
"@types/analytics-node": "3.1.14",
|
|
@@ -73,12 +76,12 @@
|
|
|
73
76
|
"@types/lodash": "4.17.15",
|
|
74
77
|
"@types/mocha": "10.0.10",
|
|
75
78
|
"@types/proxyquire": "1.3.31",
|
|
76
|
-
"@types/sinon": "17.0.
|
|
79
|
+
"@types/sinon": "17.0.4",
|
|
77
80
|
"chai": "4.5.0",
|
|
78
81
|
"mocha": "10.8.2",
|
|
79
82
|
"proxyquire": "2.1.3",
|
|
80
83
|
"sinon": "19.0.2",
|
|
81
|
-
"ts-mocha": "
|
|
84
|
+
"ts-mocha": "11.1.0",
|
|
82
85
|
"ts-node": "10.9.2"
|
|
83
86
|
}
|
|
84
87
|
}
|