@elia97/officina 0.11.0 → 0.13.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.
Files changed (45) hide show
  1. package/README.md +92 -12
  2. package/dist/bin.js +4 -1
  3. package/dist/checks/bundle.js +13 -2
  4. package/dist/checks/doctor.js +13 -10
  5. package/dist/checks/migrate.d.ts +2 -0
  6. package/dist/checks/migrate.js +44 -0
  7. package/dist/checks/placeholders.js +15 -6
  8. package/dist/checks/secrets.d.ts +2 -0
  9. package/dist/checks/secrets.js +46 -0
  10. package/dist/checks/smoke.js +20 -4
  11. package/dist/index.d.ts +1 -0
  12. package/dist/lib/alignment.d.ts +1 -0
  13. package/dist/lib/alignment.js +4 -2
  14. package/dist/lib/bundle-css.d.ts +7 -0
  15. package/dist/lib/bundle-css.js +19 -0
  16. package/dist/lib/check-placeholders.d.ts +3 -1
  17. package/dist/lib/check-placeholders.js +9 -3
  18. package/dist/lib/check-routes.js +5 -3
  19. package/dist/lib/cli.d.ts +1 -0
  20. package/dist/lib/cli.js +1 -0
  21. package/dist/lib/config-gaps.js +1 -6
  22. package/dist/lib/config.d.ts +26 -1
  23. package/dist/lib/config.js +28 -2
  24. package/dist/lib/database-auth.d.ts +5 -0
  25. package/dist/lib/database-auth.js +81 -0
  26. package/dist/lib/env-schema.d.ts +8 -0
  27. package/dist/lib/env-schema.js +37 -0
  28. package/dist/lib/migrate.d.ts +11 -0
  29. package/dist/lib/migrate.js +29 -0
  30. package/dist/lib/routes.js +5 -3
  31. package/dist/lib/secrets.d.ts +10 -0
  32. package/dist/lib/secrets.js +53 -0
  33. package/dist/lib/smoke-on-demand.d.ts +8 -0
  34. package/dist/lib/smoke-on-demand.js +65 -0
  35. package/dist/lib/smoke-production.d.ts +4 -1
  36. package/dist/lib/smoke-production.js +2 -0
  37. package/dist/lib/validate.d.ts +2 -0
  38. package/dist/lib/validate.js +9 -1
  39. package/dist/lib/vercel-build.js +0 -1
  40. package/dist/lib/workflows.d.ts +14 -0
  41. package/dist/lib/workflows.js +44 -3
  42. package/dist/smoke.d.ts +1 -0
  43. package/dist/smoke.js +1 -0
  44. package/package.json +1 -1
  45. package/presets/biome.base.json +2 -1
@@ -1,10 +1,12 @@
1
1
  export type Check = (value: unknown, path: string) => string[];
2
+ export declare const isRecord: (value: unknown) => value is Record<string, unknown>;
2
3
  export declare function typeName(value: unknown): string;
3
4
  export declare const problem: (path: string, expected: string, value: unknown) => string;
4
5
  export declare const aString: Check;
5
6
  export declare const aNumber: Check;
6
7
  export declare const aFunction: Check;
7
8
  export declare const aStringOrNull: Check;
9
+ export declare const anEnvName: Check;
8
10
  export declare const anArrayOf: (item: Check) => Check;
9
11
  export declare const aRecordOf: (item: Check) => Check;
10
12
  export declare const oneOf: (expected: string, allowed: readonly unknown[]) => Check;
@@ -1,4 +1,4 @@
1
- const isRecord = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
1
+ export const isRecord = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
2
2
  const NAMES = new Map([
3
3
  ['string', 'una stringa'],
4
4
  ['number', 'un numero'],
@@ -18,6 +18,14 @@ export const aString = (value, path) => (typeof value === 'string' ? [] : [probl
18
18
  export const aNumber = (value, path) => (typeof value === 'number' ? [] : [problem(path, 'un numero', value)]);
19
19
  export const aFunction = (value, path) => typeof value === 'function' ? [] : [problem(path, 'una funzione', value)];
20
20
  export const aStringOrNull = (value, path) => typeof value === 'string' || value === null ? [] : [problem(path, 'una stringa oppure null', value)];
21
+ const ENV_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
22
+ export const anEnvName = (value, path) => {
23
+ if (typeof value !== 'string')
24
+ return [problem(path, 'una stringa', value)];
25
+ if (ENV_NAME.test(value))
26
+ return [];
27
+ return [`${path}: atteso il nome di una variabile d'ambiente, ricevuto ${JSON.stringify(value)}`];
28
+ };
21
29
  export const anArrayOf = (item) => (value, path) => Array.isArray(value)
22
30
  ? value.flatMap((entry, index) => item(entry, `${path}[${index}]`))
23
31
  : [problem(path, 'un array', value)];
@@ -115,7 +115,6 @@ export function ssrRoutes(files) {
115
115
  const found = firstManifest(files);
116
116
  return found === null ? null : routesOf(found.host, found.manifest, files);
117
117
  }
118
- // `routePattern` non legge i segmenti misti (`[slug].md`, `post-[id]`), che il pattern del manifest di Astro sì.
119
118
  function astroPattern(route, pattern) {
120
119
  if (typeof pattern === 'string') {
121
120
  try {
@@ -4,3 +4,17 @@ export declare const isLive: (line: string) => boolean;
4
4
  export declare const mentions: (source: string | undefined, needle: string) => boolean;
5
5
  /** Le action del progetto devono stare alla stessa versione del pacchetto installato. */
6
6
  export declare function workflowGaps({ read }: ProjectFiles, version: string): ContractGap[];
7
+ export interface ActionInputs {
8
+ workflow: string;
9
+ action: string;
10
+ inputs: readonly {
11
+ name: string;
12
+ reason: string;
13
+ notFrom?: readonly {
14
+ secret: string;
15
+ reason: string;
16
+ }[];
17
+ }[];
18
+ }
19
+ /** Un workflow che non usa l'action lo segnala già `workflowGaps`. */
20
+ export declare function actionInputGaps({ read }: ProjectFiles, { workflow, action, inputs }: ActionInputs): ContractGap[];
@@ -1,5 +1,5 @@
1
- // Niente parser YAML nel pacchetto: la ricerca è testuale. Una riga commentata non soddisfa nessun
2
- // controllo, altrimenti basta commentare un passo per avere un progetto «a posto» che non lo lancia.
1
+ import { parse } from 'yaml';
2
+ import { isRecord } from './validate.js';
3
3
  export const isLive = (line) => !line.trimStart().startsWith('#');
4
4
  const liveLines = (source) => source.split(/\r?\n/).filter(isLive);
5
5
  export const mentions = (source, needle) => source !== undefined && liveLines(source).some((line) => line.includes(needle));
@@ -36,8 +36,9 @@ function refGap(line, ref, name, expected) {
36
36
  ? undefined
37
37
  : `\`${name}@${ref.slice(0, 7)}… ${declared}\`: il pacchetto installato è la ${expected.slice(1)}`;
38
38
  }
39
+ const actionName = (action) => `Elia97/officina/actions/${action}`;
39
40
  function actionGap(source, action, version) {
40
- const name = `Elia97/officina/actions/${action}`;
41
+ const name = actionName(action);
41
42
  if (source === undefined)
42
43
  return `manca: i suoi passi arrivano da \`${name}\``;
43
44
  const found = refOf(source, name);
@@ -50,3 +51,43 @@ export function workflowGaps({ read }, version) {
50
51
  return message === undefined ? [] : [{ path: workflow, message }];
51
52
  });
52
53
  }
54
+ function parsed(source) {
55
+ try {
56
+ return { workflow: parse(source) };
57
+ }
58
+ catch (error) {
59
+ return { error: `non è YAML valido: ${String(error).replace(/\n[\s\S]*$/, '')}` };
60
+ }
61
+ }
62
+ function stepsUsing(source, action) {
63
+ const result = parsed(source);
64
+ if ('error' in result)
65
+ return result.error;
66
+ const { workflow } = result;
67
+ const jobs = isRecord(workflow) && isRecord(workflow.jobs) ? Object.values(workflow.jobs) : [];
68
+ return jobs
69
+ .flatMap((job) => (isRecord(job) && Array.isArray(job.steps) ? job.steps : []))
70
+ .filter((step) => isRecord(step) && String(step.uses).startsWith(`${actionName(action)}@`));
71
+ }
72
+ const inputValue = (step, input) => isRecord(step.with) ? step.with[input] : undefined;
73
+ const receives = (step, input) => {
74
+ const value = inputValue(step, input);
75
+ return value !== undefined && value !== null && value !== '';
76
+ };
77
+ const receivesFrom = (step, input, secret) => String(inputValue(step, input)).split(/\W/).includes(secret);
78
+ /** Un workflow che non usa l'action lo segnala già `workflowGaps`. */
79
+ export function actionInputGaps({ read }, { workflow, action, inputs }) {
80
+ const source = read(workflow);
81
+ if (source === undefined)
82
+ return [];
83
+ const steps = stepsUsing(source, action);
84
+ if (typeof steps === 'string')
85
+ return [{ path: workflow, message: steps }];
86
+ const gap = (message) => [{ path: workflow, message: `\`${actionName(action)}\` ${message}` }];
87
+ return inputs.flatMap(({ name, reason, notFrom = [] }) => {
88
+ if (steps.some((step) => !receives(step, name)))
89
+ return gap(`non riceve \`${name}\`: ${reason}`);
90
+ const wrong = notFrom.find(({ secret }) => steps.some((step) => receivesFrom(step, name, secret)));
91
+ return wrong === undefined ? [] : gap(`riceve \`${name}\` da \`${wrong.secret}\`: ${wrong.reason}`);
92
+ });
93
+ }
package/dist/smoke.d.ts CHANGED
@@ -1,2 +1,3 @@
1
1
  export { NON_HTML_ROUTES } from './lib/routes.ts';
2
+ export { checkOnDemandPages } from './lib/smoke-on-demand.ts';
2
3
  export { checkBotIdChallenge, checkCanonicalHost, checkPages, checkSecurityHeaders, checkTrailingSlash, DEFAULT_CHECKS, SECURITY_HEADERS, } from './lib/smoke-production.ts';
package/dist/smoke.js CHANGED
@@ -1,2 +1,3 @@
1
1
  export { NON_HTML_ROUTES } from './lib/routes.js';
2
+ export { checkOnDemandPages } from './lib/smoke-on-demand.js';
2
3
  export { checkBotIdChallenge, checkCanonicalHost, checkPages, checkSecurityHeaders, checkTrailingSlash, DEFAULT_CHECKS, SECURITY_HEADERS, } from './lib/smoke-production.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elia97/officina",
3
- "version": "0.11.0",
3
+ "version": "0.13.0",
4
4
  "description": "Gli strumenti dei progetti nati dai template di Elia: gate di qualità, generatori e verifiche di build e di produzione.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -11,7 +11,8 @@
11
11
  "!!**/public",
12
12
  "!!.claude/worktrees",
13
13
  "!!src/assets/**/*.svg",
14
- "!!.release-please-manifest.json"
14
+ "!!.release-please-manifest.json",
15
+ "!!drizzle"
15
16
  ]
16
17
  },
17
18
  "formatter": {