Skip to content

Commit 1002589

Browse files
committed
Merge branch 'personalize-manifest'
2 parents e3ddc29 + 940eef2 commit 1002589

10 files changed

Lines changed: 336 additions & 132 deletions

File tree

packages/office-addin-manifest/.vscode/launch.json

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,21 @@
55
"version": "0.2.0",
66
"configurations": [
77
{
8-
"name": "Command Line",
8+
"name": "Command Line: Info",
99
"type": "node",
1010
"request": "launch",
11-
"program": "${workspaceFolder}/packages/office-addin-manifest/lib/manifest.js",
11+
"program": "${workspaceFolder}/lib/manifest.js",
1212
"args": [
13-
"info", "${workspaceFolder}\\packages\\office-addin-manifest\\test\\manifest.xml"
13+
"info", "${workspaceFolder}\\test\\manifests\\manifest.xml"
14+
]
15+
},
16+
{
17+
"name": "Command Line: Modify",
18+
"type": "node",
19+
"request": "launch",
20+
"program": "${workspaceFolder}/lib/manifest.js",
21+
"args": [
22+
"modify", "${workspaceFolder}\\test\\manifests\\manifest.xml", "-g", "bfb2fbf0-c71c-11e8-9c8a-d321d86ae926", "-d", "TestDisplayName"
1423
]
1524
}
1625
]

packages/office-addin-manifest/package.json

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@
2424
"fs": "0.0.1-security",
2525
"node-fetch": "^2.2.0",
2626
"path": "^0.12.7",
27-
"xml2js": "^0.4.19"
27+
"xml2js": "^0.4.19",
28+
"uuid": "^3.3.2"
2829
},
2930
"devDependencies": {
3031
"@types/es6-promise": "0.0.32",
@@ -33,11 +34,14 @@
3334
"@types/node-fetch": "^2.1.2",
3435
"@types/xml2js": "^0.4.3",
3536
"concurrently": "^3.6.1",
37+
"copy-dir": "^0.4.0",
38+
"fs-extra": "^7.0.0",
3639
"mocha": "^5.2.0",
3740
"rimraf": "^2.6.2",
3841
"ts-node": "^7.0.1",
3942
"tslint": "^5.11.0",
40-
"typescript": "^3.0.3"
43+
"typescript": "^3.0.3",
44+
"validator": "^10.8.0"
4145
},
4246
"repository": {
4347
"type": "git",
Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,41 @@
11
// Copyright (c) Microsoft Corporation. All rights reserved.
22
// Licensed under the MIT license.
33

4-
import { readManifestFile } from "./manifest";
4+
import * as commnder from "commander";
5+
import * as manifestInfo from "./manifestInfo";
56

67
export async function info(path: string) {
78
try {
8-
const manifest = await readManifestFile(path);
9-
10-
console.log(`Manifest: ${path}`);
11-
console.log(` Id: ${manifest.id || ""}`);
12-
console.log(` Name: ${manifest.displayName || ""}`);
13-
console.log(` Provider: ${manifest.providerName || ""}`);
14-
console.log(` Type: ${manifest.officeAppType || ""}`);
15-
console.log(` Version: ${manifest.version || ""}`);
16-
console.log(` Default Locale: ${manifest.defaultLocale || ""}`);
17-
console.log(` Description: ${manifest.description || ""}`);
9+
const manifest = await manifestInfo.readManifestFile(path);
10+
logManifestInfo(path, manifest);
1811
} catch (err) {
1912
console.error(`Error: ${err}`);
2013
}
2114
}
15+
16+
function logManifestInfo(path: string, manifest: manifestInfo.ManifestInfo) {
17+
console.log(`Manifest: ${path}`);
18+
console.log(` Id: ${manifest.id || ""}`);
19+
console.log(` Name: ${manifest.displayName || ""}`);
20+
console.log(` Provider: ${manifest.providerName || ""}`);
21+
console.log(` Type: ${manifest.officeAppType || ""}`);
22+
console.log(` Version: ${manifest.version || ""}`);
23+
console.log(` Default Locale: ${manifest.defaultLocale || ""}`);
24+
console.log(` Description: ${manifest.description || ""}`);
25+
}
26+
27+
export async function modify(path: string, command: commnder.Command) {
28+
try {
29+
const guid: string | undefined = (command.guid) ? command.guid : undefined;
30+
const displayName: string | undefined = (command.displayName) ? command.displayName : undefined;
31+
32+
if (guid === undefined && displayName === undefined) {
33+
throw new Error("You need to specify something to change in the manifest.");
34+
}
35+
36+
const manifest = await manifestInfo.modifyManifestFile(path, guid, displayName);
37+
logManifestInfo(path, manifest);
38+
} catch (err) {
39+
console.error(`Error: ${err}`);
40+
}
41+
}

packages/office-addin-manifest/src/manifest.ts

Lines changed: 7 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -4,98 +4,18 @@
44
// Licensed under the MIT license.
55

66
import * as commander from "commander";
7-
import * as fs from "fs";
8-
import * as xml2js from "xml2js";
97
import * as commands from "./commands";
108

11-
export class ManifestInfo {
12-
public id?: string;
13-
public defaultLocale?: string;
14-
public description?: string;
15-
public displayName?: string;
16-
public officeAppType?: string;
17-
public providerName?: string;
18-
public version?: string;
19-
}
20-
21-
function parseManifest(xml: any): ManifestInfo {
22-
const manifest: ManifestInfo = { };
23-
const officeApp = xml.OfficeApp;
24-
25-
manifest.id = xmlElementValue(officeApp, "Id");
26-
manifest.officeAppType = xmlAttributeValue(officeApp, "xsi:type");
27-
manifest.defaultLocale = xmlElementValue(officeApp, "DefaultLocale");
28-
manifest.description = xmlElementAttributeValue(officeApp, "Description");
29-
manifest.displayName = xmlElementAttributeValue(officeApp, "DisplayName");
30-
manifest.providerName = xmlElementValue(officeApp, "ProviderName");
31-
manifest.version = xmlElementValue(officeApp, "Version");
32-
33-
return manifest;
34-
}
35-
36-
export function readManifestFile(manifestPath: string): Promise<ManifestInfo> {
37-
return new Promise(async function(resolve, reject) {
38-
if (manifestPath) {
39-
try {
40-
fs.readFile(manifestPath, function(readError, fileData) {
41-
if (readError) {
42-
reject(`Unable to read the manifest file: ${manifestPath}. \n${readError}`);
43-
} else {
44-
// tslint:disable-next-line:only-arrow-functions
45-
xml2js.parseString(fileData, function(parseError, result) {
46-
if (parseError) {
47-
reject(`Unable to parse the manifest file: ${manifestPath}. \n${parseError}`);
48-
} else {
49-
try {
50-
const manifest: ManifestInfo = parseManifest(result);
51-
resolve (manifest);
52-
} catch (err) {
53-
reject(`Unable to parse the manifest file: ${manifestPath}. \n${err}`);
54-
}
55-
}
56-
});
57-
}
58-
});
59-
} catch (err) {
60-
return reject(`Unable to read the manifest file: ${manifestPath}. \n${err}`);
61-
}
62-
} else {
63-
reject(`Please provide the path to the manifest file.`);
64-
}
65-
});
66-
}
67-
68-
function xmlAttributeValue(xml: any, name: string): string | undefined {
69-
try {
70-
return xml.$[name];
71-
} catch (err) {
72-
// console.error(`Unable to get xml attribute value "${name}". ${err}`);
73-
}
74-
}
75-
76-
function xmlElementAttributeValue(xml: any, elementName: string, attributeName: string = "DefaultValue"): string | undefined {
77-
const element = xmlElementValue(xml, elementName);
78-
if (element) {
79-
return xmlAttributeValue(element, attributeName);
80-
}
81-
}
82-
83-
function xmlElementValue(xml: any, name: string): string | undefined {
84-
try {
85-
const element = xml[name];
86-
87-
if (element) {
88-
return element[0];
89-
}
90-
} catch (err) {
91-
// console.error(`Unable to get xml element value "${name}". ${err}`);
92-
}
93-
}
94-
959
if (process.argv[1].endsWith("\\manifest.js")) {
9610
commander
97-
.command("info [path]")
11+
.command("info <path>")
9812
.action(commands.info);
9913

14+
commander
15+
.command("modify <path>")
16+
.option("-g,--guid [guid]", "Change the guid. Specify \"random\" for a random guid.")
17+
.option("-d, --displayName [name]", "Display name for the add-in.")
18+
.action(commands.modify);
19+
10020
commander.parse(process.argv);
10121
}
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
import * as fs from "fs";
2+
import * as util from "util";
3+
import * as xml2js from "xml2js";
4+
import * as xmlMethods from "./xml";
5+
const readFileAsync = util.promisify(fs.readFile);
6+
const uuid = require('uuid/v1');
7+
const writeFileAsync = util.promisify(fs.writeFile);
8+
type Xml = any;
9+
10+
export class ManifestInfo {
11+
public id?: string;
12+
public defaultLocale?: string;
13+
public description?: string;
14+
public displayName?: string;
15+
public officeAppType?: string;
16+
public providerName?: string;
17+
public version?: string;
18+
}
19+
20+
function parseManifest(xml: Xml): ManifestInfo {
21+
const manifest: ManifestInfo = { };
22+
const officeApp = xml.OfficeApp;
23+
24+
manifest.id = xmlMethods.getXmlElementValue(officeApp, "Id");
25+
manifest.officeAppType = xmlMethods.getXmlAttributeValue(officeApp, "xsi:type");
26+
manifest.defaultLocale = xmlMethods.getXmlElementValue(officeApp, "DefaultLocale");
27+
manifest.description = xmlMethods.getXmlElementAttributeValue(officeApp, "Description");
28+
manifest.displayName = xmlMethods.getXmlElementAttributeValue(officeApp, "DisplayName");
29+
manifest.providerName = xmlMethods.getXmlElementValue(officeApp, "ProviderName");
30+
manifest.version = xmlMethods.getXmlElementValue(officeApp, "Version");
31+
32+
return manifest;
33+
}
34+
35+
export async function modifyManifestFile(manifestPath: string, guid?: string, displayName?: string): Promise<ManifestInfo> {
36+
let manifestData: ManifestInfo = {};
37+
if (manifestPath) {
38+
if (!guid && !displayName) {
39+
throw new Error("You need to specify something to change in the manifest.");
40+
} else {
41+
manifestData = await modifyManifestXml(manifestPath, guid, displayName);
42+
await writeManifestData(manifestPath, manifestData);
43+
return await readManifestFile(manifestPath);
44+
}
45+
} else {
46+
throw new Error(`Please provide the path to the manifest file.`);
47+
}
48+
}
49+
50+
async function modifyManifestXml(manifestPath: string, guid?: string, displayName?: string): Promise<Xml> {
51+
try {
52+
const manifestXml: Xml = await readXmlFromManifestFile(manifestPath);
53+
setModifiedXmlData(manifestXml.OfficeApp, guid, displayName);
54+
return manifestXml;
55+
} catch (err) {
56+
throw new Error(`Unable to modify xml data for manifest file: ${manifestPath}. \n${err}`);
57+
}
58+
}
59+
60+
async function parseXmlAsync(xmlString: string, manifestPath: string): Promise<Xml> {
61+
return new Promise(async function(resolve, reject) {
62+
xml2js.parseString(xmlString, function(parseError, xml) {
63+
if (parseError) {
64+
reject(new Error(`Unable to parse the manifest file: ${manifestPath}. \n${parseError}`));
65+
} else {
66+
resolve(xml);
67+
}
68+
});
69+
});
70+
}
71+
72+
export async function readManifestFile(manifestPath: string): Promise<ManifestInfo> {
73+
if (manifestPath) {
74+
const xml = await readXmlFromManifestFile(manifestPath);
75+
const manifest: ManifestInfo = parseManifest(xml);
76+
return manifest;
77+
} else {
78+
throw new Error(`Please provide the path to the manifest file.`);
79+
}
80+
}
81+
82+
async function readXmlFromManifestFile(manifestPath: string): Promise<Xml> {
83+
const fileData: string = await readFileAsync(manifestPath, {encoding: "utf8"});
84+
const xml = await parseXmlAsync(fileData, manifestPath);
85+
return xml;
86+
}
87+
88+
function setModifiedXmlData(xml: any, guid: string | undefined, displayName: string | undefined) {
89+
if (guid) {
90+
if (guid === "random") {
91+
guid = uuid();
92+
}
93+
xmlMethods.setXmlElementValue(xml, "Id", guid);
94+
}
95+
96+
if (displayName) {
97+
xmlMethods.setXmlElementAttributeValue(xml, "DisplayName", displayName);
98+
}
99+
}
100+
101+
async function writeManifestData(manifestPath: string, manifestData: any): Promise<void> {
102+
let xml: Xml;
103+
104+
try {
105+
// Generate xml for the manifest data.
106+
const builder = new xml2js.Builder();
107+
xml = builder.buildObject(manifestData);
108+
} catch (err) {
109+
throw new Error(`Unable to generate xml for the manifest.\n${err}`);
110+
}
111+
112+
try {
113+
// Write the xml back to the manifest file.
114+
await writeFileAsync(manifestPath, xml);
115+
} catch (err) {
116+
throw new Error(`Unable to write to file. ${manifestPath} \n${err}`);
117+
}
118+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
export function getXmlAttributeValue(xml: any, name: string): string | undefined {
2+
try {
3+
return xml.$[name];
4+
} catch (err) {
5+
// reading xml values is resilient to errors but you can uncomment the next line for debugging if attributes are missing
6+
// console.error(`Unable to get xml attribute value "${name}". ${err}`);
7+
}
8+
}
9+
10+
export function getXmlElementAttributeValue(xml: any, elementName: string, attributeName: string = "DefaultValue"): string | undefined {
11+
const element = getXmlElementValue(xml, elementName);
12+
if (element) {
13+
return getXmlAttributeValue(element, attributeName);
14+
}
15+
}
16+
17+
export function getXmlElementValue(xml: any, name: string): string | undefined {
18+
try {
19+
const element = xml[name];
20+
21+
if (element) {
22+
return element[0];
23+
}
24+
} catch (err) {
25+
// reading xml values is resilient to errors but you can uncomment the next line for debugging if elements are missing
26+
// console.error(`Unable to get xml element value "${name}". ${err}`);
27+
}
28+
}
29+
30+
export function setXmlElementAttributeValue(xml: any, elementName: string, input: string | undefined, attributeName: string = "DefaultValue") {
31+
xml[elementName][0].$[attributeName] = input;
32+
}
33+
34+
export function setXmlElementValue(xml: any, elementName: string, input: any) {
35+
xml[elementName] = input;
36+
}

packages/office-addin-manifest/test/manifest.incorrect-end-tag.xml renamed to packages/office-addin-manifest/test/manifests/manifest.incorrect-end-tag.xml

File renamed without changes.

packages/office-addin-manifest/test/manifest.no-description.xml renamed to packages/office-addin-manifest/test/manifests/manifest.no-description.xml

File renamed without changes.
File renamed without changes.

0 commit comments

Comments
 (0)