Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
26 changes: 25 additions & 1 deletion spec/init-spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ describe('apm init', () => {
describe('when creating a package', () => {
describe('when package syntax is CoffeeScript', () => {
it('generates the proper file structure', async () => {
await apmRun(['init', '--package', 'fake-package']);
await apmRun(['init', '--syntax', 'coffeescript', '--package', 'fake-package']);
expect(fs.existsSync(packagePath)).toBeTruthy();
expect(fs.existsSync(path.join(packagePath, 'keymaps'))).toBeTruthy();
expect(
Expand Down Expand Up @@ -82,6 +82,30 @@ describe('apm init', () => {
});
});

describe('when package syntax is TypeScript', () => {
it('generates the proper file structure', async () => {
await apmRun(['init', '--syntax', 'typescript', '--package', 'fake-package']);
expect(fs.existsSync(packagePath)).toBeTruthy();
expect(fs.existsSync(path.join(packagePath, 'keymaps'))).toBeTruthy();
expect(fs.existsSync(path.join(packagePath, 'keymaps', 'fake-package.json'))).toBeTruthy();
expect(fs.existsSync(path.join(packagePath, 'src'))).toBeTruthy();
expect(fs.existsSync(path.join(packagePath, 'src', 'fake-package-view.ts'))).toBeTruthy();
// TypeScript template uses `src/index.ts` instead of naming the
// entry point after the package.
expect(fs.existsSync(path.join(packagePath, 'src', 'index.ts'))).toBeTruthy();
expect(fs.existsSync(path.join(packagePath, 'menus'))).toBeTruthy();
expect(fs.existsSync(path.join(packagePath, 'menus', 'fake-package.json'))).toBeTruthy();
expect(fs.existsSync(path.join(packagePath, 'spec', 'fake-package-view-spec.js'))).toBeTruthy();
expect(fs.existsSync(path.join(packagePath, 'spec', 'fake-package-spec.js'))).toBeTruthy();
expect(fs.existsSync(path.join(packagePath, 'styles', 'fake-package.less'))).toBeTruthy();
expect(fs.existsSync(path.join(packagePath, 'package.json'))).toBeTruthy();
// TypeScript template includes a Rollup config file.
expect(fs.existsSync(path.join(packagePath, 'rollup.config.js'))).toBeTruthy();
expect(JSON.parse(fs.readFileSync(path.join(packagePath, 'package.json'))).name).toBe('fake-package');
expect(JSON.parse(fs.readFileSync(path.join(packagePath, 'package.json'))).repository).toBe('https://github.com/somebody/fake-package');
});
});

describe('when package syntax is unsupported', () => {
it('logs an error', async () => {
const callback = jasmine.createSpy('callback');
Expand Down
47 changes: 31 additions & 16 deletions src/init.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,11 @@ class Init extends Command {

constructor() {
super();
this.supportedSyntaxes = [ "coffeescript", "javascript" ];
// Keep `coffeescript` as a supported option, but do not advertise it.
//
// The first item in this list will be the default language if one is not
// opted into via `-s`/`--syntax`.
this.supportedSyntaxes = ["javascript", "typescript", "coffeescript"];
}

parseOptions(argv) {
Expand All @@ -21,7 +25,7 @@ class Init extends Command {
options.usage(`\
Usage:
ppm init -p <package-name>
ppm init -p <package-name> --syntax <javascript-or-coffeescript>
ppm init -p <package-name> --syntax <javascript-or-typescript>
ppm init -p <package-name> -c ~/Downloads/r.tmbundle
ppm init -p <package-name> -c https://github.com/textmate/r.tmbundle
ppm init -p <package-name> --template /path/to/your/package/template
Expand All @@ -38,7 +42,7 @@ on the option selected.\
`
);
options.alias('p', 'package').string('package').describe('package', 'Generates a basic package');
options.alias('s', 'syntax').string('syntax').describe('syntax', 'Sets package syntax to CoffeeScript or JavaScript');
options.alias('s', 'syntax').string('syntax').describe('syntax', 'Sets package syntax to JavaScript or TypeScript (applies only to -p/--package option)');
options.alias('t', 'theme').string('theme').describe('theme', 'Generates a basic theme');
options.alias('l', 'language').string('language').describe('language', 'Generates a basic language package');
options.alias('c', 'convert').string('convert').describe('convert', 'Path or URL to TextMate bundle/theme to convert');
Expand All @@ -51,20 +55,26 @@ on the option selected.\
options = this.parseOptions(options.commandArgs);
if (options.argv.package?.length > 0) {
if (options.argv.convert) {
return this.convertPackage(options.argv.convert, options.argv.package).catch(error => error); // rewire the error as a value for te time being
return this.convertPackage(
options.argv.convert,
options.argv.package
// Treat the error as a value for the time being.
).catch(error => error);
}
const packagePath = path.resolve(options.argv.package);
const syntax = options.argv.syntax || this.supportedSyntaxes[0];
if (!Array.from(this.supportedSyntaxes).includes(syntax)) {
return `You must specify one of ${this.supportedSyntaxes.join(', ')} after the --syntax argument`; // expose the error value as a value for now
// Expose the error value as a value for now.
return `You must specify one of ${this.supportedSyntaxes.join(', ')} after the --syntax argument`;
}
templatePath = this.getTemplatePath(options.argv, `package-${syntax}`);
this.generateFromTemplate(packagePath, templatePath);
return;
}
}
if (options.argv.theme?.length > 0) {
if (options.argv.convert) {
return this.convertTheme(options.argv.convert, options.argv.theme).catch(error => error); // rewiring errors...
// Rewiring errors...
return this.convertTheme(options.argv.convert, options.argv.theme).catch(error => error);
}
const themePath = path.resolve(options.argv.theme);
templatePath = this.getTemplatePath(options.argv, 'theme');
Expand All @@ -79,13 +89,17 @@ on the option selected.\
this.generateFromTemplate(languagePath, templatePath, languageName);
return;
}
// If we get this far, something about this command was invalid.
if (options.argv.package != null) {
return 'You must specify a path after the --package argument'; // errors as values...
// Errors as values...
return 'You must specify a path after the --package argument';
}
if (options.argv.theme != null) {
return 'You must specify a path after the --theme argument'; // errors as values...
// Errors as values...
return 'You must specify a path after the --theme argument';
}
return 'You must specify either --package, --theme or --language to `ppm init`'; // errors as values...
// Errors as values...
return 'You must specify either --package, --theme or --language to `ppm init`';
}

async convertPackage(sourcePath, destinationPath) {
Expand Down Expand Up @@ -122,30 +136,31 @@ on the option selected.\

fs.makeTreeSync(packagePath);

const result = [];
for (let childPath of Array.from(fs.listRecursive(templatePath))) {
const templateChildPath = path.resolve(templatePath, childPath);
let relativePath = templateChildPath.replace(templatePath, "");
relativePath = relativePath.replace(/^\//, '');

// Files with `.template` extensions typically are named that way
// because they need part of their name replaced. Most other files that
// aren't named based on a parameter can omit the `.template`
// extension, even if their contents are partially parameterized.
relativePath = relativePath.replace(/\.template$/, '');
relativePath = this.replacePackageNamePlaceholders(relativePath, packageName);

const sourcePath = path.join(packagePath, relativePath);
if (fs.existsSync(sourcePath)) { continue; }
if (fs.isDirectorySync(templateChildPath)) {
result.push(fs.makeTreeSync(sourcePath));
fs.makeTreeSync(sourcePath);
} else if (fs.isFileSync(templateChildPath)) {
fs.makeTreeSync(path.dirname(sourcePath));
let contents = fs.readFileSync(templateChildPath).toString();
contents = this.replacePackageNamePlaceholders(contents, packageName);
contents = this.replacePackageAuthorPlaceholders(contents, packageAuthor);
contents = this.replaceCurrentYearPlaceholders(contents);
result.push(fs.writeFileSync(sourcePath, contents));
} else {
result.push(undefined);
fs.writeFileSync(sourcePath, contents);
}
}
return result;
}

replacePackageAuthorPlaceholders(string, packageAuthor) {
Expand Down
3 changes: 3 additions & 0 deletions templates/package-typescript/.gitignore.template
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.DS_Store
npm-debug.log
node_modules
3 changes: 3 additions & 0 deletions templates/package-typescript/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
## 0.1.0 - First Release
* Every feature added
* Every bug fixed
20 changes: 20 additions & 0 deletions templates/package-typescript/LICENSE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
Copyright (c) __current_year__ <Your name here>

Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
26 changes: 26 additions & 0 deletions templates/package-typescript/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# __package-name__ package

A short description of your package.

![A screenshot of your package](https://f.cloud.github.com/assets/69169/2290250/c35d867a-a017-11e3-86be-cd7c5bf3ff9b.gif)

## Package authors

If you’ve just generated this package, here’s how to proceed:

1. `npm install` will install the packages necessary for the build toolchain.
2. `npm run watch` can be used during development; it will automatically recompile when files change.
3. Specs run in JavaScript and should import the package (if necessary) from `../lib/index` rather than `../src/index`.
4. `npm run build` can perform a build and should be run for safety’s sake before publishing. When you make changes, make sure you commit both the source files in `src` and the generated files in `lib`.

Other tasks to perform before publishing:

- [ ] Create a corresponding repository on GitHub and push your code to that location.
- [ ] Add specs in the `spec` folder and ensure they pass.
- [ ] Update the `LICENSE` and `CHANGELOG` files.
- [ ] Edit `package.json` to…
- [ ] …change the URL of the `repository` to match the GitHub repo you created.
- [ ] …add keywords.
- [ ] …write an accurate `description`.
- [ ] …change or remove the `activationCommands` field.
- [ ] Edit the `README` to add detail about your package — and to remove this instructional text!
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"atom-workspace": {
"ctrl-alt-o": "__package-name__:toggle"
}
}
11 changes: 11 additions & 0 deletions templates/package-typescript/lib/index.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
const description = `After installing your dependencies with \`npm install\`, you must run \`npm run build\` or \`npm run watch\` from the root in order to compile your TypeScript project into JavaScript — at which point you will no longer see this warning.

If you're stuck, follow the directions in your new package's \`README.md\`.
`;

exports.activate = () => {
atom.notifications.addWarning(
'__package-name__ is created but not built',
{ description, dismissable: true }
);
};
26 changes: 26 additions & 0 deletions templates/package-typescript/menus/__package-name__.json.template
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"context-menu": {
"atom-text-editor": [
{
"label": "Toggle __package-name__",
"command": "__package-name__:toggle"
}
]
},
"menu": [
{
"label": "Packages",
"submenu": [
{
"label": "__package-name__",
"submenu": [
{
"label": "Toggle",
"command": "__package-name__:toggle"
}
]
}
]
}
]
}
34 changes: 34 additions & 0 deletions templates/package-typescript/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"name": "__package-name__",
"type": "module",
"source": "./src/index.ts",
"main": "./lib/index.cjs",
"version": "0.0.0",
"description": "A short description of your package",
"keywords": [
],
"activationCommands": {
"atom-workspace": "__package-name__:toggle"
},
"repository": "https://github.com/__package-author__/__package-name__",
"license": "MIT",
"engines": {
"atom": ">=1.0.0 <2.0.0"
},
"scripts": {
"build": "rollup -c rollup.config.js",
"watch": "rollup --watch -c rollup.config.js"
},
"dependencies": {
},
"devDependencies": {
"@rollup/plugin-commonjs": "^28.0.3",
"@rollup/plugin-json": "^6.1.0",
"@rollup/plugin-node-resolve": "^16.0.1",
"@rollup/plugin-typescript": "^12.1.2",
"rollup": "^4.40.0",
"tslib": "^2.8.1",
"typescript": "^5.8.3",
"@types/atom": "github:pulsar-edit/types"
}
}
87 changes: 87 additions & 0 deletions templates/package-typescript/rollup.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import commonjs from '@rollup/plugin-commonjs';
import resolve from '@rollup/plugin-node-resolve';
import json from '@rollup/plugin-json';
import typescript from '@rollup/plugin-typescript';

// This is a preset Rollup configuration file designed for Pulsar community
// packages written in TypeScript. Here's what it gives us:
//
// * All dependencies that use CommonJS are preserved as-is.
// * All dependencies that use ES Modules are bundled and transpiled to
// CommonJS. (This is necessary because it is impossible for ESM files loaded
// in Electron's renderer process to have access to anything from a Node
// environment, whether built-in or NPM.)
// * JSON files can be imported directly with `import` syntax and do not need
// the "import attribute" clause. This corresponds to CommonJS's ability to
// `require('foo.json')`.
//
// Read https://www.electronjs.org/docs/latest/tutorial/esm#renderer-process
// for more information about the limitations of ESM in Electron's renderer
// process.
//
// Known caveats:
//
// * Not all ESM can be transpiled to CommonJS. If your module uses top-level
// `await` or does dynamic importing (via `await import`), Rollup might be
// unable to transpile it. If so, you'll have to find a workaround or use a
// different dependency.
//
// One possible workaround is reverting to an older version of the same
// dependency. Many popular packages that use newer ES features will have an
// older version that doesn't rely on those features, and perhaps an even
// older version that is written in CommonJS.
//
// * We have been unable to find a combination of plugins that makes it
// possible to use SolidJS (with JSX) in a TypeScript project while also
// satisfying the constraints above. (See
// https://docs.solidjs.com/configuration/typescript for more information.)
// We have managed to make this work for the equivalent toolchain in
// JavaScript, but the addition of TypeScript seems to complicate things
// further. Feel free to customize what's here and let us know if you find a
// configuration that works.
//
export default {
input: 'src/index.ts',
output: {
file: 'lib/index.cjs',
// Output CommonJS as required by Electron in renderer code.
format: 'cjs',
exports: 'auto',
interop: 'auto',
sourcemap: true
},
plugins: [
resolve({
extensions: ['.js', '.ts', '.json'],
// Look in `node_modules` for dependencies.
preferBuiltins: true,
// Prefer the `main` field to the `module` field in `package.json`; this
// means that, when a package offers both CommonJS and ESM versions of
// itself, we'll prefer the CJS so that transpilation can be avoided.
mainFields: ['main', 'module']
}),
commonjs({
// Transpile everything, even things in `node_modules`.
include: /node_modules/,
// Enable transformations of ES modules in `node_modules`.
transformMixedEsModules: true,
// Handle requiring of JSON files.
ignoreDynamicRequires: false
}),
typescript({
tsconfig: './tsconfig.json',
sourceMap: true
}),
// Allows requiring of JSON files directly.
json()
],
// Mark certain packages as external; this tells Rollup not to try to
// transpile the code in these packages. You may opt into this for any
// dependency that exports a CommonJS version.
//
// `atom` _must_ be present in this list because imports from `atom` will be
// resolved at runtime.
external: [
'atom'
]
}
Loading