Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion packages/add-on-manifest/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@
"build:release": "rushx _prebuild && tsc",
"build:watch": "rushx _prebuild && tsc --watch",
"clean": "rimraf coverage dist src/generated",
"test": "c8 mocha && c8 check-coverage"
"test": "c8 mocha && c8 check-coverage",
"copy-schema": "node scripts/copy-schema.js"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This creates a logical cycle. The @adobe/ccweb-add-on-manifest package sits at the very bottom of the dependency graph, where the other packages like @adobe/create-ccweb-add-on depend on it.
So, the copy-schema.js script from add-on-manifest writing to create-ccweb-add-on creates this cycle.
Please remove the changes made in the add-on-manifest package, skipping the copy-schema step.
It would be great, though if we could automatically generate express-add-on-manifest.schema.json. Could be a follow-up.

Copy link
Author

@prasanta352 prasanta352 Aug 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it, thanks @xerxovksi As you mentioned, I have reverted the changes in @adobe/ccweb-add-on-manifest and skip the copy-schema step to avoid the cycle.

I also agree it would be good to auto-generate express-add-on-manifest.schema.json. Updating the docs to include optional types would be the prerequisite, otherwise the schema wouldn’t be accurate.

On which step do you think the auto-generation should happen once the docs are updated?

},
"dependencies": {
"tslib": "2.7.0",
Expand Down
109 changes: 109 additions & 0 deletions packages/add-on-manifest/scripts/copy-schema.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import { writeFile, mkdir, readFile } from 'fs/promises';
import path from 'path';
import { fileURLToPath } from 'url';
import { existsSync } from 'fs';

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

/**
* Get all template directories
* @returns {string[]} Array of template directory paths
*/
function getTemplatePaths() {
const baseDir = path.resolve(__dirname, '../../create-ccweb-add-on/templates');
const templateNames = [
'javascript-with-document-sandbox',
'javascript',
'react-javascript-with-document-sandbox',
'react-javascript',
'react-typescript-with-document-sandbox',
'react-typescript',
'swc-javascript-with-document-sandbox',
'swc-javascript',
'swc-typescript-with-document-sandbox',
'swc-typescript'
];

return templateNames.map(name => path.join(baseDir, name, 'template'));
}

/**
* Update VSCode settings.json with schema configuration
* @param {string} vscodeDir - The .vscode directory path
* @returns {Promise<void>}
*/
async function updateVSCodeSettings(vscodeDir) {
const settingsPath = path.join(vscodeDir, 'settings.json');
let settings = {};

// Try to read existing settings
if (existsSync(settingsPath)) {
try {
const existingContent = await readFile(settingsPath, 'utf8');
settings = JSON.parse(existingContent);
} catch (error) {
console.warn(`Warning: Could not parse existing settings.json at ${settingsPath}`);
}
}

// Update json.schemas
if (!settings['json.schemas']) {
settings['json.schemas'] = [];
}

// Remove existing express-add-on-manifest schema if present
settings['json.schemas'] = settings['json.schemas'].filter(schema =>
!schema.url || !schema.url.includes('express-add-on-manifest.schema.json')
);

// Add new schema configuration
settings['json.schemas'].push({
"fileMatch": ["manifest.json"],
"url": ".vscode/express-add-on-manifest.schema.json"
});

await writeFile(settingsPath, JSON.stringify(settings, null, 2));
}

/**
* Main function to copy schema to templates
* @returns {Promise<void>}
*/
async function main() {
// Check if schema file exists
const schemaSourcePath = path.join(__dirname, 'express-add-on-manifest.schema.json');
if (!existsSync(schemaSourcePath)) {
console.error('Schema file not found at:', schemaSourcePath);
console.log('Please run "npm run generate-schema" first to create the schema file.');
process.exit(1);
}

console.log("Reading existing schema file...");
const schemaContent = await readFile(schemaSourcePath, 'utf8');

console.log("Copying schema to all template .vscode folders...");
const templatePaths = getTemplatePaths();

for (const templatePath of templatePaths) {
const vscodeDir = path.join(templatePath, '.vscode');

// Create .vscode directory if it doesn't exist
if (!existsSync(vscodeDir)) {
await mkdir(vscodeDir, { recursive: true });
}

// Write schema file
const schemaPath = path.join(vscodeDir, 'express-add-on-manifest.schema.json');
await writeFile(schemaPath, schemaContent);

// Update settings.json
await updateVSCodeSettings(vscodeDir);

console.log(`✓ Updated ${path.relative(process.cwd(), templatePath)}`);
}

console.log("Schema copied to all templates successfully!");
}

main().catch(console.error);
152 changes: 152 additions & 0 deletions packages/add-on-manifest/scripts/express-add-on-manifest.schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Adobe Express Add-on Manifest",
"description": "Manifest file that defines the metadata for your add-on and how it should behave",
"type": "object",
"additionalProperties": false,
"properties": {
"testId": {
"type": "string",
"description": "Used to uniquely identify an add-on among other add-ons during development workflows only. This is auto-generated and inserted into the manifest by the CLI when an add-on is created. This is mandatory in the development workflow and ignored in add-ons submitted to the marketplace."
},
"name": {
"type": "string",
"description": "Add-on name for development workflow only. The name provided in the UI during submission is used in all other workflows."
},
"version": {
"type": "string",
"pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+$",
"description": "Add-on version in 'major.minor.patch' format (e.g. '1.2.0')"
},
"manifestVersion": {
"type": "number",
"description": "Version of the manifest schema (e.g. 2)"
},
"requirements": {
"type": "object",
"description": "Specify the apps the add-on is applicable for",
"additionalProperties": false,
"properties": {
"apps": {
"type": "array",
"description": "Add-on authors can specify the apps that the add-on is intended for",
"minItems": 1,
"items": {
"type": "object",
"additionalProperties": false,
"properties": {
"name": {
"type": "string",
"enum": ["Express"],
"description": "Currently supported values: 'Express'"
},
"apiVersion": {
"type": "number",
"enum": [1],
"description": "API version that the add-on uses. Currently supported values: 1"
},
"supportedDeviceClass": {
"type": "array",
"description": "Supported platforms by the add-on. If not specified, the default value assumed is: ['desktop']",
"items": {
"type": "string",
"enum": ["desktop"],
"description": "Browser on desktop"
}
}
},
"required": ["name", "apiVersion"]
}
},
"experimentalApis": {
"type": "boolean",
"description": "Add-ons can opt to use experimental apis by specifying this flag. This flag is only allowed during development and needs to be removed during submission."
},
"supportsTouch": {
"type": "boolean",
"description": "Whether the add-on supports touch-only devices. If not specified, the default value assumed is false."
},
"renditionPreview": {
"type": "boolean",
"description": "Ensure premium content preview is properly handled for free users when add-ons create renditions."
}
},
"required": ["apps"]
},
"entryPoints": {
"type": "array",
"description": "An entry point for your add-on. At least one is required.",
"minItems": 1,
"items": {
"type": "object",
"additionalProperties": false,
"properties": {
"type": {
"type": "string",
"enum": ["panel"],
"description": "The type of the entry point. Currently supported values: 'panel'"
},
"id": {
"type": "string",
"description": "Identifier for the entry point. Must be unique within the add-on."
},
"main": {
"type": "string",
"description": "Main file for this entry point when launched."
},
"documentSandbox": {
"type": "string",
"description": "File containing the JavaScript code to use with the document sandbox SDK and Adobe Express Document APIs."
},
"permissions": {
"type": "object",
"description": "The permissions defined for this entry point",
"additionalProperties": false,
"properties": {
"sandbox": {
"type": "array",
"description": "List of iframe sandbox permissions",
"items": {
"type": "string",
"enum": [
"allow-popups",
"allow-popups-to-escape-sandbox",
"allow-presentation",
"allow-downloads"
],
"description": "allow-popups: Allows popups (such as window.open(), target='_blank', or showModalDialog()). Note: If this permission is not set and you're using popups, the popup will silently fail to open. \n allow-popups-to-escape-sandbox: Allows a sandboxed document to open new windows without forcing the sandboxing flags upon them \n allow-presentation: Lets the add-on start a presentation session \n allow-downloads: Allows downloading files through an <a> or <area> element with the download attribute, as well as through the navigation that leads to a download of a file"
}
},
"oauth": {
"type": "array",
"description": "List of 3rd party auth server domains for which OAuth workflow may be requested. eg. [\"www.dropbox.com\"]",
"items": {
"type": "string",
"format": "hostname"
}
},
"clipboard": {
"type": "array",
"description": "The allowed values for the list of clipboard permissions. Currently, clipboard-write is supported and allows an add-on to write arbitrary data to the clipboard",
"items": {
"type": "string",
"enum": ["clipboard-write"]
}
},
"microphone": {
"type": ["string", "boolean"],
"description": "The allowlists value used in iframe 'allow' attribute. Enables the add-on to access the user's microphone for audio input."
},
"camera": {
"type": ["string", "boolean"],
"description": "The allowlists value used in iframe 'allow' attribute. Enables the add-on to access the user's camera for video input."
}
}
}
},
"required": ["type", "id", "main"]
}
}
},
"required": ["version", "manifestVersion", "requirements", "entryPoints"]
}
Loading