@mschauer5/spfx-toolkit 1.0.10 → 1.0.11

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.
@@ -0,0 +1,143 @@
1
+ import { logger, util } from '../common';
2
+ import spawn from 'cross-spawn';
3
+ import { promises as fsPromises } from 'fs';
4
+ import os from 'os';
5
+ import path from 'path';
6
+ import { incrementVersion, syncVersion } from './version.command';
7
+
8
+ function getProjectConfigPaths() {
9
+ const configDir = path.join(process.cwd());
10
+ const configPath = path.join(configDir, 'package.json');
11
+ return { configDir, configPath };
12
+ }
13
+
14
+ async function getScriptValue(scriptName: string, configPath: string): Promise<any> {
15
+ try {
16
+ const fileExists = await util.checkIfFileExistsAsync(configPath, false);
17
+ if (!fileExists) {
18
+ return undefined;
19
+ }
20
+
21
+ const isGulp = await util.isUsingGulp();
22
+ let suffix = ':heft';
23
+ if (isGulp) {
24
+ suffix = ':gulp';
25
+ }
26
+
27
+ const data = await fsPromises.readFile(configPath, 'utf-8');
28
+ const config = JSON.parse(data);
29
+ if (config && typeof config === 'object' && config.scripts) {
30
+ const scriptWithSuffix = scriptName + suffix;
31
+ const hasBase = scriptName in config.scripts;
32
+ const hasSuffix = scriptWithSuffix in config.scripts;
33
+ if (hasBase && hasSuffix) {
34
+ return config.scripts[scriptWithSuffix];
35
+ } else if (hasSuffix) {
36
+ return config.scripts[scriptWithSuffix];
37
+ } else if (hasBase) {
38
+ return config.scripts[scriptName];
39
+ }
40
+ }
41
+ return undefined;
42
+ } catch (err) {
43
+ // File does not exist or cannot be read
44
+ return undefined;
45
+ }
46
+ }
47
+
48
+ async function getScriptByName(scriptName: string): Promise<string | undefined> {
49
+ const globalPath = util.getGlobalConfigPaths();
50
+ const localPath = getProjectConfigPaths();
51
+ let value = undefined;
52
+ value = await getScriptValue(scriptName, globalPath.configPath);
53
+
54
+ const localValue = await getScriptValue(scriptName, localPath.configPath);
55
+ if (localValue !== undefined) {
56
+ value = localValue;
57
+ }
58
+
59
+ return value;
60
+ }
61
+
62
+ export const run = async (scriptName: string, options?: any) => {
63
+ if (scriptName === 'bundle' && options && typeof options === 'string') {
64
+ if (options) {
65
+ incrementVersion(options as 'major' | 'minor' | 'patch');
66
+ } else {
67
+ syncVersion();
68
+ }
69
+ }
70
+
71
+ if (scriptName === 'serve') {
72
+ const isGulp = await util.isUsingGulp();
73
+ if (isGulp) {
74
+ const envFilePath = path.join(process.cwd(), '.env');
75
+ const envFileExists = await util.checkIfFileExistsAsync(envFilePath, false);
76
+ if (envFileExists) {
77
+ const envContent = await fsPromises.readFile(envFilePath, 'utf-8');
78
+ const lines = envContent.split(/\r?\n/);
79
+ const envVars: { [key: string]: string } = {};
80
+ for (const line of lines) {
81
+ const [key, value] = line.split('=');
82
+ if (key && value) {
83
+ envVars[key.trim()] = value.trim();
84
+ }
85
+ }
86
+ if (envVars['tenantDomain']) {
87
+ process.env['SPFX_SERVE_TENANT_DOMAIN'] = envVars['tenantDomain'];
88
+ }
89
+ }
90
+ }
91
+ }
92
+
93
+ const value = await getScriptByName(scriptName);
94
+ if (!value) {
95
+ logger.error(`Script name '${scriptName}' not found in either local or global package.json!`);
96
+ } else {
97
+ try {
98
+ spawn.sync(value, { stdio: 'inherit', shell: true, cwd: process.cwd() });
99
+ } catch (error) {
100
+ logger.error(error);
101
+ }
102
+ }
103
+ };
104
+
105
+ export const sync = async (scriptName: string, to: 'global' | 'local') => {
106
+ const { configPath: globalConfigPath } = util.getGlobalConfigPaths();
107
+ const { configPath: localConfigPath } = getProjectConfigPaths();
108
+ let sourcePath = to === 'local' ? globalConfigPath : localConfigPath;
109
+
110
+ const scriptValue = await getScriptValue(scriptName, sourcePath);
111
+ if (!scriptValue) {
112
+ logger.error(`Source script name '${scriptName}' not found in the ${sourcePath}!`);
113
+ return;
114
+ }
115
+
116
+ const targetPath = to === 'local' ? localConfigPath : globalConfigPath;
117
+ const targetExists = await util.checkIfFileExistsAsync(targetPath, false);
118
+ if (!targetExists) {
119
+ logger.error(`Target package.json does not exist at path: ${targetPath}`);
120
+ return;
121
+ }
122
+ const data = await fsPromises.readFile(targetPath, 'utf-8');
123
+ const targetConfig = JSON.parse(data);
124
+ if (!targetConfig.scripts) {
125
+ targetConfig.scripts = {};
126
+ }
127
+
128
+ const isGulp = await util.isUsingGulp();
129
+ let suffix = ':heft';
130
+ const orgScriptName = scriptName;
131
+ if (isGulp) {
132
+ suffix = ':gulp';
133
+ }
134
+ if (to === 'global') {
135
+ scriptName = scriptName + suffix;
136
+ }
137
+
138
+ targetConfig.scripts[scriptName] = scriptValue;
139
+
140
+ await fsPromises.writeFile(targetPath, JSON.stringify(targetConfig, null, 2), 'utf-8');
141
+
142
+ logger.log(`Synchronized script '${orgScriptName}' to ${targetPath}`);
143
+ };
@@ -1,4 +1,5 @@
1
1
  import { logger } from './logger';
2
2
  import * as constants from './constants';
3
3
  import * as util from './util';
4
+ // import * as settings from '../commands/settings';
4
5
  export { logger, constants, util };
@@ -1,12 +1,7 @@
1
1
  import { promises as fsPromises } from 'fs';
2
2
  import os from 'os';
3
3
  import path from 'path';
4
-
5
- function getConfigPaths() {
6
- const configDir = path.join(os.homedir(), '.spfx-toolkit');
7
- const configPath = path.join(configDir, 'config.json');
8
- return { configDir, configPath };
9
- }
4
+ import { logger } from './logger';
10
5
 
11
6
  export async function checkIfFileExistsAsync(filename, includeError = true) {
12
7
  try {
@@ -20,36 +15,97 @@ export async function checkIfFileExistsAsync(filename, includeError = true) {
20
15
  }
21
16
  }
22
17
 
18
+ export function getGlobalConfigPaths() {
19
+ const configDir = path.join(os.homedir(), '.spfx-toolkit');
20
+ const configPath = path.join(configDir, 'package.json');
21
+ return { configDir, configPath };
22
+ }
23
+
23
24
  export async function isUsingGulp(): Promise<boolean> {
24
25
  const gulpFile = 'gulpfile.js';
25
26
  return await checkIfFileExistsAsync(gulpFile, false);
26
27
  }
27
28
 
28
- export async function getScriptNameRunType(scriptName: string): Promise<any> {
29
- const { configPath } = getConfigPaths();
30
- try {
31
- const data = await fsPromises.readFile(configPath, 'utf-8');
32
- const config = JSON.parse(data);
33
- if (config && typeof config === 'object' && scriptName in config) {
34
- return config[scriptName];
29
+ async function ensureGlobalPackageJsonExists() {
30
+ const { configDir, configPath } = getGlobalConfigPaths();
31
+ const exists = await checkIfFileExistsAsync(configPath, false);
32
+ if (!exists) {
33
+ await fsPromises.mkdir(configDir, { recursive: true });
34
+ await fsPromises.writeFile(configPath, JSON.stringify({ scripts: {} }, null, 2), 'utf-8');
35
+ }
36
+ }
37
+
38
+ export async function openGlobalPackageJsonInEditor() {
39
+ const { configPath } = getGlobalConfigPaths();
40
+ await ensureGlobalPackageJsonExists();
41
+
42
+ const spawn = require('child_process').spawn;
43
+ // Open the global package.json in VS Code
44
+ spawn(process.platform === 'win32' ? 'code.cmd' : 'code', [configPath], {
45
+ stdio: 'inherit',
46
+ shell: true
47
+ });
48
+ }
49
+
50
+ export async function initGlobalPackageJsonWithDefaults(force?: boolean) {
51
+ const { configPath } = getGlobalConfigPaths();
52
+ ensureGlobalPackageJsonExists();
53
+
54
+ const defaultScripts = {
55
+ 'build:gulp': 'gulp build',
56
+ 'build:heft': 'heft build',
57
+ 'bundle:gulp': 'gulp clean && gulp bundle --ship && gulp package-solution --ship',
58
+ 'bundle:heft': 'heft build --clean && heft package',
59
+ 'serve:heft': 'heft start --clean',
60
+ 'serve:gulp': 'gulp serve'
61
+ };
62
+
63
+ const packageJsonContent = await fsPromises.readFile(configPath, 'utf-8');
64
+ const packageJson = JSON.parse(packageJsonContent);
65
+
66
+ // Only add/overwrite scripts if force is true, otherwise do not overwrite existing matching scripts
67
+ packageJson.scripts = packageJson.scripts || {};
68
+ for (const [key, value] of Object.entries(defaultScripts)) {
69
+ if (force || !(key in packageJson.scripts)) {
70
+ packageJson.scripts[key] = value;
35
71
  }
36
- return undefined;
37
- } catch (err) {
38
- // File does not exist or cannot be read
39
- return undefined;
40
72
  }
73
+
74
+ await fsPromises.writeFile(configPath, JSON.stringify(packageJson, null, 2), 'utf-8');
41
75
  }
42
76
 
43
- export async function addScriptNameRunType(scriptName: string, value: any): Promise<void> {
44
- const { configDir, configPath } = getConfigPaths();
45
- let config = {};
77
+ export const installHeftDotenvPlugin = async () => {
78
+ if (await isUsingGulp()) {
79
+ logger.error('Heft .env plugin can only be installed in projects using Heft, but Gulp was detected.');
80
+ return;
81
+ }
82
+
83
+ const spawn = require('child_process').spawnSync;
84
+ const result = spawn('npm', ['install', '--save-dev', '@mschauer5/heft-dotenv-plugin'], {
85
+ stdio: 'inherit',
86
+ shell: true
87
+ });
88
+ if (result.error) {
89
+ logger.error(`Error installing @mschauer5/heft-dotenv-plugin: ${result.error.message}`);
90
+ return;
91
+ }
92
+
93
+ // Modify heft.json to include the dotenv plugin
94
+ const heftConfigPath = path.join(process.cwd(), 'heft.json');
95
+ let heftConfig: any = {};
46
96
  try {
47
- const data = await fsPromises.readFile(configPath, 'utf-8');
48
- config = JSON.parse(data);
97
+ const heftConfigContent = await fsPromises.readFile(heftConfigPath, 'utf-8');
98
+ heftConfig = JSON.parse(heftConfigContent);
49
99
  } catch (err) {
50
- // File does not exist or is invalid, start with empty config
100
+ logger.error(`Error reading heft.json: ${err.message}`);
101
+ return;
51
102
  }
52
- config[scriptName] = value;
53
- await fsPromises.mkdir(configDir, { recursive: true });
54
- await fsPromises.writeFile(configPath, JSON.stringify(config, null, 2), 'utf-8');
55
- }
103
+ heftConfig.heftPlugins = heftConfig.heftPlugins || [];
104
+ if (!heftConfig.heftPlugins.some((plugin: any) => plugin.pluginPackage === '@mschauer5/heft-dotenv-plugin')) {
105
+ heftConfig.heftPlugins.push({ pluginPackage: '@mschauer5/heft-dotenv-plugin' });
106
+ await fsPromises.writeFile(heftConfigPath, JSON.stringify(heftConfig, null, 2), 'utf-8');
107
+ logger.success('Added @mschauer5/heft-dotenv-plugin to heft.json plugins.');
108
+ } else {
109
+ logger.info('@mschauer5/heft-dotenv-plugin is already included in heft.json plugins.');
110
+ }
111
+ };
package/src/index.ts CHANGED
@@ -3,7 +3,7 @@ import chalk from 'chalk';
3
3
  import * as commander from 'commander';
4
4
  import { Command } from 'commander';
5
5
  import * as commands from './commands';
6
- import { logger } from './common';
6
+ import { logger, util } from './common';
7
7
  const program = new Command();
8
8
 
9
9
  const defaultToolkitName = 'spfx-toolkit';
@@ -12,7 +12,7 @@ program
12
12
  .name('Matt Schauer SPFx Toolkit')
13
13
  .description('CLI to help with SPFx development')
14
14
  .addHelpText('beforeAll', chalk.blueBright('Developed by Matt Schauer'))
15
- .version('1.0.10');
15
+ .version('1.0.11');
16
16
 
17
17
  program
18
18
  .command('add-alias')
@@ -24,7 +24,7 @@ program
24
24
 
25
25
  program
26
26
  .command('clear-alias')
27
- .description('remove alias for spfx-toolkit')
27
+ .description('remove any alias for spfx-toolkit')
28
28
  .action(() => {
29
29
  commands.alias.clearAlias(defaultToolkitName);
30
30
  });
@@ -33,43 +33,77 @@ program
33
33
  .command('serve')
34
34
  .description('Serve / Start the SPFx Project')
35
35
  .action(() => {
36
- commands.serve.run();
36
+ commands.scripts.run('serve');
37
37
  });
38
38
 
39
39
  program
40
40
  .command('build')
41
41
  .description('Build Project')
42
- .option('-p, --usepackage', "Use package.json script 'build' as the command")
43
- .option('-d, --isdefault', 'Set current command as default build action')
44
- .action((options) => {
45
- let usePackage = false;
46
- let isdefault = false;
47
- if (options.isdefault) {
48
- isdefault = true;
49
- }
50
- if (options.usepackage) {
51
- usePackage = true;
52
- }
53
-
54
- commands.build.run(usePackage, isdefault);
42
+ .action(() => {
43
+ commands.scripts.run('build');
55
44
  });
56
45
 
57
46
  program
58
47
  .command('bundle')
59
48
  .description('Bundle Project')
60
- .option('-p, --usepackage', "Use package.json script 'bundle' as the command")
61
- .option('-d, --isdefault', 'Set current command as default bundle action')
62
- .action((options) => {
63
- let usePackage = false;
64
- let isdefault = false;
65
- if (options.isdefault) {
66
- isdefault = true;
49
+ .option('-i, --increment <part>', 'Increment package version (major, minor, patch)', (value: string) => {
50
+ if (!['major', 'minor', 'patch'].includes(value)) {
51
+ throw new Error(`Invalid increment part: ${value}. Must be one of major, minor, or patch.`);
67
52
  }
68
- if (options.usepackage) {
69
- usePackage = true;
53
+ return value; // Return the validated value
54
+ })
55
+ .action((options) => {
56
+ commands.scripts.run('bundle', options.increment);
57
+ });
58
+
59
+ program
60
+ .command('run')
61
+ .description('Run a script from package.json by name')
62
+ .addArgument(new commander.Argument('<name>', 'script name to run from package.json'))
63
+ .action((name) => {
64
+ commands.scripts.run(name);
65
+ });
66
+
67
+ program
68
+ .command('open-global-config')
69
+ .description('Open global package.json for spfx-toolkit')
70
+ .action(() => {
71
+ util.openGlobalPackageJsonInEditor();
72
+ });
73
+
74
+ program
75
+ .command('global-config-init')
76
+ .description('Set default scripts in global package.json for spfx-toolkit')
77
+ .option('-f, --force', 'If set will overwrite existing scripts names in global package.json')
78
+ .action((options) => {
79
+ util.initGlobalPackageJsonWithDefaults(options.force);
80
+ });
81
+
82
+ program
83
+ .command('sync-script')
84
+ .description('Sync a script name from global or local package.json')
85
+ .addArgument(new commander.Argument('<name>', 'script name in package.json'))
86
+ .addArgument(new commander.Argument('<to>', 'global | local'))
87
+ .action((name, to) => {
88
+ if (to !== 'global' && to !== 'local') {
89
+ logger.error("Source must be either 'global' or 'local'.");
90
+ return;
70
91
  }
92
+ commands.scripts.sync(name, to);
93
+ });
94
+
95
+ program
96
+ .command('open-solution')
97
+ .description('Open solution folder')
98
+ .action(() => {
99
+ commands.openSolution();
100
+ });
71
101
 
72
- commands.bundle.run(usePackage, isdefault);
102
+ program
103
+ .command('set-heft-dotenv-plugin')
104
+ .description('Installs and configures npm package that allows you to use .env files with Heft')
105
+ .action(() => {
106
+ util.installHeftDotenvPlugin();
73
107
  });
74
108
 
75
109
  program
@@ -1,13 +0,0 @@
1
- import { logger, util } from '../common';
2
-
3
- export const run = async (): Promise<void> => {
4
- logger.info('Starting the SPFx development server...');
5
- util.isUsingGulp().then((usingGulp) => {
6
- if (usingGulp) {
7
- logger.info('Gulp detected. Running gulp serve...');
8
- } else {
9
- logger.error('Gulp not detected. Please ensure you have gulp installed and configured.');
10
- }
11
- });
12
- // Implementation of the serve command goes here
13
- };