@suitegeezus/suitecloud-stacker 25.2.131 → 25.2.132-2

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,6 @@
1
+ {
2
+ "enabledMcpjsonServers": [
3
+ "linear"
4
+ ],
5
+ "enableAllProjectMcpServers": true
6
+ }
@@ -75,7 +75,7 @@ type BetterAccountList = {
75
75
  */
76
76
  export declare const fetchAccountList: () => Promise<BetterAccountList[]>;
77
77
  export declare const betterList: () => SdfCommand;
78
- export declare const getEnvironment: (authid: string) => Promise<"PRODUCTION" | "SANDBOX" | "RELEASE_PREVIEW" | "TESTDRIVE" | "UNKNOWN">;
78
+ export declare const getEnvironment: <R extends "PRODUCTION" | "SANDBOX" | "RELEASE_PREVIEW" | "TESTDRIVE" | "UNKNOWN">(authid: string) => Promise<R>;
79
79
  /**
80
80
  * @description - Prompts for account choice. This provides both verification and literal choice. If the choice does not match the current
81
81
  * context then an error is forced -- this is because SDF does not recognize a change in the account after launching.
@@ -106,20 +106,28 @@ const getEnvironment = async (authid) => {
106
106
  const list = await (0, exports.fetchAccountList)();
107
107
  const match = list.find((item) => (item.authid === authid));
108
108
  const companyId = match?.accountInfo?.companyId;
109
+ let environment;
109
110
  switch (true) {
110
111
  case typeof companyId === 'undefined':
111
- return 'UNKNOWN';
112
+ environment = 'UNKNOWN';
113
+ break;
112
114
  case /^td/.test(companyId):
113
- return 'TESTDRIVE';
115
+ environment = 'TESTDRIVE';
116
+ break;
114
117
  case /^\d+$/.test(companyId):
115
- return 'PRODUCTION';
118
+ environment = 'PRODUCTION';
119
+ break;
116
120
  case /_SB\d+$/.test(companyId):
117
- return 'SANDBOX';
121
+ environment = 'SANDBOX';
122
+ break;
118
123
  case /_RP\d*$/.test(companyId):
119
- return 'RELEASE_PREVIEW';
124
+ environment = 'RELEASE_PREVIEW';
125
+ break;
120
126
  default:
121
- return 'UNKNOWN';
127
+ environment = 'UNKNOWN';
128
+ break;
122
129
  }
130
+ return environment;
123
131
  };
124
132
  exports.getEnvironment = getEnvironment;
125
133
  /**
@@ -129,11 +137,13 @@ exports.getEnvironment = getEnvironment;
129
137
  */
130
138
  const promptForAccountChoice = () => {
131
139
  const origin = 'promptForAccountChoice';
140
+ let isQuiet = false;
132
141
  return {
133
142
  _origin: origin,
134
143
  beforeExecuting: async (options) => {
135
144
  process.stdout.write((0, promptHelpers_1.goColor)('HIGHLIGHT', '\n', options));
136
- if (options?.arguments?.runhooks === 'quiet')
145
+ isQuiet = options?.arguments?.runhooks === 'quiet';
146
+ if (isQuiet)
137
147
  return options;
138
148
  if (!Reflect.has({ ...options?.arguments }, 'authid')) {
139
149
  throw onErrorHelper.makeError(origin, new Error(`${options.command} is missing authid. Perhaps you should not stack ${origin} here`));
@@ -208,9 +218,9 @@ const promptForAccountChoice = () => {
208
218
  }
209
219
  },
210
220
  onError: async (error) => {
211
- console.log(error);
212
- await (0, promptHelpers_1.promptUser)({ message: 'pause oc' });
213
- return onErrorHelper.detectOriginAndReturn(origin, error, (error) => {
221
+ return onErrorHelper.detectOriginAndReturn(origin, error, async (error) => {
222
+ if (!isQuiet)
223
+ await (0, promptHelpers_1.promptUser)({ message: 'pause' });
214
224
  if (`${error}`?.includes('This error is a necessary evil')) {
215
225
  process.stdout.write((0, promptHelpers_1.goColor)('GOOD', '\n', error, '\n'));
216
226
  }
@@ -261,7 +261,8 @@ const fixFileImportPaths = () => {
261
261
  await (0, promptHelpers_1.promptUser)({
262
262
  message: JSON.stringify({
263
263
  executionPath, ...options, ischild: spawnSuitecloud.isChildProcess()
264
- })
264
+ }),
265
+ timeout: 500
265
266
  });
266
267
  // deal with relative paths, etc 1️⃣
267
268
  const shortenedPathOrGlobs = options.arguments.paths.map((path) => {
@@ -291,8 +292,7 @@ const fixFileImportPaths = () => {
291
292
  }, ['--folder', '/SuiteScripts']);
292
293
  if (options.authId)
293
294
  childArgs.push('--authid', options.authId);
294
- if (!isQuiet)
295
- await (0, promptHelpers_1.promptUser)({ message: 'pause' });
295
+ // if (!isQuiet) await promptUser({message: 'pause'});
296
296
  // globbing has to be done here before we clean 2️⃣
297
297
  const exitCode = await spawnSuitecloud.createAsync({
298
298
  command: 'file:list',
@@ -473,16 +473,18 @@ const detectManifest = () => {
473
473
  isStackable: true,
474
474
  beforeExecuting: async (options) => {
475
475
  // not needed when...
476
- if (options.arguments?.paths?.length === 0)
477
- return options;
478
- if (options.arguments.calledfromcomparefiles)
476
+ if (
477
+ // no paths
478
+ options.arguments?.paths?.length === 0 ||
479
+ // not diffing
480
+ options.arguments.calledfromcomparefiles)
479
481
  return options;
480
482
  const isQuiet = options?.arguments?.runhooks === 'quiet';
481
483
  // check if manifest exists
482
484
  const hasManifest = await (0, pathHelpers_1.hasFile)([options.projectPath, 'manifest.xml']);
483
485
  const choices = { 'Y': 'Yes' };
484
- let answer = choices['Y'];
485
486
  if (!hasManifest) {
487
+ let answer = choices['Y'];
486
488
  if (!isQuiet) {
487
489
  // prompt to create one
488
490
  answer = await (0, promptHelpers_1.promptChoices)({
@@ -11,12 +11,14 @@ export declare const promptForPath: (opts?: {
11
11
  message?: string;
12
12
  }) => SdfCommand;
13
13
  /**
14
- * @description - deploy.xml files are filtered out of an upload allowed in the upload, buuut...
15
- * - If you provide one as the only file to upload then it will load the files listed in it
14
+ * @description - deploy.xml files are filtered out of an upload because they are not allowed in the upload, buuut...
15
+ * - If you provide one as the only file to upload then it will instead parse the `<files>` tag and load the <path> listed in it
16
16
  *
17
17
  * - they are allowed in an import BUT that is rare so if an import see this path then it will not import
18
18
  * it but treat it like an import instructions
19
19
  *
20
+ * Supports file:import -- including diffing
21
+ *
20
22
  */
21
23
  export declare const getFilesFromDeployXml: () => SdfCommand;
22
24
  /**
@@ -72,12 +72,14 @@ const promptForPath = (opts) => {
72
72
  };
73
73
  exports.promptForPath = promptForPath;
74
74
  /**
75
- * @description - deploy.xml files are filtered out of an upload allowed in the upload, buuut...
76
- * - If you provide one as the only file to upload then it will load the files listed in it
75
+ * @description - deploy.xml files are filtered out of an upload because they are not allowed in the upload, buuut...
76
+ * - If you provide one as the only file to upload then it will instead parse the `<files>` tag and load the <path> listed in it
77
77
  *
78
78
  * - they are allowed in an import BUT that is rare so if an import see this path then it will not import
79
79
  * it but treat it like an import instructions
80
80
  *
81
+ * Supports file:import -- including diffing
82
+ *
81
83
  */
82
84
  const getFilesFromDeployXml = () => {
83
85
  const origin = 'getFilesFromDeployXml';
@@ -87,25 +89,38 @@ const getFilesFromDeployXml = () => {
87
89
  beforeExecuting: async (options) => {
88
90
  if (spawnSuitecloud.isChildProcess())
89
91
  return options;
90
- // ignore this combo
91
- if (options.command === 'file:import' &&
92
- // @ts-expect-error it is in file:import
93
- options.arguments?.calledfromcomparefiles)
94
- return options;
95
- const arrayKey = options.command === 'object:import' ? 'scriptid' : 'paths';
96
- if (!Array.isArray(Reflect.get(options.arguments, arrayKey)))
92
+ const argumentFlagNameForArray = options.command === 'object:import' ? 'scriptid' : 'paths';
93
+ if (!Array.isArray(Reflect.get(options.arguments, argumentFlagNameForArray)))
97
94
  return options;
98
- const [first] = Reflect.get(options.arguments, arrayKey);
99
- if (!/\bdeploy\b.*\bxml\b$/.test(first))
95
+ const [firstEntry] = Reflect.get(options.arguments, argumentFlagNameForArray);
96
+ if (!/\bdeploy\b.*\bxml\b$/.test(firstEntry))
100
97
  return options;
101
- //zero-out the array
102
- Reflect.set(options.arguments, arrayKey, []);
98
+ //zero-out the array so it can be filled with new paths
99
+ Reflect.set(options.arguments, argumentFlagNameForArray, []);
100
+ // fix paths
101
+ const correctedPath = pathHelpers.getAbsolutePath({
102
+ projectPath: options.projectPath,
103
+ filePath: firstEntry
104
+ });
103
105
  // open the file and get all the paths from it
104
106
  // relies upon the xml being in a good structure
105
107
  try {
106
- const fromXml = await (0, deployFileHelper_1.extractFilePathsFromDeployXmlFile)(options.projectPath, first, options.command === 'object:import' ? 'Objects' : 'SuiteScripts');
108
+ if (!await pathHelpers.hasFile(correctedPath)) {
109
+ promptHelpers.promptUser({
110
+ message: `Problem finding ${correctedPath} in ${origin}`,
111
+ timeout: options.arguments.runhooks === 'quiet' ? 0 : null
112
+ }).then(() => []);
113
+ throw 'oops';
114
+ }
115
+ const fromXml = await (0, deployFileHelper_1.extractFilePathsFromDeployXmlFile)(options.projectPath, correctedPath, options.command === 'object:import' ? 'Objects' : 'SuiteScripts').catch((e) => {
116
+ // non-fatal
117
+ return promptHelpers.promptUser({
118
+ message: 'Problem reading contents of deploy.xml in ' + origin,
119
+ timeout: options.arguments.runhooks === 'quiet' ? 0 : null
120
+ }).then(() => []);
121
+ });
107
122
  if (options.command === 'object:import') {
108
- Reflect.set(options.arguments, arrayKey,
123
+ Reflect.set(options.arguments, argumentFlagNameForArray,
109
124
  // add the type prefix that doesn't exist in the deploy file
110
125
  // TODO: guessing the prefix from the record folder
111
126
  fromXml.map((x) => [
@@ -115,7 +130,7 @@ const getFilesFromDeployXml = () => {
115
130
  ].join(':')));
116
131
  }
117
132
  else {
118
- Reflect.set(options.arguments, arrayKey,
133
+ Reflect.set(options.arguments, argumentFlagNameForArray,
119
134
  // add the type prefix
120
135
  fromXml);
121
136
  }
@@ -145,36 +160,37 @@ const confirmFileList = () => {
145
160
  const isQuiet = options.arguments.runhooks === 'quiet';
146
161
  const arrayKey = options.command === 'object:import' ? 'scriptid' : 'paths';
147
162
  const noFiles = Reflect.get(options.arguments, arrayKey)?.length === 0;
148
- if (noFiles && !isQuiet) {
149
- let answer = await promptHelpers.promptChoices({
150
- choices: [],
151
- displayChoices: [],
152
- question: `Nothing to do?`,
153
- default: null
154
- });
155
- return options;
156
- }
157
- if (noFiles && isQuiet) {
158
- process.stdout.write(promptHelpers.goColor('INFO', 'Nothing to do'));
159
- return options;
160
- }
161
163
  const premessage = Reflect.get(options.arguments, arrayKey)
162
164
  .map((p) => promptHelpers.goColor('HIGHLIGHT', ' ', p)).join('\n');
163
- if (isQuiet) {
164
- process.stdout.write((0, promptHelpers_1.goColor)('SUBTLE', '\n', premessage));
165
- }
166
- else {
167
- const choices = { 'Y': 'Yes', 'N': 'No' };
168
- let answer = await promptHelpers.promptChoices({
169
- choices: choices,
170
- displayChoices: choices,
171
- question: `Are you good with this list?`,
172
- premessage,
173
- default: null
174
- });
175
- // force it empty which will quit
176
- if (answer === choices['N'])
177
- Reflect.set(options.arguments, arrayKey, []);
165
+ switch (true) {
166
+ case (noFiles && !isQuiet): {
167
+ await promptHelpers.promptUser({
168
+ message: `Found Nothing to do`,
169
+ timeout: 500,
170
+ });
171
+ return options;
172
+ }
173
+ case noFiles && isQuiet: {
174
+ process.stdout.write(promptHelpers.goColor('INFO', 'Nothing to do'));
175
+ return options;
176
+ }
177
+ case isQuiet:
178
+ process.stdout.write((0, promptHelpers_1.goColor)('SUBTLE', '\n', premessage));
179
+ break;
180
+ default: {
181
+ const choices = { 'Y': 'Yes', 'N': 'No' };
182
+ let answer = await promptHelpers.promptChoices({
183
+ choices: choices,
184
+ displayChoices: choices,
185
+ question: `Are you good with this list?`,
186
+ premessage,
187
+ default: null
188
+ });
189
+ // force it empty which will quit
190
+ if (answer === choices['N'])
191
+ Reflect.set(options.arguments, arrayKey, []);
192
+ break;
193
+ }
178
194
  }
179
195
  return options;
180
196
  }
@@ -2,5 +2,5 @@
2
2
  * @file sdf/lib/makeManifestXml.ts
3
3
  * @author Gerald Gillespie <gerald.gillespie@fullscript.com>
4
4
  */
5
- declare const makeManifest: (path: string, folder: string, fileName?: string) => Promise<void>;
5
+ declare const makeManifest: (path: string, projectName: string, fileName?: string) => Promise<void>;
6
6
  export = makeManifest;
@@ -5,16 +5,16 @@
5
5
  */
6
6
  const fs = require("node:fs/promises");
7
7
  const nodePath = require("node:path");
8
- const makeManifest = async (path, folder, fileName = 'manifest.xml') => {
8
+ const makeManifest = async (path, projectName, fileName = 'manifest.xml') => {
9
9
  const generic = `<manifest projecttype="ACCOUNTCUSTOMIZATION">
10
- <projectname>${folder}</projectname>
10
+ <projectname>${projectName}</projectname>
11
11
  <frameworkversion>1.0</frameworkversion>
12
12
  </manifest>`;
13
13
  try {
14
14
  return fs.writeFile(nodePath.join(path, fileName), generic, { encoding: 'utf-8' });
15
15
  }
16
16
  catch {
17
- console.error('Error writing manifest ***************************', folder);
17
+ console.error('Error writing manifest ***************************', projectName);
18
18
  }
19
19
  };
20
20
  module.exports = makeManifest;
@@ -230,7 +230,7 @@ exports.removeTempDirForSdf = removeTempDirForSdf;
230
230
  const removeFile = async (pathBits) => {
231
231
  const newPath = nodePath.join(...pathBits);
232
232
  await fs.rm(newPath, { force: true }).catch((e) => {
233
- console.error(e);
233
+ console.info('non-fatal error deleting manifest', e);
234
234
  });
235
235
  return newPath;
236
236
  };
@@ -74,7 +74,10 @@ const promptUser = async ({ message, timeout }, callback) => {
74
74
  output: process.stdout,
75
75
  prompt: QuestionPrompt
76
76
  });
77
- console.log(`\n${(0, exports.goColor)('BOLD', message || 'Hit Enter to continue')}`);
77
+ if (typeof timeout === 'number')
78
+ console.log(`\n${(0, exports.goColor)('BOLD', message || `Hit Enter to continue (or wait ${timeout / 1000} seconds) `)}`);
79
+ else
80
+ console.log(`\n${(0, exports.goColor)('BOLD', message || 'Hit Enter to continue ')}`);
78
81
  // Display the prompt
79
82
  rl.prompt();
80
83
  return new Promise((resolve, reject) => {
@@ -82,16 +85,16 @@ const promptUser = async ({ message, timeout }, callback) => {
82
85
  console.log((0, exports.goColor)('SUBTLE', 'You pressed:', line, false));
83
86
  resolve(line);
84
87
  });
85
- if (typeof timeout === 'number') {
86
- setTimeout(() => {
87
- resolve(null);
88
- }, timeout);
89
- }
90
88
  rl.once('close', (e) => {
91
89
  console.log((0, exports.goColor)('SUBTLE', 'Exiting'));
92
90
  if (e)
93
91
  reject(e);
94
92
  });
93
+ if (typeof timeout === 'number') {
94
+ setTimeout(() => {
95
+ resolve(null);
96
+ }, timeout);
97
+ }
95
98
  }).then((answer) => {
96
99
  rl.close();
97
100
  return (typeof callback === 'function' ? callback(answer) : answer);
@@ -109,23 +112,26 @@ const promptUserSync = ({ message, timeout }, callback) => {
109
112
  output: process.stdout,
110
113
  prompt: (0, exports.goColor)('SUBTLE', '> ')
111
114
  });
112
- console.log(`\n${(0, exports.goColor)('BOLD', message || 'Hit Enter to continue')}`);
115
+ if (typeof timeout === 'number')
116
+ console.log(`\n${(0, exports.goColor)('BOLD', message || `Hit Enter to continue (or wait ${timeout / 1000} seconds) `)}`);
117
+ else
118
+ console.log(`\n${(0, exports.goColor)('BOLD', message || 'Hit Enter to continue ')}`);
113
119
  // Display the prompt
114
120
  rl.prompt();
115
121
  rl.once('line', (line) => {
116
122
  console.log((0, exports.goColor)('SUBTLE', 'You pressed:', line, false));
117
123
  rl.close();
118
124
  });
119
- if (typeof timeout === 'number') {
120
- setTimeout(() => {
121
- rl.close();
122
- }, timeout);
123
- }
124
125
  rl.once('close', (e) => {
125
126
  console.log((0, exports.goColor)('SUBTLE', 'Exiting'));
126
127
  if (e)
127
128
  throw e;
128
129
  });
130
+ if (typeof timeout === 'number') {
131
+ setTimeout(() => {
132
+ rl.close();
133
+ }, timeout);
134
+ }
129
135
  };
130
136
  exports.promptUserSync = promptUserSync;
131
137
  /**
package/package.json CHANGED
@@ -1,12 +1,13 @@
1
1
  {
2
2
  "name": "@suitegeezus/suitecloud-stacker",
3
- "version": "25.2.131",
3
+ "version": "25.2.132-2",
4
4
  "description": "SuiteCloud Stacker",
5
5
  "main": "safeCommands.js",
6
6
  "scripts": {
7
7
  "test": "test",
8
8
  "build:sdf": "tsc -p ./tsconfig.json",
9
- "update:module": "node ./bin/updateModule.js"
9
+ "update:module": "node ./bin/updateModule.js",
10
+ "prepack": "npm run build:sdf"
10
11
  },
11
12
  "type": "commonjs",
12
13
  "imports": {
@@ -33,6 +34,6 @@
33
34
  "typescript": "^5.2.2"
34
35
  },
35
36
  "dependencies": {
36
- "minimatch": "^10.0.1"
37
+ "minimatch": "10.2.3"
37
38
  }
38
39
  }
package/safeCommands.js CHANGED
@@ -893,6 +893,11 @@ exports.caseCommands = {
893
893
  * @param {module} module
894
894
  */
895
895
  const makeSafeExports = (options, extra, module) => {
896
+ const preExistingFiles = [];
897
+ ['deploy.xml', '.project.json'].forEach(async (f) => {
898
+ if (await pathHelpers.hasFile([process.cwd(), f]))
899
+ preExistingFiles.push(f);
900
+ });
896
901
  try {
897
902
  // if launched by something like:
898
903
  // nodemon ./suitecloud.config.js watch -e ts --ignore module.d.ts
@@ -909,8 +914,10 @@ const makeSafeExports = (options, extra, module) => {
909
914
  process.once('exit', async (e) => {
910
915
  // console.log('close');
911
916
  // do not delete 'tsconfig.json'
912
- const toClean = Array.isArray(extra.clean) ? extra.clean : ['deploy.xml', '.project.json'];
913
- process.stdout.write(exports.promptHelpers.goColor('SUBTLE', '\ncleaning up '));
917
+ const toClean = Array.isArray(extra.clean)
918
+ ? extra.clean
919
+ : ['deploy.xml', '.project.json'].filter((f) => !preExistingFiles.includes(f));
920
+ process.stdout.write(exports.promptHelpers.goColor('SUBTLE', '\ncleaning up files that were created by SDF '));
914
921
  cleanup(toClean, process.cwd());
915
922
  if (!e)
916
923
  process.stdout.write('\n✅');