forked from meteor/meteor
-
Notifications
You must be signed in to change notification settings - Fork 0
/
package-namespace.js
308 lines (285 loc) · 11.4 KB
/
package-namespace.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
import { each, size, compact } from "underscore";
import { inCheckout } from "../fs/files.js";
import buildmessage from "../utils/buildmessage.js";
import packageVersionParser from "../packaging/package-version-parser.js";
export class PackageNamespace {
/**
* @summary Class of the 'Package' object visible in package.js
* @locus package.js
* @instanceName Package
* @showInstanceName true
*/
constructor(packageSource) {
this._packageSource = packageSource;
this._fileAndDepLoader = null;
this._hasTests = false;
}
// Set package metadata. Options:
// - summary: for 'meteor list' & package server
// - version: package version string
// There used to be a third option documented here,
// 'environments', but it was never implemented and no package
// ever used it.
/**
* @summary Provide basic package information.
* @locus package.js
* @memberOf PackageNamespace
* @param {Object} options
* @param {String} options.summary A concise 1-2 sentence description of
* the package, required for publication.
* @param {String} options.version The (extended)
* [semver](http://www.semver.org) version for your package. Additionally,
* Meteor allows a wrap number: a positive integer that follows the
* version number. If you are porting another package that uses semver
* versioning, you may want to use the original version, postfixed with
* `_wrapnumber`. For example, `1.2.3_1` or `2.4.5-rc1_4`. Wrap numbers
* sort after the original numbers: `1.2.3` < `1.2.3_1` < `1.2.3_2` <
* `1.2.4-rc.0`. If no version is specified, this field defaults to
* `0.0.0`. If you want to publish your package to the package server, you
* must specify a version.
* @param {String} options.name Optional name override. By default, the
* package name comes from the name of its directory.
* @param {String} options.git Optional Git URL to the source repository.
* @param {String} options.documentation Optional Filepath to
* documentation. Set to 'README.md' by default. Set this to null to submit
* no documentation.
* @param {Boolean} options.debugOnly A package with this flag set to true
* will not be bundled into production builds. This is useful for packages
* meant to be used in development only.
* @param {Boolean} options.prodOnly A package with this flag set to true
* will ONLY be bundled into production builds.
* @param {Boolean} options.testOnly A package with this flag set to true
* will ONLY be bundled as part of `meteor test`.
*/
describe(options) {
const source = this._packageSource;
each(options, function (value, key) {
if (key === "summary" ||
key === "git") {
source.metadata[key] = value;
} else if (key === "documentation") {
source.metadata[key] = value;
source.docsExplicitlyProvided = true;
} else if (key === "version") {
if (typeof(value) !== "string") {
buildmessage.error("The package version (specified with "
+ "Package.describe) must be a string.");
// Recover by pretending that version was not set.
} else {
var goodVersion = true;
try {
var parsedVersion =
packageVersionParser.getValidServerVersion(value);
} catch (e) {
if (!e.versionParserError) {
throw e;
}
buildmessage.error(
"The package version " + value + " (specified with Package.describe) "
+ "is not a valid Meteor package version.\n"
+ "Valid package versions are semver (see http://semver.org/), "
+ "optionally followed by '_' and an integer greater or equal to 1.");
goodVersion = false;
}
// Recover by pretending that the version was not set.
}
if (goodVersion && parsedVersion !== value) {
buildmessage.error(
"The package version (specified with Package.describe) may not "
+ "contain a plus-separated build ID.");
// Recover by pretending that the version was not set.
goodVersion = false;
}
if (goodVersion) {
source.version = value;
source.versionExplicitlyProvided = true;
}
} else if (key === "name" && ! source.isTest) {
if (! source.name) {
source.name = value;
} else if (source.name !== value) {
// Woah, so we requested a non-test package by name, and it is not
// the name that we find inside. That's super weird.
buildmessage.error(
"trying to initialize a nonexistent base package " + value);
}
// `debugOnly`, `prodOnly` and `testOnly` are boolean
// flags you can put on a package, currently undocumented.
// when set to true, they cause a package's code to be
// only included (i.e. linked into the bundle) in dev
// mode, prod mode (`meteor --production`) or app tests
// (`meteor test`), and excluded otherwise.
//
// Notes:
//
// * These flags do not affect which packages or which versions are
// are selected by the version solver.
//
// * When you use a debugOnly, prodOnly or testOnly
// package, its exports are not imported for you. You
// have to access them using
// `Package["my-package"].MySymbol`.
//
// * These flags CAN cause different package load orders in
// development and production! We should probably fix this.
// Basically, packages that are excluded from the build using
// these flags are also excluded fro the build order calculation,
// and that's the problem
//
// * We should consider publicly documenting these flags, since they
// are effectively part of the public API.
} else if (key === "debugOnly") {
source.debugOnly = !!value;
} else if (key === "prodOnly") {
source.prodOnly = !!value;
} else if (key === "testOnly") {
source.testOnly = !!value;
} else {
// Do nothing. We might want to add some keys later, and we should err
// on the side of backwards compatibility.
}
if (size(compact([source.debugOnly, source.prodOnly, source.testOnly])) > 1) {
buildmessage.error(
"Package can't have more than one of: debugOnly, prodOnly, testOnly.");
}
});
}
/**
* @summary Define package dependencies and expose package methods.
* @locus package.js
* @param {Function} func A function that takes in the package control `api` object, which keeps track of dependencies and exports.
*/
onUse(f) {
if (! this._packageSource.isTest) {
if (this._fileAndDepLoader) {
buildmessage.error("duplicate onUse handler; a package may have " +
"only one", { useMyCaller: true });
// Recover by ignoring the duplicate
return;
}
this._fileAndDepLoader = f;
}
}
/**
* @deprecated in 0.9.0
*/
on_use(f) {
this.onUse(f);
}
/**
* @summary Define dependencies and expose package methods for unit tests.
* @locus package.js
* @param {Function} func A function that takes in the package control 'api' object, which keeps track of dependencies and exports.
*/
onTest(f) {
const isTest = this._packageSource.isTest;
// If we are not initializing the test package, then we are initializing
// the normal package and have now noticed that it has tests. So, let's
// register the test. This is a medium-length hack until we have new
// control files.
if (! isTest) {
this._hasTests = true;
return;
}
// We are initializing the test, so proceed as normal.
if (isTest) {
if (this._fileAndDepLoader) {
buildmessage.error("duplicate onTest handler; a package may have " +
"only one", { useMyCaller: true });
// Recover by ignoring the duplicate
return;
}
this._fileAndDepLoader = f;
}
}
/**
* @deprecated in 0.9.0
*/
on_test(f) {
this.onTest(f);
}
// Define a plugin. A plugin extends the build process for
// targets that use this package. For example, a Coffeescript
// compiler would be a plugin. A plugin is its own little
// program, with its own set of source files, used packages, and
// npm dependencies.
//
// This is an experimental API and for now you should assume
// that it will change frequently and radically (thus the
// '_transitional_'). For maximum R&D velocity and for the good
// of the platform, we will push changes that break your
// packages that use this API. You've been warned.
//
// Options:
// - name: a name for this plugin. required (cosmetic -- string)
// - use: package to use for the plugin (names, as strings)
// - sources: sources for the plugin (array of string)
// - npmDependencies: map from npm package name to required
// version (string)
/**
* @summary Define a build plugin. A build plugin extends the build
* process for apps and packages that use this package. For example,
* the `coffeescript` package uses a build plugin to compile CoffeeScript
* source files into JavaScript.
* @param {Object} [options]
* @param {String} options.name A cosmetic name, must be unique in the
* package.
* @param {String|String[]} options.use Meteor packages that this
* plugin uses, independent of the packages specified in
* [api.onUse](#pack_onUse).
* @param {String[]} options.sources The source files that make up the
* build plugin, independent from [api.addFiles](#pack_addFiles).
* @param {Object} options.npmDependencies An object where the keys
* are NPM package names, and the values are the version numbers of
* required NPM packages, just like in [Npm.depends](#Npm-depends).
* @locus package.js
*/
registerBuildPlugin(options) {
const isTest = this._packageSource.isTest;
// Tests don't have plugins; plugins initialized in the control file
// belong to the package and not to the test. (This will be less
// confusing in the new control file format).
if (isTest) {
return;
}
if (! ('name' in options)) {
buildmessage.error("build plugins require a name",
{ useMyCaller: true });
// recover by ignoring plugin
return;
}
const pluginInfo = this._packageSource.pluginInfo;
if (options.name in pluginInfo) {
buildmessage.error("this package already has a plugin named '" +
options.name + "'",
{ useMyCaller: true });
// recover by ignoring plugin
return;
}
if (options.name.match(/\.\./) ||
options.name.match(/[\\\/]/)) {
buildmessage.error("bad plugin name", { useMyCaller: true });
// recover by ignoring plugin
return;
}
// XXX probably want further type checking
pluginInfo[options.name] = options;
}
/**
* @deprecated in 0.9.4
*/
_transitional_registerBuildPlugin(options) {
this.registerBuildPlugin(options);
}
includeTool() {
const source = this._packageSource;
if (! inCheckout()) {
buildmessage.error("Package.includeTool() can only be used with a " +
"checkout of meteor");
} else if (source.includeTool) {
buildmessage.error("Duplicate includeTool call");
} else {
source.includeTool = true;
}
}
}