Skip to content

Commit 97f6ac4

Browse files
committed
init
1 parent bb462fe commit 97f6ac4

File tree

8 files changed

+235
-131
lines changed

8 files changed

+235
-131
lines changed

app/exec/extension/_lib/interfaces.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,19 @@ export interface MergeSettings {
135135
*/
136136
root: string;
137137

138+
/*
139+
* Manifest in the form of a standard Node.js CommonJS module with an exported function.
140+
* The function takes an environment as a parameter and must return the manifest JSON object.
141+
* Environment variables are specified with the env command line parameter.
142+
* If this is present then manifests, manifestGlobs, json5, override, and overridesFile are ignored.
143+
*/
144+
manifestJs: string;
145+
146+
/*
147+
* A series of environment variables that are passed to the function exported from the manifestJs module.
148+
*/
149+
env: string[];
150+
138151
/*
139152
* List of paths to manifest files
140153
*/

app/exec/extension/_lib/merger.ts

Lines changed: 152 additions & 126 deletions
Original file line numberDiff line numberDiff line change
@@ -98,17 +98,43 @@ export class Merger {
9898
}
9999
}
100100

101+
private loadManifestJs(): any {
102+
trace.debug("merger.manifestJs");
103+
104+
// build environment object from --env parameter
105+
const env = {};
106+
(this.settings.env || []).forEach(kvp => {
107+
const [key, ...value] = kvp.split('=');
108+
env[key] = value.join('=');
109+
});
110+
111+
const fullJsFile = path.resolve(this.settings.manifestJs);
112+
const manifestModuleFn = require(fullJsFile);
113+
if (!manifestModuleFn || typeof manifestModuleFn != "function") {
114+
throw new Error(`Missing export function from manifest-js file ${fullJsFile}`)
115+
}
116+
const manifestData = manifestModuleFn(env);
117+
if (!manifestData) {
118+
throw new Error(`The export function from manifest-js file ${fullJsFile} must return the manifest object`)
119+
}
120+
return manifestData;
121+
}
122+
101123
/**
102124
* Finds all manifests and merges them into two JS Objects: vsoManifest and vsixManifest
103125
* @return Q.Promise<SplitManifest> An object containing the two manifests
104126
*/
105-
public merge(): Promise<VsixComponents> {
127+
public async merge(): Promise<VsixComponents> {
106128
trace.debug("merger.merge");
107129

108-
return this.gatherManifests().then(files => {
109-
let overridesProvided = false;
110-
const manifestPromises: Promise<any>[] = [];
111-
files.forEach(file => {
130+
const manifestPromises: Promise<any>[] = [];
131+
let overridesProvided = false;
132+
133+
if (this.settings.manifestJs) {
134+
manifestPromises.push(Promise.resolve(this.loadManifestJs()));
135+
} else {
136+
let manifestFiles = await this.gatherManifests();
137+
manifestFiles.forEach(file => {
112138
manifestPromises.push(
113139
promisify(readFile)(file, "utf8").then(data => {
114140
const jsonData = data.replace(/^\uFEFF/, "");
@@ -129,141 +155,141 @@ export class Merger {
129155
if (this.settings.overrides) {
130156
overridesProvided = true;
131157
manifestPromises.push(Promise.resolve(this.settings.overrides));
132-
}
158+
}
159+
}
133160

134-
return Promise.all(manifestPromises).then(partials => {
135-
// Determine the targets so we can construct the builders
136-
let targets: TargetDeclaration[] = [];
137-
partials.forEach(partial => {
138-
if (_.isArray(partial["targets"])) {
139-
targets = targets.concat(partial["targets"]);
140-
}
141-
});
142-
this.extensionComposer = ComposerFactory.GetComposer(this.settings, targets);
143-
this.manifestBuilders = this.extensionComposer.getBuilders();
144-
let updateVersionPromise = Promise.resolve<void>(null);
145-
partials.forEach((partial, partialIndex) => {
146-
// Rev the version if necessary
147-
if (this.settings.revVersion) {
148-
if (partial["version"] && partial.__origin) {
149-
try {
150-
const parsedVersion = version.DynamicVersion.parse(partial["version"]);
151-
const newVersion = version.DynamicVersion.increase(parsedVersion);
152-
const newVersionString = newVersion.toString();
153-
partial["version"] = newVersionString;
154-
155-
updateVersionPromise = promisify(readFile)(partial.__origin, "utf8").then(versionPartial => {
156-
try {
157-
let newPartial: any;
158-
if (this.settings.json5) {
159-
const parsed = jju.parse(versionPartial);
160-
parsed["version"] = newVersionString;
161-
newPartial = jju.update(versionPartial, parsed);
162-
} else {
163-
newPartial = jsonInPlace(versionPartial).set("version", newVersionString);
164-
}
165-
return promisify(writeFile)(partial.__origin, newPartial);
166-
} catch (e) {
167-
trace.warn(
168-
"Failed to lex partial as JSON to update the version. Skipping version rev...",
169-
);
161+
return Promise.all(manifestPromises).then(partials => {
162+
// Determine the targets so we can construct the builders
163+
let targets: TargetDeclaration[] = [];
164+
partials.forEach(partial => {
165+
if (_.isArray(partial["targets"])) {
166+
targets = targets.concat(partial["targets"]);
167+
}
168+
});
169+
this.extensionComposer = ComposerFactory.GetComposer(this.settings, targets);
170+
this.manifestBuilders = this.extensionComposer.getBuilders();
171+
let updateVersionPromise = Promise.resolve<void>(null);
172+
partials.forEach((partial, partialIndex) => {
173+
// Rev the version if necessary
174+
if (this.settings.revVersion) {
175+
if (partial["version"] && partial.__origin) {
176+
try {
177+
const parsedVersion = version.DynamicVersion.parse(partial["version"]);
178+
const newVersion = version.DynamicVersion.increase(parsedVersion);
179+
const newVersionString = newVersion.toString();
180+
partial["version"] = newVersionString;
181+
182+
updateVersionPromise = promisify(readFile)(partial.__origin, "utf8").then(versionPartial => {
183+
try {
184+
let newPartial: any;
185+
if (this.settings.json5) {
186+
const parsed = jju.parse(versionPartial);
187+
parsed["version"] = newVersionString;
188+
newPartial = jju.update(versionPartial, parsed);
189+
} else {
190+
newPartial = jsonInPlace(versionPartial).set("version", newVersionString);
170191
}
171-
});
172-
} catch (e) {
173-
trace.warn(
174-
"Could not parse %s as a version (e.g. major.minor.patch). Skipping version rev...",
175-
partial["version"],
176-
);
177-
}
192+
return promisify(writeFile)(partial.__origin, newPartial);
193+
} catch (e) {
194+
trace.warn(
195+
"Failed to lex partial as JSON to update the version. Skipping version rev...",
196+
);
197+
}
198+
});
199+
} catch (e) {
200+
trace.warn(
201+
"Could not parse %s as a version (e.g. major.minor.patch). Skipping version rev...",
202+
partial["version"],
203+
);
178204
}
179205
}
206+
}
180207

181-
// Transform asset paths to be relative to the root of all manifests, verify assets
182-
if (_.isArray(partial["files"])) {
183-
(<Array<FileDeclaration>>partial["files"]).forEach(asset => {
184-
const keys = Object.keys(asset);
185-
if (keys.indexOf("path") < 0) {
186-
throw new Error("Files must have an absolute or relative (to the manifest) path.");
187-
}
188-
let absolutePath;
189-
if (path.isAbsolute(asset.path)) {
190-
absolutePath = asset.path;
191-
} else {
192-
absolutePath = path.join(path.dirname(partial.__origin), asset.path);
193-
}
194-
asset.path = path.relative(this.settings.root, absolutePath);
195-
});
196-
}
197-
// Transform icon paths as above
198-
if (_.isObject(partial["icons"])) {
199-
const icons = partial["icons"];
200-
Object.keys(icons).forEach((iconKind: string) => {
201-
const absolutePath = path.join(path.dirname(partial.__origin), icons[iconKind]);
202-
icons[iconKind] = path.relative(this.settings.root, absolutePath);
203-
});
204-
}
208+
// Transform asset paths to be relative to the root of all manifests, verify assets
209+
if (_.isArray(partial["files"])) {
210+
(<Array<FileDeclaration>>partial["files"]).forEach(asset => {
211+
const keys = Object.keys(asset);
212+
if (keys.indexOf("path") < 0) {
213+
throw new Error("Files must have an absolute or relative (to the manifest) path.");
214+
}
215+
let absolutePath;
216+
if (path.isAbsolute(asset.path)) {
217+
absolutePath = asset.path;
218+
} else {
219+
absolutePath = path.join(path.dirname(partial.__origin), asset.path);
220+
}
221+
asset.path = path.relative(this.settings.root, absolutePath);
222+
});
223+
}
224+
// Transform icon paths as above
225+
if (_.isObject(partial["icons"])) {
226+
const icons = partial["icons"];
227+
Object.keys(icons).forEach((iconKind: string) => {
228+
const absolutePath = path.join(path.dirname(partial.__origin), icons[iconKind]);
229+
icons[iconKind] = path.relative(this.settings.root, absolutePath);
230+
});
231+
}
205232

206-
// Expand any directories listed in the files array
207-
if (_.isArray(partial["files"])) {
208-
for (let i = partial["files"].length - 1; i >= 0; --i) {
209-
const fileDecl: FileDeclaration = partial["files"][i];
210-
const fsPath = path.join(this.settings.root, fileDecl.path);
211-
if (fs.lstatSync(fsPath).isDirectory()) {
212-
Array.prototype.splice.apply(
213-
partial["files"],
214-
(<any[]>[i, 1]).concat(this.pathToFileDeclarations(fsPath, this.settings.root, fileDecl)),
215-
);
216-
}
233+
// Expand any directories listed in the files array
234+
if (_.isArray(partial["files"])) {
235+
for (let i = partial["files"].length - 1; i >= 0; --i) {
236+
const fileDecl: FileDeclaration = partial["files"][i];
237+
const fsPath = path.join(this.settings.root, fileDecl.path);
238+
if (fs.lstatSync(fsPath).isDirectory()) {
239+
Array.prototype.splice.apply(
240+
partial["files"],
241+
(<any[]>[i, 1]).concat(this.pathToFileDeclarations(fsPath, this.settings.root, fileDecl)),
242+
);
217243
}
218244
}
245+
}
219246

220-
// Process each key by each manifest builder.
221-
Object.keys(partial).forEach(key => {
222-
const isOverridePartial = partials.length - 1 === partialIndex && overridesProvided;
223-
if (partial[key] !== undefined && (partial[key] !== null || isOverridePartial)) {
224-
// Notify each manifest builder of the key/value pair
225-
this.manifestBuilders.forEach(builder => {
226-
builder.processKey(key, partial[key], isOverridePartial);
227-
});
228-
}
229-
});
247+
// Process each key by each manifest builder.
248+
Object.keys(partial).forEach(key => {
249+
const isOverridePartial = partials.length - 1 === partialIndex && overridesProvided;
250+
if (partial[key] !== undefined && (partial[key] !== null || isOverridePartial)) {
251+
// Notify each manifest builder of the key/value pair
252+
this.manifestBuilders.forEach(builder => {
253+
builder.processKey(key, partial[key], isOverridePartial);
254+
});
255+
}
230256
});
257+
});
231258

232-
// Generate localization resources
233-
const locPrepper = new loc.LocPrep.LocKeyGenerator(this.manifestBuilders);
234-
const resources = locPrepper.generateLocalizationKeys();
259+
// Generate localization resources
260+
const locPrepper = new loc.LocPrep.LocKeyGenerator(this.manifestBuilders);
261+
const resources = locPrepper.generateLocalizationKeys();
235262

236-
// Build up resource data by reading the translations from disk
237-
return this.buildResourcesData().then(resourceData => {
238-
if (resourceData) {
239-
resourceData["defaults"] = resources.combined;
240-
}
263+
// Build up resource data by reading the translations from disk
264+
return this.buildResourcesData().then(resourceData => {
265+
if (resourceData) {
266+
resourceData["defaults"] = resources.combined;
267+
}
241268

242-
// Build up a master file list
243-
const packageFiles: PackageFiles = {};
244-
this.manifestBuilders.forEach(builder => {
245-
_.assign(packageFiles, builder.files);
246-
});
269+
// Build up a master file list
270+
const packageFiles: PackageFiles = {};
271+
this.manifestBuilders.forEach(builder => {
272+
_.assign(packageFiles, builder.files);
273+
});
247274

248-
const components: VsixComponents = { builders: this.manifestBuilders, resources: resources };
249-
250-
// Finalize each builder
251-
return Promise.all(
252-
[updateVersionPromise].concat(
253-
this.manifestBuilders.map(b => b.finalize(packageFiles, resourceData, this.manifestBuilders)),
254-
),
255-
).then(() => {
256-
// const the composer do validation
257-
return this.extensionComposer.validate(components).then(validationResult => {
258-
if (validationResult.length === 0 || this.settings.bypassValidation) {
259-
return components;
260-
} else {
261-
throw new Error(
262-
"There were errors with your extension. Address the following and re-run the tool.\n" +
263-
validationResult,
264-
);
265-
}
266-
});
275+
const components: VsixComponents = { builders: this.manifestBuilders, resources: resources };
276+
277+
// Finalize each builder
278+
return Promise.all(
279+
[updateVersionPromise].concat(
280+
this.manifestBuilders.map(b => b.finalize(packageFiles, resourceData, this.manifestBuilders)),
281+
),
282+
).then(() => {
283+
// const the composer do validation
284+
return this.extensionComposer.validate(components).then(validationResult => {
285+
if (validationResult.length === 0 || this.settings.bypassValidation) {
286+
return components;
287+
} else {
288+
throw new Error(
289+
"There were errors with your extension. Address the following and re-run the tool.\n" +
290+
validationResult,
291+
);
292+
}
267293
});
268294
});
269295
});

app/exec/extension/create.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ export class ExtensionCreate extends extBase.ExtensionBase<CreationResult> {
4747
protected getHelpArgs(): string[] {
4848
return [
4949
"root",
50+
"manifestJs",
51+
"env",
5052
"manifests",
5153
"manifestGlobs",
5254
"json5",

0 commit comments

Comments
 (0)