alex-c-line 2.6.1 → 2.7.0

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/dist/index.cjs CHANGED
@@ -42,6 +42,7 @@ let _alextheman_utility_internal = require("@alextheman/utility/internal");
42
42
  let zod = require("zod");
43
43
  zod = __toESM(zod);
44
44
  let node_module = require("node:module");
45
+ let toml = require("toml");
45
46
  let gray_matter = require("gray-matter");
46
47
  gray_matter = __toESM(gray_matter);
47
48
  let _alextheman_utility_node = require("@alextheman/utility/node");
@@ -120,8 +121,8 @@ function cache(program) {
120
121
  loadCommands(program.command("cache").description("Commands related to the alex-c-line cache"), { cachePath });
121
122
  }
122
123
  //#endregion
123
- //#region src/utility/constants/errorPrefix.ts
124
- const errorPrefix = "❌ ERROR:";
124
+ //#region src/utility/constants/ERROR_PREFIX.ts
125
+ const ERROR_PREFIX = "❌ ERROR:";
125
126
  //#endregion
126
127
  //#region src/utility/envFile/upsertDotenvFile.ts
127
128
  async function upsertDotenvFile(contents, envFilePath) {
@@ -132,14 +133,14 @@ async function upsertDotenvFile(contents, envFilePath) {
132
133
  async function addVariable(program, envFileContents, file) {
133
134
  const newVariableName = await (0, _inquirer_prompts.input)({ message: "Please enter the name of the environment variable you would like to add." });
134
135
  if (newVariableName in envFileContents) program.error(`
135
- ${errorPrefix} Error with chosen environment variable name ${newVariableName}.
136
+ ${ERROR_PREFIX} Error with chosen environment variable name ${newVariableName}.
136
137
  Variable name already exists. If you wish to edit this variable, please select it from the initial menu instead.
137
138
  `, {
138
139
  exitCode: 2,
139
140
  code: "DUPLICATE_ENVIRONMENT_VARIABLE_NAME"
140
141
  });
141
142
  if (/[ \t\r\n]/.test(newVariableName)) program.error(_alextheman_utility.normaliseIndents`
142
- ${errorPrefix} Error with chosen environment variable name ${newVariableName}.
143
+ ${ERROR_PREFIX} Error with chosen environment variable name ${newVariableName}.
143
144
  Environment variables are not allowed to have whitespace.
144
145
  `, {
145
146
  exitCode: 2,
@@ -274,7 +275,7 @@ function checkLockfileVersionDiscrepancy(program) {
274
275
  const { version: packageVersion } = JSON.parse(await (0, node_fs_promises.readFile)(node_path.default.resolve(process.cwd(), "package.json"), "utf-8"));
275
276
  const { version: packageLockVersion } = JSON.parse(await (0, node_fs_promises.readFile)(node_path.default.resolve(process.cwd(), "package-lock.json"), "utf-8"));
276
277
  if (packageVersion !== packageLockVersion) {
277
- console.error(`${errorPrefix} package.json and package-lock.json out of sync. Please run \`npm install\` to fix this.`);
278
+ console.error(`${ERROR_PREFIX} package.json and package-lock.json out of sync. Please run \`npm install\` to fix this.`);
278
279
  process.exitCode = 1;
279
280
  return;
280
281
  }
@@ -287,7 +288,7 @@ function gitPostMergeCleanup(program) {
287
288
  program.command("git-post-merge-cleanup").alias("git-cleanup").description("Run after merging into a given branch to quickly clean up").argument("[branch]", "The branch you want to merge into", "main").option("--rebase", "Enable if your repository mainly rebases into main", true).action(async (branch, { rebase }) => {
288
289
  console.info(`Running git-post-merge-cleanup in ${rebase ? "rebase" : "merge"} mode...`);
289
290
  const { stdout: currentBranch } = await execa.execa`git branch --show-current`;
290
- if (currentBranch === branch) program.error(`${errorPrefix} Cannot run cleanup on ${branch} branch!`, {
291
+ if (currentBranch === branch) program.error(`${ERROR_PREFIX} Cannot run cleanup on ${branch} branch!`, {
291
292
  exitCode: 1,
292
293
  code: "INVALID_BRANCH"
293
294
  });
@@ -303,7 +304,7 @@ function gitPostMergeCleanup(program) {
303
304
  const { stdout: changes } = await execa.execa`git diff ${branch}..${currentBranch}`;
304
305
  if (changes) {
305
306
  await execa.execa`git checkout ${currentBranch}`;
306
- program.error(`${errorPrefix} Changes on branch not fully merged!`, {
307
+ program.error(`${ERROR_PREFIX} Changes on branch not fully merged!`, {
307
308
  exitCode: 1,
308
309
  code: "CHANGES_NOT_MERGED"
309
310
  });
@@ -313,7 +314,7 @@ function gitPostMergeCleanup(program) {
313
314
  const { stdout: branchDeletedMessage, exitCode } = await (0, execa.execa)({ reject: false })`git branch --delete ${currentBranch}`;
314
315
  if (exitCode !== 0) {
315
316
  await execa.execa`git checkout ${currentBranch}`;
316
- program.error(`${errorPrefix} Changes on branch not fully merged!`, {
317
+ program.error(`${ERROR_PREFIX} Changes on branch not fully merged!`, {
317
318
  exitCode: 1,
318
319
  code: "CHANGES_NOT_MERGED"
319
320
  });
@@ -323,8 +324,8 @@ function gitPostMergeCleanup(program) {
323
324
  });
324
325
  }
325
326
  //#endregion
326
- //#region src/utility/constants/warningPrefix.ts
327
- const warningPrefix = "WARNING:";
327
+ //#region src/utility/constants/WARNING_PREFIX.ts
328
+ const WARNING_PREFIX = "WARNING:";
328
329
  //#endregion
329
330
  //#region src/utility/fileSystem/readdirSafe.ts
330
331
  async function readdirSafe(path) {
@@ -338,7 +339,7 @@ async function readdirSafe(path) {
338
339
  //#endregion
339
340
  //#region src/cli/commands/internal/media/generate.ts
340
341
  function internalMediaGenerate(program) {
341
- program.command("generate").argument("[target]", "The directory to generate from", process.cwd()).option("--ignore <ignore>", "Extra directories to ignore as comma-separated list").action(async (target, { ignore }) => {
342
+ program.command("generate").argument("[target]", "The directory to generate from.", process.cwd()).option("--ignore <ignore>", "Extra directories to ignore as comma-separated list.").option("-r --resolution <resolution>", "The resolution of the rendered scenes, with a comma between width and height.").action(async (target, { ignore, resolution }) => {
342
343
  const ignored = new Set([
343
344
  ".git",
344
345
  "node_modules",
@@ -350,16 +351,18 @@ function internalMediaGenerate(program) {
350
351
  async function renderFile(file) {
351
352
  const relativePath = node_path.default.relative(process.cwd(), file);
352
353
  console.info(`Rendering ${relativePath}...`);
354
+ const runManimCommand = (0, execa.execa)({
355
+ stdio: "inherit",
356
+ env: {
357
+ ...process.env,
358
+ PYTHONPATH: node_path.default.resolve("src")
359
+ }
360
+ });
353
361
  try {
354
- return await (0, execa.execa)({
355
- stdio: "inherit",
356
- env: {
357
- ...process.env,
358
- PYTHONPATH: node_path.default.resolve("src")
359
- }
360
- })`manim -qh ${file}`;
362
+ if (resolution) return await runManimCommand`manim -qh -r ${resolution} ${file}`;
363
+ return await runManimCommand`manim -qh ${file}`;
361
364
  } catch (error) {
362
- if (error instanceof execa.ExecaError) program.error(`${errorPrefix} An error has occurred with Manim while rendering ${relativePath}.`, {
365
+ if (error instanceof execa.ExecaError) program.error(`${ERROR_PREFIX} An error has occurred with Manim while rendering ${relativePath}.`, {
363
366
  exitCode: error.exitCode ?? 1,
364
367
  code: "MANIM_ERROR"
365
368
  });
@@ -378,7 +381,7 @@ function internalMediaGenerate(program) {
378
381
  const statResult = await (0, node_fs_promises.stat)(target);
379
382
  if (statResult.isFile()) await renderFile(target);
380
383
  else if (statResult.isDirectory()) await readDirectory(target);
381
- else console.warn(`${warningPrefix} Not a file or directory.`);
384
+ else console.warn(`${WARNING_PREFIX} Not a file or directory.`);
382
385
  });
383
386
  }
384
387
  //#endregion
@@ -402,7 +405,7 @@ async function findPackageRoot(startDirectory, packageName) {
402
405
  throw new _alextheman_utility.DataError({ packageName }, "PACKAGE_ROOT_NOT_FOUND", `Could not find package root for ${packageName}`);
403
406
  }
404
407
  //#endregion
405
- //#region src/utility/constants/alexCLinePackageRoot.ts
408
+ //#region src/utility/constants/ALEX_C_LINE_PACKAGE_ROOT.ts
406
409
  const __filename$3 = (0, node_url.fileURLToPath)(require("url").pathToFileURL(__filename).href);
407
410
  const ALEX_C_LINE_PACKAGE_ROOT = findPackageRoot(node_path.default.dirname(__filename$3), "alex-c-line");
408
411
  //#endregion
@@ -653,7 +656,7 @@ function localPackageUse(program) {
653
656
  stdio: "inherit",
654
657
  reject: false
655
658
  });
656
- if (exitCode !== 0) program.error(`${errorPrefix} An error occurred during the local \`alex-c-line\` run.`, {
659
+ if (exitCode !== 0) program.error(`${ERROR_PREFIX} An error occurred during the local \`alex-c-line\` run.`, {
657
660
  exitCode,
658
661
  code: "LOCAL_ALEX_C_LINE_ERROR"
659
662
  });
@@ -716,6 +719,43 @@ function localPackage(program) {
716
719
  loadCommands(program.command("local-package").description("Manage the use of local packages in your JavaScript project."), { localPackageUse });
717
720
  }
718
721
  //#endregion
722
+ //#region src/utility/constants/SUCCESS_PREFIX.ts
723
+ const SUCCESS_PREFIX = chalk.default.green("✓");
724
+ //#endregion
725
+ //#region src/cli/commands/pyproject/check/preferExactDependencyVersions.ts
726
+ const pyprojectSchema = zod.default.object({
727
+ project: zod.default.object({ dependencies: zod.default.array(zod.default.string()).optional() }),
728
+ "dependency-groups": zod.default.object({ dev: zod.default.array(zod.default.string()).optional() })
729
+ }).partial();
730
+ async function preferExactDependencyVersions(program) {
731
+ const data = (0, _alextheman_utility.parseZodSchema)(pyprojectSchema, (0, toml.parse)(await (0, node_fs_promises.readFile)("pyproject.toml", "utf-8")));
732
+ const sections = [data.project?.dependencies ?? [], data["dependency-groups"]?.dev ?? []];
733
+ const violations = [];
734
+ for (const dependencies of sections) for (const dependency of dependencies) if (!dependency.includes("==")) violations.push(dependency);
735
+ if (violations.length !== 0) program.error(`${ERROR_PREFIX} Non-exact dependencies found:\n\n${violations.join("\n")}`, {
736
+ code: "NON_EXACT_DEPENDENCIES_FOUND",
737
+ exitCode: 2
738
+ });
739
+ console.info(`${SUCCESS_PREFIX} All dependencies are exactly pinned`);
740
+ }
741
+ //#endregion
742
+ //#region src/cli/commands/pyproject/check/index.ts
743
+ const RuleName$1 = { PREFER_EXACT_DEPENDENCY_VERSIONS: "prefer-exact-dependency-versions" };
744
+ function pyprojectCheck(program) {
745
+ program.command("check").description("Run checks on your pyproject.toml file").option("--rules <rules>", "The name of the rule to check", (rawRules) => {
746
+ const rawRuleNamesArray = rawRules.split(",");
747
+ return (0, _alextheman_utility.parseZodSchema)(zod.default.array(zod.default.enum(RuleName$1)), rawRuleNamesArray);
748
+ }).action(async ({ rules }) => {
749
+ if (rules?.includes("prefer-exact-dependency-versions")) await preferExactDependencyVersions(program);
750
+ console.info(`${SUCCESS_PREFIX} Success! All checks passed!`);
751
+ });
752
+ }
753
+ //#endregion
754
+ //#region src/cli/commands/pyproject/index.ts
755
+ function pyproject(program) {
756
+ loadCommands(program.command("pyproject").description("Manage the pyproject.toml file."), { pyprojectCheck });
757
+ }
758
+ //#endregion
719
759
  //#region src/cli/commands/root/pre-commit/createStepRunner.ts
720
760
  const runCommandAndLogToConsole = (0, execa.execa)({
721
761
  stdio: "inherit",
@@ -753,7 +793,7 @@ function createStepRunner(program) {
753
793
  //#endregion
754
794
  //#region src/cli/commands/root/pre-commit/getCommandArguments.ts
755
795
  function getCommandArguments(program, script, scripts, args) {
756
- if (!(script in (scripts ?? {}))) program.error(`${errorPrefix} Could not find script \`${script}\` in package.json.`, {
796
+ if (!(script in (scripts ?? {}))) program.error(`${ERROR_PREFIX} Could not find script \`${script}\` in package.json.`, {
757
797
  exitCode: 1,
758
798
  code: "SCRIPT_NOT_FOUND"
759
799
  });
@@ -786,7 +826,7 @@ function preCommit(program) {
786
826
  const { allowNoStagedChanges = options?.allowNoStagedChanges, updateIndex = options?.updateIndex } = preCommitConfig;
787
827
  const { exitCode: diffExitCode } = await (0, execa.execa)({ reject: false })`git diff --cached --quiet`;
788
828
  switch (diffExitCode) {
789
- case 128: program.error(`${errorPrefix} Not currently in a Git repository`, {
829
+ case 128: program.error(`${ERROR_PREFIX} Not currently in a Git repository`, {
790
830
  exitCode: 1,
791
831
  code: "GIT_DIFF_FAILED"
792
832
  });
@@ -898,12 +938,9 @@ function templatePullRequest(program) {
898
938
  loadCommands(program.command("pull-request").description("Manage the pull request templates."), { templatePullRequestCreate });
899
939
  }
900
940
  //#endregion
901
- //#region src/utility/constants/successPrefix.ts
902
- const successPrefix = chalk.default.green("✓");
903
- //#endregion
904
941
  //#region src/utility/errors/convertDataErrorToProgramError.ts
905
942
  function convertDataErrorToProgramError(dataError, program, options) {
906
- program.error(`${errorPrefix} ${dataError.message}`, {
943
+ program.error(`${ERROR_PREFIX} ${dataError.message}`, {
907
944
  exitCode: options?.exitCode ?? 1,
908
945
  code: dataError.code
909
946
  });
@@ -1004,7 +1041,7 @@ function templateReleaseNoteCheck(program) {
1004
1041
  }).join("."));
1005
1042
  try {
1006
1043
  await validateReleaseDocument(name, documentVersion, fileContents, expectedReleaseStatus);
1007
- console.info(`${successPrefix} Release document is valid!`);
1044
+ console.info(`${SUCCESS_PREFIX} Release document is valid!`);
1008
1045
  } catch (error) {
1009
1046
  if (_alextheman_utility.DataError.check(error)) convertDataErrorToProgramError(error, program, { exitCode: 2 });
1010
1047
  else throw error;
@@ -1096,7 +1133,7 @@ function templateReleaseNoteCreate(program) {
1096
1133
  await (0, node_fs_promises.mkdir)(node_path.default.dirname(releaseNotePath), { recursive: true });
1097
1134
  await (0, node_fs_promises.writeFile)(releaseNotePath, releaseNoteTemplate, { flag: "wx" });
1098
1135
  } catch (error) {
1099
- if (error instanceof Error && "code" in error && error.code === "EEXIST") program.error(`${errorPrefix} Release notes already exist.`, {
1136
+ if (error instanceof Error && "code" in error && error.code === "EEXIST") program.error(`${ERROR_PREFIX} Release notes already exist.`, {
1100
1137
  exitCode: 1,
1101
1138
  code: "RELEASE_NOTE_EXISTS"
1102
1139
  });
@@ -1166,7 +1203,7 @@ function template(program) {
1166
1203
  //#endregion
1167
1204
  //#region package.json
1168
1205
  var name = "alex-c-line";
1169
- var version$1 = "2.6.1";
1206
+ var version$1 = "2.7.0";
1170
1207
  var description = "Command-line tool with commands to streamline the developer workflow.";
1171
1208
  //#endregion
1172
1209
  //#region src/utility/updates/checkUpdate.ts
@@ -1315,12 +1352,12 @@ async function noFileDependencies(program) {
1315
1352
  };
1316
1353
  if (Object.keys(allFileDependencies.dependencies ?? {}).length === 0) delete allFileDependencies.dependencies;
1317
1354
  if (Object.keys(allFileDependencies.devDependencies ?? {}).length === 0) delete allFileDependencies.devDependencies;
1318
- if (Object.keys(allFileDependencies).length !== 0) program.error(`${errorPrefix} File dependencies found:\n\n${JSON.stringify(allFileDependencies, void 0, 2)}
1355
+ if (Object.keys(allFileDependencies).length !== 0) program.error(`${ERROR_PREFIX} File dependencies found:\n\n${JSON.stringify(allFileDependencies, void 0, 2)}
1319
1356
  `, {
1320
1357
  exitCode: 2,
1321
1358
  code: "FILE_DEPENDENCIES_FOUND"
1322
1359
  });
1323
- console.info(`${successPrefix} No file dependencies found!`);
1360
+ console.info(`${SUCCESS_PREFIX} No file dependencies found!`);
1324
1361
  }
1325
1362
  //#endregion
1326
1363
  //#region src/cli/commands/package-json/check/noPreReleaseDependencies.ts
@@ -1338,7 +1375,7 @@ async function noPreReleaseDependencies(program) {
1338
1375
  for (const [dependencyName, dependencyVersionRange] of Object.entries(dependencies)) if (isPreRelease(dependencyVersionRange)) preReleaseDependencies[dependencyName] = dependencyVersionRange;
1339
1376
  for (const [dependencyName, dependencyVersionRange] of Object.entries(devDependencies)) if (isPreRelease(dependencyVersionRange)) preReleaseDevDependencies[dependencyName] = dependencyVersionRange;
1340
1377
  if (Object.keys(preReleaseDependencies).length !== 0 || Object.keys(preReleaseDevDependencies).length !== 0) program.error(_alextheman_utility.normaliseIndents`
1341
- ${errorPrefix} Pre-release version pinning is not allowed. Found the following violations:
1378
+ ${ERROR_PREFIX} Pre-release version pinning is not allowed. Found the following violations:
1342
1379
 
1343
1380
  ` + JSON.stringify({
1344
1381
  dependencies: preReleaseDependencies,
@@ -1347,7 +1384,7 @@ async function noPreReleaseDependencies(program) {
1347
1384
  exitCode: 2,
1348
1385
  code: "UNEXPECTED_PRE_RELEASE_VERSION"
1349
1386
  });
1350
- console.info(`${successPrefix} No pre-release versions found!`);
1387
+ console.info(`${SUCCESS_PREFIX} No pre-release versions found!`);
1351
1388
  }
1352
1389
  //#endregion
1353
1390
  //#region src/cli/commands/package-json/check/index.ts
@@ -1362,7 +1399,7 @@ function packageJsonCheck(program) {
1362
1399
  }).action(async ({ rules }) => {
1363
1400
  if (rules?.includes("no-pre-release-dependencies")) await noPreReleaseDependencies(program);
1364
1401
  if (rules?.includes("no-file-dependencies")) await noFileDependencies(program);
1365
- console.info(`${successPrefix} Success! All checks passed!`);
1402
+ console.info(`${SUCCESS_PREFIX} Success! All checks passed!`);
1366
1403
  });
1367
1404
  }
1368
1405
  //#endregion
@@ -1380,6 +1417,7 @@ function createCommands(program) {
1380
1417
  internal,
1381
1418
  localPackage,
1382
1419
  packageJson,
1420
+ pyproject,
1383
1421
  root,
1384
1422
  template,
1385
1423
  update,
package/dist/index.js CHANGED
@@ -14,6 +14,7 @@ import { ExecaError, execa } from "execa";
14
14
  import { fileURLToPath, pathToFileURL } from "node:url";
15
15
  import { DependencyGroup, PackageManager, getDependenciesFromGroup, getExpectedTgzName, getPackageJsonContents, packageJsonNotFoundError } from "@alextheman/utility/internal";
16
16
  import z from "zod";
17
+ import { parse as parse$1 } from "toml";
17
18
  import matter from "gray-matter";
18
19
  import { parseFilePath } from "@alextheman/utility/node";
19
20
  import axios from "axios";
@@ -89,8 +90,8 @@ function cache(program) {
89
90
  loadCommands(program.command("cache").description("Commands related to the alex-c-line cache"), { cachePath });
90
91
  }
91
92
  //#endregion
92
- //#region src/utility/constants/errorPrefix.ts
93
- const errorPrefix = "❌ ERROR:";
93
+ //#region src/utility/constants/ERROR_PREFIX.ts
94
+ const ERROR_PREFIX = "❌ ERROR:";
94
95
  //#endregion
95
96
  //#region src/utility/envFile/upsertDotenvFile.ts
96
97
  async function upsertDotenvFile(contents, envFilePath) {
@@ -101,14 +102,14 @@ async function upsertDotenvFile(contents, envFilePath) {
101
102
  async function addVariable(program, envFileContents, file) {
102
103
  const newVariableName = await input({ message: "Please enter the name of the environment variable you would like to add." });
103
104
  if (newVariableName in envFileContents) program.error(`
104
- ${errorPrefix} Error with chosen environment variable name ${newVariableName}.
105
+ ${ERROR_PREFIX} Error with chosen environment variable name ${newVariableName}.
105
106
  Variable name already exists. If you wish to edit this variable, please select it from the initial menu instead.
106
107
  `, {
107
108
  exitCode: 2,
108
109
  code: "DUPLICATE_ENVIRONMENT_VARIABLE_NAME"
109
110
  });
110
111
  if (/[ \t\r\n]/.test(newVariableName)) program.error(normaliseIndents`
111
- ${errorPrefix} Error with chosen environment variable name ${newVariableName}.
112
+ ${ERROR_PREFIX} Error with chosen environment variable name ${newVariableName}.
112
113
  Environment variables are not allowed to have whitespace.
113
114
  `, {
114
115
  exitCode: 2,
@@ -243,7 +244,7 @@ function checkLockfileVersionDiscrepancy(program) {
243
244
  const { version: packageVersion } = JSON.parse(await readFile(path.resolve(process.cwd(), "package.json"), "utf-8"));
244
245
  const { version: packageLockVersion } = JSON.parse(await readFile(path.resolve(process.cwd(), "package-lock.json"), "utf-8"));
245
246
  if (packageVersion !== packageLockVersion) {
246
- console.error(`${errorPrefix} package.json and package-lock.json out of sync. Please run \`npm install\` to fix this.`);
247
+ console.error(`${ERROR_PREFIX} package.json and package-lock.json out of sync. Please run \`npm install\` to fix this.`);
247
248
  process.exitCode = 1;
248
249
  return;
249
250
  }
@@ -256,7 +257,7 @@ function gitPostMergeCleanup(program) {
256
257
  program.command("git-post-merge-cleanup").alias("git-cleanup").description("Run after merging into a given branch to quickly clean up").argument("[branch]", "The branch you want to merge into", "main").option("--rebase", "Enable if your repository mainly rebases into main", true).action(async (branch, { rebase }) => {
257
258
  console.info(`Running git-post-merge-cleanup in ${rebase ? "rebase" : "merge"} mode...`);
258
259
  const { stdout: currentBranch } = await execa`git branch --show-current`;
259
- if (currentBranch === branch) program.error(`${errorPrefix} Cannot run cleanup on ${branch} branch!`, {
260
+ if (currentBranch === branch) program.error(`${ERROR_PREFIX} Cannot run cleanup on ${branch} branch!`, {
260
261
  exitCode: 1,
261
262
  code: "INVALID_BRANCH"
262
263
  });
@@ -272,7 +273,7 @@ function gitPostMergeCleanup(program) {
272
273
  const { stdout: changes } = await execa`git diff ${branch}..${currentBranch}`;
273
274
  if (changes) {
274
275
  await execa`git checkout ${currentBranch}`;
275
- program.error(`${errorPrefix} Changes on branch not fully merged!`, {
276
+ program.error(`${ERROR_PREFIX} Changes on branch not fully merged!`, {
276
277
  exitCode: 1,
277
278
  code: "CHANGES_NOT_MERGED"
278
279
  });
@@ -282,7 +283,7 @@ function gitPostMergeCleanup(program) {
282
283
  const { stdout: branchDeletedMessage, exitCode } = await execa({ reject: false })`git branch --delete ${currentBranch}`;
283
284
  if (exitCode !== 0) {
284
285
  await execa`git checkout ${currentBranch}`;
285
- program.error(`${errorPrefix} Changes on branch not fully merged!`, {
286
+ program.error(`${ERROR_PREFIX} Changes on branch not fully merged!`, {
286
287
  exitCode: 1,
287
288
  code: "CHANGES_NOT_MERGED"
288
289
  });
@@ -292,8 +293,8 @@ function gitPostMergeCleanup(program) {
292
293
  });
293
294
  }
294
295
  //#endregion
295
- //#region src/utility/constants/warningPrefix.ts
296
- const warningPrefix = "WARNING:";
296
+ //#region src/utility/constants/WARNING_PREFIX.ts
297
+ const WARNING_PREFIX = "WARNING:";
297
298
  //#endregion
298
299
  //#region src/utility/fileSystem/readdirSafe.ts
299
300
  async function readdirSafe(path) {
@@ -307,7 +308,7 @@ async function readdirSafe(path) {
307
308
  //#endregion
308
309
  //#region src/cli/commands/internal/media/generate.ts
309
310
  function internalMediaGenerate(program) {
310
- program.command("generate").argument("[target]", "The directory to generate from", process.cwd()).option("--ignore <ignore>", "Extra directories to ignore as comma-separated list").action(async (target, { ignore }) => {
311
+ program.command("generate").argument("[target]", "The directory to generate from.", process.cwd()).option("--ignore <ignore>", "Extra directories to ignore as comma-separated list.").option("-r --resolution <resolution>", "The resolution of the rendered scenes, with a comma between width and height.").action(async (target, { ignore, resolution }) => {
311
312
  const ignored = new Set([
312
313
  ".git",
313
314
  "node_modules",
@@ -319,16 +320,18 @@ function internalMediaGenerate(program) {
319
320
  async function renderFile(file) {
320
321
  const relativePath = path.relative(process.cwd(), file);
321
322
  console.info(`Rendering ${relativePath}...`);
323
+ const runManimCommand = execa({
324
+ stdio: "inherit",
325
+ env: {
326
+ ...process.env,
327
+ PYTHONPATH: path.resolve("src")
328
+ }
329
+ });
322
330
  try {
323
- return await execa({
324
- stdio: "inherit",
325
- env: {
326
- ...process.env,
327
- PYTHONPATH: path.resolve("src")
328
- }
329
- })`manim -qh ${file}`;
331
+ if (resolution) return await runManimCommand`manim -qh -r ${resolution} ${file}`;
332
+ return await runManimCommand`manim -qh ${file}`;
330
333
  } catch (error) {
331
- if (error instanceof ExecaError) program.error(`${errorPrefix} An error has occurred with Manim while rendering ${relativePath}.`, {
334
+ if (error instanceof ExecaError) program.error(`${ERROR_PREFIX} An error has occurred with Manim while rendering ${relativePath}.`, {
332
335
  exitCode: error.exitCode ?? 1,
333
336
  code: "MANIM_ERROR"
334
337
  });
@@ -347,7 +350,7 @@ function internalMediaGenerate(program) {
347
350
  const statResult = await stat(target);
348
351
  if (statResult.isFile()) await renderFile(target);
349
352
  else if (statResult.isDirectory()) await readDirectory(target);
350
- else console.warn(`${warningPrefix} Not a file or directory.`);
353
+ else console.warn(`${WARNING_PREFIX} Not a file or directory.`);
351
354
  });
352
355
  }
353
356
  //#endregion
@@ -371,7 +374,7 @@ async function findPackageRoot(startDirectory, packageName) {
371
374
  throw new DataError({ packageName }, "PACKAGE_ROOT_NOT_FOUND", `Could not find package root for ${packageName}`);
372
375
  }
373
376
  //#endregion
374
- //#region src/utility/constants/alexCLinePackageRoot.ts
377
+ //#region src/utility/constants/ALEX_C_LINE_PACKAGE_ROOT.ts
375
378
  const __filename$2 = fileURLToPath(import.meta.url);
376
379
  const ALEX_C_LINE_PACKAGE_ROOT = findPackageRoot(path.dirname(__filename$2), "alex-c-line");
377
380
  //#endregion
@@ -622,7 +625,7 @@ function localPackageUse(program) {
622
625
  stdio: "inherit",
623
626
  reject: false
624
627
  });
625
- if (exitCode !== 0) program.error(`${errorPrefix} An error occurred during the local \`alex-c-line\` run.`, {
628
+ if (exitCode !== 0) program.error(`${ERROR_PREFIX} An error occurred during the local \`alex-c-line\` run.`, {
626
629
  exitCode,
627
630
  code: "LOCAL_ALEX_C_LINE_ERROR"
628
631
  });
@@ -685,6 +688,43 @@ function localPackage(program) {
685
688
  loadCommands(program.command("local-package").description("Manage the use of local packages in your JavaScript project."), { localPackageUse });
686
689
  }
687
690
  //#endregion
691
+ //#region src/utility/constants/SUCCESS_PREFIX.ts
692
+ const SUCCESS_PREFIX = chalk.green("✓");
693
+ //#endregion
694
+ //#region src/cli/commands/pyproject/check/preferExactDependencyVersions.ts
695
+ const pyprojectSchema = z.object({
696
+ project: z.object({ dependencies: z.array(z.string()).optional() }),
697
+ "dependency-groups": z.object({ dev: z.array(z.string()).optional() })
698
+ }).partial();
699
+ async function preferExactDependencyVersions(program) {
700
+ const data = parseZodSchema(pyprojectSchema, parse$1(await readFile("pyproject.toml", "utf-8")));
701
+ const sections = [data.project?.dependencies ?? [], data["dependency-groups"]?.dev ?? []];
702
+ const violations = [];
703
+ for (const dependencies of sections) for (const dependency of dependencies) if (!dependency.includes("==")) violations.push(dependency);
704
+ if (violations.length !== 0) program.error(`${ERROR_PREFIX} Non-exact dependencies found:\n\n${violations.join("\n")}`, {
705
+ code: "NON_EXACT_DEPENDENCIES_FOUND",
706
+ exitCode: 2
707
+ });
708
+ console.info(`${SUCCESS_PREFIX} All dependencies are exactly pinned`);
709
+ }
710
+ //#endregion
711
+ //#region src/cli/commands/pyproject/check/index.ts
712
+ const RuleName$1 = { PREFER_EXACT_DEPENDENCY_VERSIONS: "prefer-exact-dependency-versions" };
713
+ function pyprojectCheck(program) {
714
+ program.command("check").description("Run checks on your pyproject.toml file").option("--rules <rules>", "The name of the rule to check", (rawRules) => {
715
+ const rawRuleNamesArray = rawRules.split(",");
716
+ return parseZodSchema(z.array(z.enum(RuleName$1)), rawRuleNamesArray);
717
+ }).action(async ({ rules }) => {
718
+ if (rules?.includes("prefer-exact-dependency-versions")) await preferExactDependencyVersions(program);
719
+ console.info(`${SUCCESS_PREFIX} Success! All checks passed!`);
720
+ });
721
+ }
722
+ //#endregion
723
+ //#region src/cli/commands/pyproject/index.ts
724
+ function pyproject(program) {
725
+ loadCommands(program.command("pyproject").description("Manage the pyproject.toml file."), { pyprojectCheck });
726
+ }
727
+ //#endregion
688
728
  //#region src/cli/commands/root/pre-commit/createStepRunner.ts
689
729
  const runCommandAndLogToConsole = execa({
690
730
  stdio: "inherit",
@@ -722,7 +762,7 @@ function createStepRunner(program) {
722
762
  //#endregion
723
763
  //#region src/cli/commands/root/pre-commit/getCommandArguments.ts
724
764
  function getCommandArguments(program, script, scripts, args) {
725
- if (!(script in (scripts ?? {}))) program.error(`${errorPrefix} Could not find script \`${script}\` in package.json.`, {
765
+ if (!(script in (scripts ?? {}))) program.error(`${ERROR_PREFIX} Could not find script \`${script}\` in package.json.`, {
726
766
  exitCode: 1,
727
767
  code: "SCRIPT_NOT_FOUND"
728
768
  });
@@ -755,7 +795,7 @@ function preCommit(program) {
755
795
  const { allowNoStagedChanges = options?.allowNoStagedChanges, updateIndex = options?.updateIndex } = preCommitConfig;
756
796
  const { exitCode: diffExitCode } = await execa({ reject: false })`git diff --cached --quiet`;
757
797
  switch (diffExitCode) {
758
- case 128: program.error(`${errorPrefix} Not currently in a Git repository`, {
798
+ case 128: program.error(`${ERROR_PREFIX} Not currently in a Git repository`, {
759
799
  exitCode: 1,
760
800
  code: "GIT_DIFF_FAILED"
761
801
  });
@@ -867,12 +907,9 @@ function templatePullRequest(program) {
867
907
  loadCommands(program.command("pull-request").description("Manage the pull request templates."), { templatePullRequestCreate });
868
908
  }
869
909
  //#endregion
870
- //#region src/utility/constants/successPrefix.ts
871
- const successPrefix = chalk.green("✓");
872
- //#endregion
873
910
  //#region src/utility/errors/convertDataErrorToProgramError.ts
874
911
  function convertDataErrorToProgramError(dataError, program, options) {
875
- program.error(`${errorPrefix} ${dataError.message}`, {
912
+ program.error(`${ERROR_PREFIX} ${dataError.message}`, {
876
913
  exitCode: options?.exitCode ?? 1,
877
914
  code: dataError.code
878
915
  });
@@ -973,7 +1010,7 @@ function templateReleaseNoteCheck(program) {
973
1010
  }).join("."));
974
1011
  try {
975
1012
  await validateReleaseDocument(name, documentVersion, fileContents, expectedReleaseStatus);
976
- console.info(`${successPrefix} Release document is valid!`);
1013
+ console.info(`${SUCCESS_PREFIX} Release document is valid!`);
977
1014
  } catch (error) {
978
1015
  if (DataError.check(error)) convertDataErrorToProgramError(error, program, { exitCode: 2 });
979
1016
  else throw error;
@@ -1065,7 +1102,7 @@ function templateReleaseNoteCreate(program) {
1065
1102
  await mkdir(path.dirname(releaseNotePath), { recursive: true });
1066
1103
  await writeFile(releaseNotePath, releaseNoteTemplate, { flag: "wx" });
1067
1104
  } catch (error) {
1068
- if (error instanceof Error && "code" in error && error.code === "EEXIST") program.error(`${errorPrefix} Release notes already exist.`, {
1105
+ if (error instanceof Error && "code" in error && error.code === "EEXIST") program.error(`${ERROR_PREFIX} Release notes already exist.`, {
1069
1106
  exitCode: 1,
1070
1107
  code: "RELEASE_NOTE_EXISTS"
1071
1108
  });
@@ -1135,7 +1172,7 @@ function template(program) {
1135
1172
  //#endregion
1136
1173
  //#region package.json
1137
1174
  var name = "alex-c-line";
1138
- var version$1 = "2.6.1";
1175
+ var version$1 = "2.7.0";
1139
1176
  var description = "Command-line tool with commands to streamline the developer workflow.";
1140
1177
  //#endregion
1141
1178
  //#region src/utility/updates/checkUpdate.ts
@@ -1284,12 +1321,12 @@ async function noFileDependencies(program) {
1284
1321
  };
1285
1322
  if (Object.keys(allFileDependencies.dependencies ?? {}).length === 0) delete allFileDependencies.dependencies;
1286
1323
  if (Object.keys(allFileDependencies.devDependencies ?? {}).length === 0) delete allFileDependencies.devDependencies;
1287
- if (Object.keys(allFileDependencies).length !== 0) program.error(`${errorPrefix} File dependencies found:\n\n${JSON.stringify(allFileDependencies, void 0, 2)}
1324
+ if (Object.keys(allFileDependencies).length !== 0) program.error(`${ERROR_PREFIX} File dependencies found:\n\n${JSON.stringify(allFileDependencies, void 0, 2)}
1288
1325
  `, {
1289
1326
  exitCode: 2,
1290
1327
  code: "FILE_DEPENDENCIES_FOUND"
1291
1328
  });
1292
- console.info(`${successPrefix} No file dependencies found!`);
1329
+ console.info(`${SUCCESS_PREFIX} No file dependencies found!`);
1293
1330
  }
1294
1331
  //#endregion
1295
1332
  //#region src/cli/commands/package-json/check/noPreReleaseDependencies.ts
@@ -1307,7 +1344,7 @@ async function noPreReleaseDependencies(program) {
1307
1344
  for (const [dependencyName, dependencyVersionRange] of Object.entries(dependencies)) if (isPreRelease(dependencyVersionRange)) preReleaseDependencies[dependencyName] = dependencyVersionRange;
1308
1345
  for (const [dependencyName, dependencyVersionRange] of Object.entries(devDependencies)) if (isPreRelease(dependencyVersionRange)) preReleaseDevDependencies[dependencyName] = dependencyVersionRange;
1309
1346
  if (Object.keys(preReleaseDependencies).length !== 0 || Object.keys(preReleaseDevDependencies).length !== 0) program.error(normaliseIndents`
1310
- ${errorPrefix} Pre-release version pinning is not allowed. Found the following violations:
1347
+ ${ERROR_PREFIX} Pre-release version pinning is not allowed. Found the following violations:
1311
1348
 
1312
1349
  ` + JSON.stringify({
1313
1350
  dependencies: preReleaseDependencies,
@@ -1316,7 +1353,7 @@ async function noPreReleaseDependencies(program) {
1316
1353
  exitCode: 2,
1317
1354
  code: "UNEXPECTED_PRE_RELEASE_VERSION"
1318
1355
  });
1319
- console.info(`${successPrefix} No pre-release versions found!`);
1356
+ console.info(`${SUCCESS_PREFIX} No pre-release versions found!`);
1320
1357
  }
1321
1358
  //#endregion
1322
1359
  //#region src/cli/commands/package-json/check/index.ts
@@ -1331,7 +1368,7 @@ function packageJsonCheck(program) {
1331
1368
  }).action(async ({ rules }) => {
1332
1369
  if (rules?.includes("no-pre-release-dependencies")) await noPreReleaseDependencies(program);
1333
1370
  if (rules?.includes("no-file-dependencies")) await noFileDependencies(program);
1334
- console.info(`${successPrefix} Success! All checks passed!`);
1371
+ console.info(`${SUCCESS_PREFIX} Success! All checks passed!`);
1335
1372
  });
1336
1373
  }
1337
1374
  //#endregion
@@ -1349,6 +1386,7 @@ function createCommands(program) {
1349
1386
  internal,
1350
1387
  localPackage,
1351
1388
  packageJson,
1389
+ pyproject,
1352
1390
  root,
1353
1391
  template,
1354
1392
  update,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "alex-c-line",
3
- "version": "2.6.1",
3
+ "version": "2.7.0",
4
4
  "description": "Command-line tool with commands to streamline the developer workflow.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -48,6 +48,7 @@
48
48
  "gray-matter": "4.0.3",
49
49
  "semver": "7.7.4",
50
50
  "supports-color": "10.2.2",
51
+ "toml": "4.1.1",
51
52
  "zod": "4.3.6"
52
53
  },
53
54
  "devDependencies": {
@@ -87,7 +88,7 @@
87
88
  "format-prettier-javascript": "prettier --write \"./**/*.js\"",
88
89
  "format-prettier-typescript": "prettier --write --parser typescript \"./**/*.ts\"",
89
90
  "format-prettier-yml": "prettier --write \"./**/*.{yml,yaml}\"",
90
- "lint": "pnpm run lint-tsc && pnpm run lint-eslint && pnpm run lint-markdownlint && pnpm run lint-prettier && pnpm run lint-markdownlint && pnpm run lint-pre-release",
91
+ "lint": "pnpm run lint-tsc && pnpm run lint-eslint && pnpm run lint-markdownlint && pnpm run lint-prettier && pnpm run lint-pre-release",
91
92
  "lint-eslint": "eslint \"package.json\" \"{src,tests}/**/*.ts\"",
92
93
  "lint-markdownlint": "bash -o pipefail -c 'pnpm exec markdownlint-cli2 \"**/*.md\" \"!{node_modules,dist}/**\"| grep -v \"^Finding:\"'",
93
94
  "lint-pre-release": "node dist/index.js package-json check --rules no-pre-release-dependencies",