actionspack 0.1.3 → 0.1.5

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
@@ -76,6 +76,19 @@ git diff
76
76
  Review the dependency SHA changes in `.github/workflow.lock.yml` and the
77
77
  resulting generated workflow changes before committing.
78
78
 
79
+ ### VS Code
80
+
81
+ Generated workflows should not be edited by hand. Consider marking them as
82
+ read-only in your workspace settings:
83
+
84
+ ```json
85
+ {
86
+ "files.readonlyInclude": {
87
+ ".github/workflows/*.yml": true
88
+ }
89
+ }
90
+ ```
91
+
79
92
  ## Commands
80
93
 
81
94
  ```bash
package/dist/cli.mjs CHANGED
@@ -1,11 +1,11 @@
1
1
  #!/usr/bin/env node
2
- import { a as why, f as scan, i as update, l as verify, r as tree, s as pack, t as diff } from "./commands-4KKgssQI.mjs";
2
+ import { a as why, f as scan, i as update, l as verify, r as tree, s as pack, t as diff } from "./commands-BayB-wc-.mjs";
3
3
  import { styleText } from "node:util";
4
4
  import process from "node:process";
5
5
  import { cac } from "cac";
6
6
  //#region package.json
7
7
  var name = "actionspack";
8
- var version = "0.1.3";
8
+ var version = "0.1.5";
9
9
  //#endregion
10
10
  //#region src/cli.ts
11
11
  const cli = cac(name);
@@ -134,13 +134,11 @@ function evaluateStaticExpression(expr, values) {
134
134
  const data = new Evaluator(expr, expressionContext(values)).evaluate();
135
135
  return {
136
136
  data,
137
- text: dataLiteral(data),
137
+ text: valueLiteral(dataValue(data)),
138
138
  truthy: truthy(data),
139
139
  value: dataValue(data)
140
140
  };
141
- } catch {
142
- return;
143
- }
141
+ } catch {}
144
142
  }
145
143
  function hasValueForIndexAccess(expr, values) {
146
144
  const key = indexAccessKey(expr);
@@ -213,9 +211,6 @@ function dataValue(value) {
213
211
  if (value instanceof data.StringData) return value.value;
214
212
  return null;
215
213
  }
216
- function dataLiteral(value) {
217
- return valueLiteral(dataValue(value));
218
- }
219
214
  function replacementForIndexAccess(expr, values) {
220
215
  if (!hasValueForIndexAccess(expr, values)) return;
221
216
  const value = valueForIndexAccess(expr, values);
@@ -282,7 +277,7 @@ function simplifyFormatString(format, args) {
282
277
  if (!rawIndex) return;
283
278
  const arg = args[Number(rawIndex)];
284
279
  if (!arg) return;
285
- if (arg.static) nextFormat += escapeFormatLiteral(arg.static.data.coerceString());
280
+ if (arg.static) nextFormat += arg.static.data.coerceString().replaceAll("{", "{{").replaceAll("}", "}}");
286
281
  else {
287
282
  const nextIndex = nextArgs.push(arg.text) - 1;
288
283
  nextFormat += `{${nextIndex}}`;
@@ -295,11 +290,9 @@ function simplifyFormatString(format, args) {
295
290
  format: nextFormat
296
291
  };
297
292
  }
298
- function escapeFormatLiteral(value) {
299
- return value.replaceAll("{", "{{").replaceAll("}", "}}");
300
- }
301
293
  //#endregion
302
294
  //#region src/utils/yaml.ts
295
+ const GENERATED_FILE_NOTICE = "# This file is generated by actionspack. Do not edit it manually.\n\n";
303
296
  function parseYamlMap(source, file) {
304
297
  const value = parse(source);
305
298
  if (!isRecord(value)) throw new TypeError(`${file} must contain a YAML mapping`);
@@ -313,7 +306,7 @@ function stringifyYaml(value) {
313
306
  });
314
307
  }
315
308
  function stringifyWorkflowYaml(value) {
316
- return formatWorkflowYaml(stringifyYaml(normalizeWorkflowYamlValue(value)));
309
+ return `${GENERATED_FILE_NOTICE}${formatWorkflowYaml(stringifyYaml(normalizeWorkflowYamlValue(value)))}`;
317
310
  }
318
311
  function isRecord(value) {
319
312
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -330,7 +323,7 @@ function formatWorkflowYaml(source) {
330
323
  let stepsIndent;
331
324
  let stepItemIndent;
332
325
  for (const line of lines) {
333
- const indent = leadingSpaces(line);
326
+ const indent = line.length - line.trimStart().length;
334
327
  const trimmed = line.trim();
335
328
  const isTopLevelKey = indent === 0 && /^[\w-]+:/u.test(line);
336
329
  if (stepsIndent !== void 0 && trimmed && indent <= stepsIndent) {
@@ -350,9 +343,6 @@ function formatWorkflowYaml(source) {
350
343
  }
351
344
  return `${output.join("\n")}\n`;
352
345
  }
353
- function leadingSpaces(value) {
354
- return value.length - value.trimStart().length;
355
- }
356
346
  function pushBlankLine(lines) {
357
347
  if (lines.length > 0 && lines.at(-1) !== "") lines.push("");
358
348
  }
@@ -367,7 +357,7 @@ function normalizeWorkflowYamlValue(value) {
367
357
  //#region src/optimizer.ts
368
358
  function optimizeJob(job) {
369
359
  const next = { ...job };
370
- if (isEmptyNeeds(next.needs)) delete next.needs;
360
+ if (Array.isArray(next.needs) && next.needs.length === 0) delete next.needs;
371
361
  if (staticIfValue(next.if) === true) delete next.if;
372
362
  return next;
373
363
  }
@@ -395,9 +385,6 @@ function staticIfValue(value) {
395
385
  const trimmed = value.trim();
396
386
  return staticIfExpression(expressionBody(trimmed) ?? trimmed);
397
387
  }
398
- function isEmptyNeeds(value) {
399
- return Array.isArray(value) && value.length === 0;
400
- }
401
388
  //#endregion
402
389
  //#region src/utils/workflow-parser.ts
403
390
  const workflowParserEntry = fileURLToPath(import.meta.resolve("@actions/workflow-parser"));
@@ -405,7 +392,8 @@ const workflowParserDist = path.dirname(workflowParserEntry);
405
392
  let actionSchema;
406
393
  let workflowSchema;
407
394
  function parseWorkflowMap(source, file) {
408
- return parseTemplateMap(WORKFLOW_ROOT, workflowSchemaForParser(), source, file);
395
+ workflowSchema ??= loadSchema("workflow-v1.0.min.json");
396
+ return parseTemplateMap(WORKFLOW_ROOT, workflowSchema, source, file);
409
397
  }
410
398
  function parseActionMap(source, file) {
411
399
  return parseTemplateMap(ACTION_ROOT, actionSchemaForParser(), source, file);
@@ -448,10 +436,6 @@ function templateTokenToValue(token) {
448
436
  if (isBasicExpression(token)) return token.toString();
449
437
  return token.toJSON();
450
438
  }
451
- function workflowSchemaForParser() {
452
- workflowSchema ??= loadSchema("workflow-v1.0.min.json");
453
- return workflowSchema;
454
- }
455
439
  function actionSchemaForParser() {
456
440
  actionSchema ??= loadSchema("action-v1.0.min.json");
457
441
  return actionSchema;
@@ -479,13 +463,6 @@ async function fileExists(file) {
479
463
  async function readYamlFile(file) {
480
464
  return parseYamlMap(await readFile(file, "utf8"), file);
481
465
  }
482
- async function readWorkflowFile(file) {
483
- return parseWorkflowMap(await readFile(file, "utf8"), file);
484
- }
485
- async function writeYamlFile(file, value) {
486
- await mkdir(path.dirname(file), { recursive: true });
487
- await writeFile(file, stringifyYaml(value), "utf8");
488
- }
489
466
  async function discoverConfig(cwd, overrides = {}) {
490
467
  const root = resolveCwd(cwd);
491
468
  const configPath = path.join(root, "actionspack.yml");
@@ -508,7 +485,8 @@ async function discoverConfig(cwd, overrides = {}) {
508
485
  } else config = await discoverDefaultConfig(root);
509
486
  return {
510
487
  ...config,
511
- ...normalizeConfigOverrides(overrides)
488
+ ...overrides.entries ? { entries: overrides.entries } : {},
489
+ ...overrides.external ? { external: overrides.external } : {}
512
490
  };
513
491
  }
514
492
  async function discoverDefaultConfig(root) {
@@ -525,31 +503,23 @@ async function discoverDefaultConfig(root) {
525
503
  }))
526
504
  };
527
505
  }
528
- function normalizeConfigOverrides(overrides) {
529
- return {
530
- ...overrides.entries ? { entries: overrides.entries } : {},
531
- ...overrides.external ? { external: overrides.external } : {}
532
- };
533
- }
534
506
  function normalizeStringList(value) {
535
507
  if (typeof value === "string") return [value];
536
508
  if (!Array.isArray(value)) return [];
537
509
  return value.filter((item) => typeof item === "string");
538
510
  }
539
- function readWorkflowEntry(root, entry) {
540
- return readWorkflowFile(path.join(root, entry.source));
511
+ async function readWorkflowEntry(root, entry) {
512
+ const file = path.join(root, entry.source);
513
+ return parseWorkflowMap(await readFile(file, "utf8"), file);
541
514
  }
542
- function emptyLockfile() {
543
- return {
515
+ async function readLockfile(cwd) {
516
+ const root = resolveCwd(cwd);
517
+ const file = path.join(root, LOCKFILE_PATH);
518
+ if (!await fileExists(file)) return {
544
519
  lockfileVersion: 1,
545
520
  entries: {},
546
521
  packages: {}
547
522
  };
548
- }
549
- async function readLockfile(cwd) {
550
- const root = resolveCwd(cwd);
551
- const file = path.join(root, LOCKFILE_PATH);
552
- if (!await fileExists(file)) return emptyLockfile();
553
523
  const value = await readYamlFile(file);
554
524
  return {
555
525
  lockfileVersion: 1,
@@ -558,7 +528,9 @@ async function readLockfile(cwd) {
558
528
  };
559
529
  }
560
530
  async function writeLockfile(cwd, lockfile) {
561
- await writeYamlFile(path.join(resolveCwd(cwd), LOCKFILE_PATH), lockfile);
531
+ const file = path.join(resolveCwd(cwd), LOCKFILE_PATH);
532
+ await mkdir(path.dirname(file), { recursive: true });
533
+ await writeFile(file, `${GENERATED_FILE_NOTICE}${stringifyYaml(lockfile)}`, "utf8");
562
534
  }
563
535
  async function writeWorkflow(cwd, output, workflow) {
564
536
  const file = path.join(resolveCwd(cwd), output);
@@ -586,7 +558,7 @@ var HttpGitHubClient = class {
586
558
  ref
587
559
  ];
588
560
  for (const candidate of candidates) {
589
- const sha = (await this.#request(`/repos/${owner}/${repo}/git/ref/${candidate}`))?.object?.sha;
561
+ const sha = (await this.#request(`/repos/${owner}/${repo}/git/ref/${candidate}`, true))?.object?.sha;
590
562
  if (sha) return sha;
591
563
  }
592
564
  throw new Error(`Unable to resolve ${owner}/${repo}@${ref}`);
@@ -595,7 +567,8 @@ var HttpGitHubClient = class {
595
567
  const cacheFile = this.#cacheFile(owner, repo, ref, filePath);
596
568
  const cached = await readFile(cacheFile, "utf8").catch(() => void 0);
597
569
  if (cached !== void 0) return cached;
598
- const response = await this.#request(`/repos/${owner}/${repo}/contents/${encodeURIComponentPath(filePath)}?ref=${ref}`, true);
570
+ const encodedPath = filePath.split("/").map(encodeURIComponent).join("/");
571
+ const response = await this.#request(`/repos/${owner}/${repo}/contents/${encodedPath}?ref=${ref}`, true);
599
572
  if (!response) return;
600
573
  if (response.encoding !== "base64" || typeof response.content !== "string") throw new Error(`Unexpected GitHub content response for ${owner}/${repo}/${filePath}@${ref}`);
601
574
  const content = Buffer.from(response.content, "base64").toString("utf8");
@@ -619,15 +592,9 @@ var HttpGitHubClient = class {
619
592
  return await response.json();
620
593
  }
621
594
  };
622
- function encodeURIComponentPath(filePath) {
623
- return filePath.split("/").map(encodeURIComponent).join("/");
624
- }
625
595
  //#endregion
626
596
  //#region src/utils/ref.ts
627
597
  const REMOTE_USES_RE = /^[\w.-]+\/[\w.-]+(?:\/[^@\s]+)?@[^@\s]+$/;
628
- function packageKey(owner, repo, path) {
629
- return path && path !== "." ? `github:${owner}/${repo}/${path}` : `github:${owner}/${repo}`;
630
- }
631
598
  function isRemoteUses(value) {
632
599
  return typeof value === "string" && REMOTE_USES_RE.test(value);
633
600
  }
@@ -649,7 +616,7 @@ function parseRemoteUses(value, kind = "action") {
649
616
  repo,
650
617
  path,
651
618
  ref,
652
- package: packageKey(owner, repo, path),
619
+ package: path && path !== "." ? `github:${owner}/${repo}/${path}` : `github:${owner}/${repo}`,
653
620
  kind: inferredKind
654
621
  };
655
622
  }
@@ -907,7 +874,7 @@ function substituteString(value, values) {
907
874
  if (!expression) return value;
908
875
  try {
909
876
  const expr = parseExpression(expression);
910
- const replacement = directReplacement(expr, values);
877
+ const replacement = valueForIndexAccess(expr, values);
911
878
  if (replacement !== void 0) return replacement;
912
879
  const evaluated = staticExpression(expr, values);
913
880
  if (evaluated) return evaluated.value;
@@ -951,9 +918,6 @@ function findToken(token, path) {
951
918
  return Number.isInteger(index) ? findToken(token.get(index), rest) : void 0;
952
919
  }
953
920
  }
954
- function directReplacement(expr, values) {
955
- return valueForIndexAccess(expr, values);
956
- }
957
921
  //#endregion
958
922
  //#region src/pack.ts
959
923
  async function pack(options = {}) {
@@ -1236,7 +1200,8 @@ async function why(packageName, options = {}) {
1236
1200
  if (matches.size === 0) throw new Error(`Package is not in the lockfile: ${packageName}`);
1237
1201
  const paths = [];
1238
1202
  for (const [source, entry] of Object.entries(lockfile.entries)) for (const dependency of entry.dependencies) collectWhyPaths(lockfile, dependency, matches, [source], paths);
1239
- const output = paths.length === 0 ? `${packageName} is not reachable\n` : `${packageName} is used by:\n\n${paths.map(formatPath).join("\n\n")}\n`;
1203
+ let output = `${packageName} is not reachable\n`;
1204
+ if (paths.length > 0) output = `${packageName} is used by:\n\n${paths.map((path) => path.map((item, index) => `${" ".repeat(index)}${index === 0 ? item : `└─ ${item}`}`).join("\n")).join("\n\n")}\n`;
1240
1205
  options.stdout?.write(output);
1241
1206
  return output;
1242
1207
  }
@@ -1279,9 +1244,6 @@ function collectWhyPaths(lockfile, dependency, matches, current, paths) {
1279
1244
  if (matches.has(dependency.package)) paths.push(next);
1280
1245
  item?.dependencies.forEach((child) => collectWhyPaths(lockfile, child, matches, next, paths));
1281
1246
  }
1282
- function formatPath(path) {
1283
- return path.map((item, index) => `${" ".repeat(index)}${index === 0 ? item : `└─ ${item}`}`).join("\n");
1284
- }
1285
1247
  async function readHeadLockfile(cwd) {
1286
1248
  try {
1287
1249
  const { stdout } = await execFileAsync("git", ["show", `HEAD:${LOCKFILE_PATH}`], { cwd });
package/dist/index.mjs CHANGED
@@ -1,2 +1,2 @@
1
- import { a as why, c as packWorkflow, d as collectWorkflowDependencies, f as scan, i as update, l as verify, n as diffLockfiles, o as assertNoRemoteUses, r as tree, s as pack, t as diff, u as collectStepDependencies } from "./commands-4KKgssQI.mjs";
1
+ import { a as why, c as packWorkflow, d as collectWorkflowDependencies, f as scan, i as update, l as verify, n as diffLockfiles, o as assertNoRemoteUses, r as tree, s as pack, t as diff, u as collectStepDependencies } from "./commands-BayB-wc-.mjs";
2
2
  export { assertNoRemoteUses, collectStepDependencies, collectWorkflowDependencies, diff, diffLockfiles, pack, packWorkflow, scan, tree, update, verify, why };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "actionspack",
3
3
  "type": "module",
4
- "version": "0.1.3",
4
+ "version": "0.1.5",
5
5
  "description": "Lockfile-first GitHub Actions workflow packer",
6
6
  "author": "Kevin Deng <sxzz@sxzz.moe>",
7
7
  "license": "MIT",
@@ -40,7 +40,7 @@
40
40
  "devDependencies": {
41
41
  "@sxzz/eslint-config": "^8.1.0",
42
42
  "@sxzz/prettier-config": "^2.3.1",
43
- "@types/node": "^25.9.0",
43
+ "@types/node": "^25.9.1",
44
44
  "@typescript/native-preview": "7.0.0-dev.20260519.1",
45
45
  "bumpp": "^11.1.0",
46
46
  "eslint": "^10.4.0",