poe-code 3.0.275 → 3.0.276

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "poe-code",
3
- "version": "3.0.275",
3
+ "version": "3.0.276",
4
4
  "description": "CLI tool to configure Poe API for developer workflows.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -14,6 +14,11 @@ function toStringRecord(value) {
14
14
  }
15
15
  return out;
16
16
  }
17
+ function toBinRecord(value, packageName) {
18
+ if (typeof value === "string")
19
+ return { [packageName]: value };
20
+ return toStringRecord(value);
21
+ }
17
22
  function toStringArray(value) {
18
23
  return Array.isArray(value) ? value.filter((v) => typeof v === "string") : [];
19
24
  }
@@ -46,7 +51,7 @@ async function loadPackage(fs, rootDir, relDir, isRoot) {
46
51
  repositoryDirectory: typeof repository?.directory === "string" ? repository.directory : undefined,
47
52
  ecosystem,
48
53
  exports: pkg.exports,
49
- bin: toStringRecord(pkg.bin),
54
+ bin: toBinRecord(pkg.bin, typeof pkg.name === "string" ? pkg.name : relDir),
50
55
  files: Array.isArray(pkg.files)
51
56
  ? pkg.files.filter((f) => typeof f === "string")
52
57
  : [],
@@ -88,6 +93,29 @@ function normalizeWorkingDir(wd) {
88
93
  p = p.slice(2);
89
94
  return p.endsWith("/") ? p.slice(0, -1) : p;
90
95
  }
96
+ function splitShellLine(line) {
97
+ return line
98
+ .trim()
99
+ .split(" ")
100
+ .map((part) => part.trim())
101
+ .filter(Boolean);
102
+ }
103
+ function hasPublishRun(run) {
104
+ for (const line of run.split("\n")) {
105
+ const trimmed = line.trim();
106
+ if (trimmed.length === 0 || trimmed.startsWith("#"))
107
+ continue;
108
+ const parts = splitShellLine(trimmed);
109
+ if (parts[0] === "npm" && parts[1] === "publish")
110
+ return true;
111
+ if (parts.includes("semantic-release"))
112
+ return true;
113
+ }
114
+ return false;
115
+ }
116
+ function isPypiPublishAction(uses) {
117
+ return uses === "pypa/gh-action-pypi-publish" || uses.startsWith("pypa/gh-action-pypi-publish@");
118
+ }
91
119
  function parseLockstepGroup(value) {
92
120
  if (typeof value !== "string")
93
121
  return { dirs: [], valid: false };
@@ -115,17 +143,19 @@ function parseReleaseWorkflow(file, raw) {
115
143
  const lockstepGroups = [];
116
144
  const jobs = doc?.jobs && typeof doc.jobs === "object" ? doc.jobs : {};
117
145
  for (const job of Object.values(jobs)) {
146
+ const workflowJob = job && typeof job === "object" ? job : {};
118
147
  const activeLockstepGroups = [];
119
- const steps = Array.isArray(job?.steps)
120
- ? job.steps
121
- : [];
148
+ const steps = Array.isArray(workflowJob.steps) ? workflowJob.steps : [];
149
+ const defaultWorkingDir = typeof workflowJob.defaults?.run?.["working-directory"] === "string"
150
+ ? normalizeWorkingDir(workflowJob.defaults.run["working-directory"])
151
+ : undefined;
122
152
  for (const step of steps) {
123
153
  const run = typeof step.run === "string" ? step.run : "";
124
154
  const uses = typeof step.uses === "string" ? step.uses : "";
125
155
  const wd = typeof step["working-directory"] === "string"
126
156
  ? normalizeWorkingDir(step["working-directory"])
127
- : undefined;
128
- if (run.includes("npm publish") || run.includes("semantic-release")) {
157
+ : defaultWorkingDir;
158
+ if (hasPublishRun(run)) {
129
159
  const targetDir = wd ?? ".";
130
160
  targetDirs.add(targetDir);
131
161
  for (const group of activeLockstepGroups) {
@@ -133,14 +163,14 @@ function parseReleaseWorkflow(file, raw) {
133
163
  group.publishedDirs.push(targetDir);
134
164
  }
135
165
  }
136
- if (uses.startsWith("pypa/gh-action-pypi-publish")) {
166
+ if (isPypiPublishAction(uses)) {
137
167
  const pd = step.with?.["packages-dir"];
138
168
  if (typeof pd === "string")
139
169
  targetDirs.add(dirFromArtifactPath(pd));
140
170
  }
141
171
  if (uses === "./.github/actions/prepare-lockstep-release") {
142
172
  const parsed = parseLockstepGroup(step.with?.packages);
143
- const validVersion = typeof step.with?.version === "string" && step.with.version.length > 0;
173
+ const validVersion = typeof step.with?.version === "string" && step.with.version.trim().length > 0;
144
174
  const group = { ...parsed, valid: parsed.valid && validVersion, publishedDirs: [] };
145
175
  lockstepGroups.push(group);
146
176
  activeLockstepGroups.push(group);
@@ -159,7 +189,9 @@ async function loadReleaseWorkflows(fs, rootDir) {
159
189
  return [];
160
190
  }
161
191
  const files = entries
162
- .filter((e) => !e.isDirectory() && e.name.startsWith("release") && e.name.endsWith(".yml"))
192
+ .filter((e) => !e.isDirectory() &&
193
+ e.name.startsWith("release") &&
194
+ (e.name.endsWith(".yml") || e.name.endsWith(".yaml")))
163
195
  .map((e) => e.name)
164
196
  .sort();
165
197
  const workflows = await Promise.all(files.map(async (file) => parseReleaseWorkflow(file, await fs.readFile(path.join(dir, file)))));
@@ -55,7 +55,13 @@ export function formatReport(result, opts) {
55
55
  const errors = result.violations.filter((v) => v.severity === "error").length;
56
56
  const warnings = result.violations.length - errors;
57
57
  if (result.summary.ok) {
58
- lines.push(color.green(`✓ all ${result.summary.rules} rules passed`));
58
+ if (result.skipped.length > 0) {
59
+ const passed = result.summary.rules - result.skipped.length;
60
+ lines.push(color.green(`✓ ${passed} rules passed · ${result.skipped.length} skipped`));
61
+ }
62
+ else {
63
+ lines.push(color.green(`✓ all ${result.summary.rules} rules passed`));
64
+ }
59
65
  }
60
66
  else {
61
67
  const summary = `${failedRules} rules failed · ${result.violations.length} violations`;
@@ -23,6 +23,15 @@ export const rules = [
23
23
  publishedBinMustBeExecutable
24
24
  ];
25
25
  export function runRules(model, build, only) {
26
+ if (only && only.length > 0) {
27
+ const known = new Set(rules.map((rule) => rule.id));
28
+ const unknown = only.filter((id) => !known.has(id));
29
+ if (unknown.length > 0) {
30
+ throw new Error(`Unknown package-lint rule: ${unknown.join(", ")}. Known rules: ${rules
31
+ .map((rule) => rule.id)
32
+ .join(", ")}`);
33
+ }
34
+ }
26
35
  const selected = only && only.length > 0 ? rules.filter((r) => only.includes(r.id)) : rules;
27
36
  const violations = [];
28
37
  const skipped = [];
@@ -1,5 +1,36 @@
1
1
  import { isGenuinelyPublished } from "../model.js";
2
2
  const id = "published-bin-must-be-executable";
3
+ function splitShellLine(line) {
4
+ return line
5
+ .trim()
6
+ .split(" ")
7
+ .map((part) => part.trim())
8
+ .filter(Boolean);
9
+ }
10
+ function stripQuotes(value) {
11
+ let out = value;
12
+ if (((out.startsWith('"') && out.endsWith('"')) || (out.startsWith("'") && out.endsWith("'"))) &&
13
+ out.length >= 2) {
14
+ out = out.slice(1, -1);
15
+ }
16
+ return out.replaceAll("\\", "/");
17
+ }
18
+ function prepackRunsSetBinExecutable(prepack) {
19
+ for (const line of prepack.split("\n")) {
20
+ const trimmed = line.trim();
21
+ if (trimmed.length === 0 || trimmed.startsWith("#"))
22
+ continue;
23
+ for (const part of splitShellLine(trimmed)) {
24
+ const token = stripQuotes(part);
25
+ if (token === "scripts/set-bin-executable.mjs" ||
26
+ token === "./scripts/set-bin-executable.mjs" ||
27
+ token.endsWith("/scripts/set-bin-executable.mjs")) {
28
+ return true;
29
+ }
30
+ }
31
+ }
32
+ return false;
33
+ }
3
34
  /**
4
35
  * `tsc` emits bin files with mode 0644, so a package that builds its bin with
5
36
  * the compiler ships a binary that loses its executable bit and fails with
@@ -19,7 +50,7 @@ export const publishedBinMustBeExecutable = {
19
50
  const bins = Object.values(pkg.bin);
20
51
  if (bins.length === 0)
21
52
  continue;
22
- if ((pkg.scripts.prepack ?? "").includes("set-bin-executable"))
53
+ if (prepackRunsSetBinExecutable(pkg.scripts.prepack ?? ""))
23
54
  continue;
24
55
  violations.push({
25
56
  rule: id,
@@ -36,7 +36,12 @@ function extractImportsFromAst(text, fileName) {
36
36
  }
37
37
  else if (ts.isExportDeclaration(statement) && statement.moduleSpecifier) {
38
38
  if (ts.isStringLiteralLike(statement.moduleSpecifier)) {
39
- out.push({ specifier: statement.moduleSpecifier.text, typeOnly: statement.isTypeOnly });
39
+ let typeOnly = statement.isTypeOnly;
40
+ if (!typeOnly && statement.exportClause && ts.isNamedExports(statement.exportClause)) {
41
+ const elements = statement.exportClause.elements;
42
+ typeOnly = elements.length > 0 && elements.every((e) => e.isTypeOnly);
43
+ }
44
+ out.push({ specifier: statement.moduleSpecifier.text, typeOnly });
40
45
  }
41
46
  }
42
47
  else if (ts.isImportEqualsDeclaration(statement)) {
@@ -62,13 +67,12 @@ function extractImportsFromAst(text, fileName) {
62
67
  return out;
63
68
  }
64
69
  export function extractRelevantImports(text, fileName) {
65
- if (text.includes("import type") || text.includes("export type") || text.includes("{ type ")) {
66
- return extractImportsFromAst(text, fileName);
67
- }
68
- return ts.preProcessFile(text, true, true).importedFiles.map(({ fileName: specifier }) => ({
69
- specifier,
70
- typeOnly: false
71
- }));
70
+ return extractImportsFromAst(text, fileName);
71
+ }
72
+ function isSourceFile(name) {
73
+ if (name.endsWith(".d.ts") || name.endsWith(".d.mts") || name.endsWith(".d.cts"))
74
+ return false;
75
+ return (name.endsWith(".ts") || name.endsWith(".tsx") || name.endsWith(".mts") || name.endsWith(".cts"));
72
76
  }
73
77
  async function listSourceFiles(fs, dir) {
74
78
  let entries;
@@ -84,8 +88,7 @@ async function listSourceFiles(fs, dir) {
84
88
  if (entry.isDirectory()) {
85
89
  files.push(...(await listSourceFiles(fs, full)));
86
90
  }
87
- else if ((entry.name.endsWith(".ts") || entry.name.endsWith(".tsx")) &&
88
- !entry.name.endsWith(".d.ts")) {
91
+ else if (isSourceFile(entry.name)) {
89
92
  files.push(full);
90
93
  }
91
94
  }