@octoberswimmer/aer-sf-plugin 0.0.3 → 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,21 +1,21 @@
1
1
  # @octoberswimmer/aer-sf-plugin
2
2
 
3
3
  A [Salesforce CLI](https://developer.salesforce.com/tools/salesforcecli) plugin
4
- that runs Apex tests locally using [aer](https://github.com/octoberswimmer/aer-dist).
4
+ that runs Apex tests and previews Lightning Web Components locally using
5
+ [aer](https://github.com/octoberswimmer/aer-dist).
5
6
 
6
7
  ```
7
8
  sf aer apex run test
8
9
  ```
9
10
 
10
- works the same way as
11
+ works the same way as `sf apex run test`, but runs tests locally via `aer test`.
11
12
 
12
13
  ```
13
- sf apex run test
14
+ sf aer lightning dev component
14
15
  ```
15
16
 
16
- but instead of submitting tests to a Salesforce org, it stages your project's
17
- Apex source into a temp directory and invokes `aer test` against the staged
18
- copy.
17
+ works the same way as `sf lightning dev component`, but previews LWC components
18
+ locally via `aer server`.
19
19
 
20
20
  ## Requirements
21
21
 
@@ -35,6 +35,8 @@ sf plugins install @octoberswimmer/aer-sf-plugin
35
35
 
36
36
  ## Usage
37
37
 
38
+ ### Apex tests
39
+
38
40
  Run every test in the project:
39
41
 
40
42
  ```
@@ -65,6 +67,30 @@ With code coverage:
65
67
  sf aer apex run test --code-coverage --output-dir test-results
66
68
  ```
67
69
 
70
+ ### LWC component preview
71
+
72
+ Select a component interactively and launch the preview:
73
+
74
+ ```
75
+ sf aer lightning dev component
76
+ ```
77
+
78
+ Preview a specific component by name:
79
+
80
+ ```
81
+ sf aer lightning dev component --name myComponent
82
+ ```
83
+
84
+ Open the component list in the browser instead of selecting interactively:
85
+
86
+ ```
87
+ sf aer lightning dev component --client-select
88
+ ```
89
+
90
+ The development server uses `aer server --watch` to serve your project's
91
+ source directly (no staging), so changes to component HTML, CSS, and
92
+ JavaScript are reflected immediately via hot module replacement.
93
+
68
94
  ## Locating the aer binary
69
95
 
70
96
  When the plugin needs to invoke `aer`, it tries these sources in order:
@@ -173,5 +199,4 @@ node bin/dev.js aer apex run test --help
173
199
  This plugin is open source, licensed under BSD-3-Clause.
174
200
 
175
201
  Note that [aer](https://github.com/octoberswimmer/aer-dist) itself is not open
176
- source — it is distributed as a binary under its own license. You must obtain
177
- and install aer separately for the plugin to do anything useful.
202
+ source — it is distributed as a binary under its own license.
@@ -0,0 +1,17 @@
1
+ import { SfCommand } from '@salesforce/sf-plugins-core';
2
+ export type AerLightningDevComponentResult = {
3
+ exitCode: number;
4
+ };
5
+ export default class AerLightningDevComponent extends SfCommand<AerLightningDevComponentResult> {
6
+ static readonly summary: string;
7
+ static readonly description: string;
8
+ static readonly examples: string[];
9
+ static readonly flags: {
10
+ 'target-org': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
11
+ name: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
12
+ 'client-select': import("@oclif/core/interfaces").BooleanFlag<boolean>;
13
+ };
14
+ run(): Promise<AerLightningDevComponentResult>;
15
+ private runServer;
16
+ private maybePromptForUpdate;
17
+ }
@@ -0,0 +1,160 @@
1
+ import { spawn, exec } from 'node:child_process';
2
+ import { resolve } from 'node:path';
3
+ import { SfCommand, Flags } from '@salesforce/sf-plugins-core';
4
+ import { Messages, SfProject } from '@salesforce/core';
5
+ import select from '@inquirer/select';
6
+ import { discoverLwcComponents } from '../../../../lwc.js';
7
+ import { checkForUpdate, ensureAerBinary, installAerVersion, recordPromptedVersion, } from '../../../../aerBinary.js';
8
+ Messages.importMessagesDirectoryFromMetaUrl(import.meta.url);
9
+ const messages = Messages.loadMessages('@octoberswimmer/aer-sf-plugin', 'aer.lightning.dev.component');
10
+ export default class AerLightningDevComponent extends SfCommand {
11
+ static summary = messages.getMessage('summary');
12
+ static description = messages.getMessage('description');
13
+ static examples = messages.getMessages('examples');
14
+ static flags = {
15
+ 'target-org': Flags.string({
16
+ char: 'o',
17
+ summary: messages.getMessage('flags.target-org.summary'),
18
+ }),
19
+ name: Flags.string({
20
+ char: 'n',
21
+ summary: messages.getMessage('flags.name.summary'),
22
+ }),
23
+ 'client-select': Flags.boolean({
24
+ char: 'c',
25
+ summary: messages.getMessage('flags.client-select.summary'),
26
+ }),
27
+ };
28
+ async run() {
29
+ const { flags } = await this.parse(AerLightningDevComponent);
30
+ const project = await SfProject.resolve();
31
+ const projectRoot = project.getPath();
32
+ const contents = project.getSfProjectJson().getContents();
33
+ const packageDirectories = contents.packageDirectories ?? [];
34
+ if (flags['target-org']) {
35
+ this.warn(messages.getMessage('warn.ignoredFlags', ['--target-org']));
36
+ }
37
+ const interactive = Boolean(process.stdin.isTTY);
38
+ const aerPath = await ensureAerBinary({
39
+ allowPrompt: interactive,
40
+ confirm: (message) => this.confirm({ message, ms: 5 * 60 * 1000, defaultAnswer: false }),
41
+ log: (m) => this.log(m),
42
+ warn: (m) => this.warn(m),
43
+ });
44
+ const updateCheck = interactive
45
+ ? checkForUpdate({ aerPath, log: (m) => this.log(m) }).catch(() => null)
46
+ : Promise.resolve(null);
47
+ let componentName = flags.name;
48
+ const clientSelect = flags['client-select'] ?? false;
49
+ if (!clientSelect) {
50
+ const components = await discoverLwcComponents(projectRoot, packageDirectories);
51
+ if (components.length === 0) {
52
+ throw messages.createError('error.noLwcComponents');
53
+ }
54
+ if (componentName) {
55
+ const match = components.find((c) => c.name === componentName);
56
+ if (!match) {
57
+ const available = components.map((c) => c.name).join(', ');
58
+ throw messages.createError('error.componentNotFound', [componentName, available]);
59
+ }
60
+ }
61
+ else {
62
+ componentName = await select({
63
+ message: messages.getMessage('prompt.selectComponent'),
64
+ choices: components.map((c) => ({ name: c.name, value: c.name })),
65
+ });
66
+ }
67
+ }
68
+ const sourcePaths = packageDirectories.map((pd) => resolve(projectRoot, pd.path));
69
+ const args = ['server', ...sourcePaths, '--watch'];
70
+ const previewPath = componentName ? `/dev/lwc/${componentName}` : '/dev/lwc';
71
+ const exitCode = await this.runServer(aerPath, args, projectRoot, previewPath);
72
+ if (exitCode !== 0) {
73
+ this.warn(messages.getMessage('warn.aerExit', [String(exitCode)]));
74
+ }
75
+ await this.maybePromptForUpdate(updateCheck);
76
+ return { exitCode };
77
+ }
78
+ runServer(binaryPath, args, cwd, previewPath) {
79
+ return new Promise((resolvePromise, rejectPromise) => {
80
+ const child = spawn(binaryPath, args, {
81
+ cwd,
82
+ stdio: ['inherit', 'inherit', 'pipe'],
83
+ env: {
84
+ ...process.env,
85
+ AER_AUTOUPDATE_DISABLED: '1',
86
+ },
87
+ });
88
+ let opened = false;
89
+ let stderrBuf = '';
90
+ child.stderr?.on('data', (chunk) => {
91
+ const text = chunk.toString();
92
+ process.stderr.write(text);
93
+ if (!opened) {
94
+ stderrBuf += text;
95
+ const match = stderrBuf.match(/server listening on (\S+)/);
96
+ if (match) {
97
+ opened = true;
98
+ const addr = match[1];
99
+ const url = `http://${addr}${previewPath}`;
100
+ this.log(messages.getMessage('info.serverUrl', [url]));
101
+ openBrowser(url);
102
+ }
103
+ }
104
+ });
105
+ child.on('error', (err) => {
106
+ if (err.code === 'ENOENT') {
107
+ rejectPromise(new Error(`Could not execute aer at ${binaryPath}. The file may have been moved or deleted.`));
108
+ return;
109
+ }
110
+ rejectPromise(err);
111
+ });
112
+ child.on('exit', (code, signal) => {
113
+ if (signal) {
114
+ resolvePromise(128 + (signal === 'SIGINT' ? 2 : signal === 'SIGTERM' ? 15 : 1));
115
+ }
116
+ else {
117
+ resolvePromise(code ?? 0);
118
+ }
119
+ });
120
+ });
121
+ }
122
+ async maybePromptForUpdate(check) {
123
+ let update;
124
+ try {
125
+ update = await check;
126
+ }
127
+ catch {
128
+ return;
129
+ }
130
+ if (!update)
131
+ return;
132
+ this.log('');
133
+ const yes = await this.confirm({
134
+ message: messages.getMessage('prompt.updateAvailable', [update.installed, update.latest]),
135
+ ms: 5 * 60 * 1000,
136
+ defaultAnswer: false,
137
+ });
138
+ if (!yes) {
139
+ await recordPromptedVersion(update.latest).catch(() => { });
140
+ this.log(messages.getMessage('info.updateDeferred'));
141
+ return;
142
+ }
143
+ try {
144
+ const newPath = await installAerVersion(update.latest, { log: (m) => this.log(m) });
145
+ this.log(messages.getMessage('info.updateInstalled', [update.latest, newPath]));
146
+ }
147
+ catch (err) {
148
+ this.warn(messages.getMessage('warn.updateFailed', [err.message]));
149
+ }
150
+ }
151
+ }
152
+ function openBrowser(url) {
153
+ const cmd = process.platform === 'darwin'
154
+ ? 'open'
155
+ : process.platform === 'win32'
156
+ ? 'start'
157
+ : 'xdg-open';
158
+ exec(`${cmd} ${JSON.stringify(url)}`);
159
+ }
160
+ //# sourceMappingURL=component.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"component.js","sourceRoot":"","sources":["../../../../../src/commands/aer/lightning/dev/component.ts"],"names":[],"mappings":"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"}
package/lib/lwc.d.ts ADDED
@@ -0,0 +1,7 @@
1
+ export type LwcComponent = {
2
+ name: string;
3
+ path: string;
4
+ };
5
+ export declare function discoverLwcComponents(projectRoot: string, packageDirectories: {
6
+ path: string;
7
+ }[]): Promise<LwcComponent[]>;
package/lib/lwc.js ADDED
@@ -0,0 +1,67 @@
1
+ import { readdir, stat } from 'node:fs/promises';
2
+ import { join, resolve } from 'node:path';
3
+ export async function discoverLwcComponents(projectRoot, packageDirectories) {
4
+ const seen = new Map();
5
+ for (const pd of packageDirectories) {
6
+ const absPdRoot = resolve(projectRoot, pd.path);
7
+ const lwcDirs = await findLwcDirs(absPdRoot);
8
+ for (const lwcDir of lwcDirs) {
9
+ let entries;
10
+ try {
11
+ entries = await readdir(lwcDir, { withFileTypes: true });
12
+ }
13
+ catch {
14
+ continue;
15
+ }
16
+ for (const entry of entries) {
17
+ if (!entry.isDirectory())
18
+ continue;
19
+ const name = entry.name;
20
+ if (name.startsWith('__'))
21
+ continue;
22
+ const componentDir = join(lwcDir, name);
23
+ const hasMetaXml = await fileExists(join(componentDir, `${name}.js-meta.xml`));
24
+ if (!hasMetaXml)
25
+ continue;
26
+ seen.set(name, { name, path: componentDir });
27
+ }
28
+ }
29
+ }
30
+ return [...seen.values()].sort((a, b) => a.name.localeCompare(b.name));
31
+ }
32
+ async function findLwcDirs(root) {
33
+ const results = [];
34
+ const stack = [root];
35
+ while (stack.length) {
36
+ const dir = stack.pop();
37
+ let entries;
38
+ try {
39
+ entries = await readdir(dir, { withFileTypes: true });
40
+ }
41
+ catch {
42
+ continue;
43
+ }
44
+ for (const entry of entries) {
45
+ if (!entry.isDirectory())
46
+ continue;
47
+ const full = join(dir, entry.name);
48
+ if (entry.name === 'lwc') {
49
+ results.push(full);
50
+ }
51
+ else {
52
+ stack.push(full);
53
+ }
54
+ }
55
+ }
56
+ return results;
57
+ }
58
+ async function fileExists(filePath) {
59
+ try {
60
+ const s = await stat(filePath);
61
+ return s.isFile();
62
+ }
63
+ catch {
64
+ return false;
65
+ }
66
+ }
67
+ //# sourceMappingURL=lwc.js.map
package/lib/lwc.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lwc.js","sourceRoot":"","sources":["../src/lwc.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,87 @@
1
+ # summary
2
+
3
+ Preview Lightning Web Components locally using aer.
4
+
5
+ # description
6
+
7
+ Start a local development server powered by aer to preview Lightning Web Components in isolation with hot module replacement (HMR).
8
+
9
+ When running the development server, changes to component templates (HTML), styles (CSS), and JavaScript logic are automatically reflected in the browser.
10
+
11
+ If you run the command without flags, it displays a list of components found in your local DX project for you to choose to preview. Use the --name flag to bypass the prompt. Use --client-select to open the component list in the browser instead.
12
+
13
+ The `aer` binary is resolved in the same order as `<%= config.bin %> aer apex run test` — see that command's help for details.
14
+
15
+ # examples
16
+
17
+ - Select a component interactively and launch the preview:
18
+
19
+ <%= config.bin %> <%= command.id %>
20
+
21
+ - Preview a specific component:
22
+
23
+ <%= config.bin %> <%= command.id %> --name myComponent
24
+
25
+ - Open the component list in the browser:
26
+
27
+ <%= config.bin %> <%= command.id %> --client-select
28
+
29
+ # flags.target-org.summary
30
+
31
+ Username or alias of the target org. Accepted for compatibility with `sf lightning dev component`; aer runs locally and ignores this flag.
32
+
33
+ # flags.name.summary
34
+
35
+ Name of a component to preview.
36
+
37
+ # flags.client-select.summary
38
+
39
+ Open the component list in the browser instead of selecting interactively.
40
+
41
+ # prompt.selectComponent
42
+
43
+ Select a component to preview:
44
+
45
+ # info.serverListening
46
+
47
+ aer server listening. Opening browser…
48
+
49
+ # info.serverUrl
50
+
51
+ Preview available at %s
52
+
53
+ # info.serverStopped
54
+
55
+ aer server stopped.
56
+
57
+ # error.noLwcComponents
58
+
59
+ No Lightning Web Components found in the project's package directories.
60
+
61
+ # error.componentNotFound
62
+
63
+ Component "%s" was not found. Available components: %s
64
+
65
+ # warn.ignoredFlags
66
+
67
+ The following flags were accepted for compatibility but have no effect when running locally with aer: %s
68
+
69
+ # warn.aerExit
70
+
71
+ aer exited with non-zero status %s.
72
+
73
+ # prompt.updateAvailable
74
+
75
+ A newer aer release is available (installed: %s, latest: %s). Update now?
76
+
77
+ # info.updateDeferred
78
+
79
+ Skipping aer update. You'll be prompted again the next time a new release is published.
80
+
81
+ # info.updateInstalled
82
+
83
+ Installed aer %s to %s. The new version will be used on the next run.
84
+
85
+ # warn.updateFailed
86
+
87
+ Failed to update aer: %s
@@ -195,7 +195,102 @@
195
195
  "test:run:aer:apex",
196
196
  "test:run:apex:aer"
197
197
  ]
198
+ },
199
+ "aer:lightning:dev:component": {
200
+ "aliases": [],
201
+ "args": {},
202
+ "description": "Start a local development server powered by aer to preview Lightning Web Components in isolation with hot module replacement (HMR).\n\nWhen running the development server, changes to component templates (HTML), styles (CSS), and JavaScript logic are automatically reflected in the browser.\n\nIf you run the command without flags, it displays a list of components found in your local DX project for you to choose to preview. Use the --name flag to bypass the prompt. Use --client-select to open the component list in the browser instead.\n\nThe `aer` binary is resolved in the same order as `<%= config.bin %> aer apex run test` — see that command's help for details.",
203
+ "examples": [
204
+ "Select a component interactively and launch the preview:\n<%= config.bin %> <%= command.id %>",
205
+ "Preview a specific component:\n<%= config.bin %> <%= command.id %> --name myComponent",
206
+ "Open the component list in the browser:\n<%= config.bin %> <%= command.id %> --client-select"
207
+ ],
208
+ "flags": {
209
+ "json": {
210
+ "description": "Format output as json.",
211
+ "helpGroup": "GLOBAL",
212
+ "name": "json",
213
+ "allowNo": false,
214
+ "type": "boolean"
215
+ },
216
+ "flags-dir": {
217
+ "helpGroup": "GLOBAL",
218
+ "name": "flags-dir",
219
+ "summary": "Import flag values from a directory.",
220
+ "hasDynamicHelp": false,
221
+ "multiple": false,
222
+ "type": "option"
223
+ },
224
+ "target-org": {
225
+ "char": "o",
226
+ "name": "target-org",
227
+ "summary": "Username or alias of the target org. Accepted for compatibility with `sf lightning dev component`; aer runs locally and ignores this flag.",
228
+ "hasDynamicHelp": false,
229
+ "multiple": false,
230
+ "type": "option"
231
+ },
232
+ "name": {
233
+ "char": "n",
234
+ "name": "name",
235
+ "summary": "Name of a component to preview.",
236
+ "hasDynamicHelp": false,
237
+ "multiple": false,
238
+ "type": "option"
239
+ },
240
+ "client-select": {
241
+ "char": "c",
242
+ "name": "client-select",
243
+ "summary": "Open the component list in the browser instead of selecting interactively.",
244
+ "allowNo": false,
245
+ "type": "boolean"
246
+ }
247
+ },
248
+ "hasDynamicHelp": false,
249
+ "hiddenAliases": [],
250
+ "id": "aer:lightning:dev:component",
251
+ "pluginAlias": "@octoberswimmer/aer-sf-plugin",
252
+ "pluginName": "@octoberswimmer/aer-sf-plugin",
253
+ "pluginType": "core",
254
+ "strict": true,
255
+ "summary": "Preview Lightning Web Components locally using aer.",
256
+ "enableJsonFlag": true,
257
+ "isESM": true,
258
+ "relativePath": [
259
+ "lib",
260
+ "commands",
261
+ "aer",
262
+ "lightning",
263
+ "dev",
264
+ "component.js"
265
+ ],
266
+ "aliasPermutations": [],
267
+ "permutations": [
268
+ "aer:lightning:dev:component",
269
+ "lightning:aer:dev:component",
270
+ "lightning:dev:aer:component",
271
+ "lightning:dev:component:aer",
272
+ "aer:dev:lightning:component",
273
+ "dev:aer:lightning:component",
274
+ "dev:lightning:aer:component",
275
+ "dev:lightning:component:aer",
276
+ "aer:dev:component:lightning",
277
+ "dev:aer:component:lightning",
278
+ "dev:component:aer:lightning",
279
+ "dev:component:lightning:aer",
280
+ "aer:lightning:component:dev",
281
+ "lightning:aer:component:dev",
282
+ "lightning:component:aer:dev",
283
+ "lightning:component:dev:aer",
284
+ "aer:component:lightning:dev",
285
+ "component:aer:lightning:dev",
286
+ "component:lightning:aer:dev",
287
+ "component:lightning:dev:aer",
288
+ "aer:component:dev:lightning",
289
+ "component:aer:dev:lightning",
290
+ "component:dev:aer:lightning",
291
+ "component:dev:lightning:aer"
292
+ ]
198
293
  }
199
294
  },
200
- "version": "0.0.3"
295
+ "version": "0.0.4"
201
296
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@octoberswimmer/aer-sf-plugin",
3
- "description": "Salesforce CLI plugin that runs Apex tests locally using aer.",
4
- "version": "0.0.3",
3
+ "description": "Salesforce CLI plugin that runs Apex tests and previews LWC components locally using aer.",
4
+ "version": "0.0.4",
5
5
  "author": "Christian G. Warden",
6
6
  "bugs": "https://github.com/octoberswimmer/aer-sf-plugin/issues",
7
7
  "homepage": "https://github.com/octoberswimmer/aer-sf-plugin",
@@ -51,6 +51,14 @@
51
51
  "description": "Run Apex code or tests locally via aer."
52
52
  }
53
53
  }
54
+ },
55
+ "lightning": {
56
+ "description": "Work with Lightning Web Components locally via aer.",
57
+ "subtopics": {
58
+ "dev": {
59
+ "description": "Develop Lightning Web Components locally via aer."
60
+ }
61
+ }
54
62
  }
55
63
  }
56
64
  }