@aksp/opencrew 1.9.0 → 1.10.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 (36) hide show
  1. package/CHANGELOG.md +58 -0
  2. package/README.md +88 -4
  3. package/package.json +1 -1
  4. package/templates/AGENTS.md +10 -2
  5. package/templates/_opencrew/.opencrew-version +1 -1
  6. package/templates/_opencrew/core/best-practices/_catalog.yaml +5 -0
  7. package/templates/_opencrew/core/best-practices/documento-oficial.md +144 -0
  8. package/templates/_opencrew/core/modelos/documento-oficial.md +42 -0
  9. package/templates/_opencrew/core/prompts/design.prompt.md +1 -0
  10. package/templates/_opencrew/core/prompts/documento.prompt.md +134 -0
  11. package/templates/_opencrew/core/prompts/entrega.prompt.md +5 -4
  12. package/templates/_opencrew/core/scripts/documento/argumentos.mjs +50 -0
  13. package/templates/_opencrew/core/scripts/documento/corpo.mjs +51 -0
  14. package/templates/_opencrew/core/scripts/documento/estilos.mjs +42 -0
  15. package/templates/_opencrew/core/scripts/documento/gravar.mjs +44 -0
  16. package/templates/_opencrew/core/scripts/documento/linha.mjs +58 -0
  17. package/templates/_opencrew/core/scripts/documento/marcacoes.mjs +55 -0
  18. package/templates/_opencrew/core/scripts/documento/markdown.mjs +92 -0
  19. package/templates/_opencrew/core/scripts/documento/pacote.mjs +82 -0
  20. package/templates/_opencrew/core/scripts/documento/perfil.mjs +70 -0
  21. package/templates/_opencrew/core/scripts/documento/png.mjs +20 -0
  22. package/templates/_opencrew/core/scripts/documento/projeto.mjs +60 -0
  23. package/templates/_opencrew/core/scripts/documento/tabelas.mjs +57 -0
  24. package/templates/_opencrew/core/scripts/documento/timbre.mjs +64 -0
  25. package/templates/_opencrew/core/scripts/documento/xml.mjs +95 -0
  26. package/templates/_opencrew/core/scripts/documento/zip.mjs +86 -0
  27. package/templates/_opencrew/core/scripts/documento.mjs +150 -0
  28. package/templates/_opencrew/core/scripts/entrega/canais.mjs +7 -3
  29. package/templates/_opencrew/core/scripts/entrega/comparar.mjs +2 -2
  30. package/templates/_opencrew/core/scripts/entrega/copia.mjs +1 -1
  31. package/templates/_opencrew/core/scripts/entrega/documentos.mjs +104 -0
  32. package/templates/_opencrew/core/scripts/entrega/gravar.mjs +3 -3
  33. package/templates/_opencrew/core/scripts/entrega/leiame.mjs +3 -2
  34. package/templates/_opencrew/core/scripts/entrega/passos.mjs +12 -2
  35. package/templates/_opencrew/core/scripts/entrega/separar.mjs +3 -0
  36. package/templates/_opencrew/core/scripts/entregar.mjs +12 -5
@@ -5,7 +5,7 @@
5
5
  // (ressalvas, a cópia e "Sobre esta pasta"), no repositório do OpenCrew.
6
6
  import { CANAIS, EDITAVEIS, OUTROS } from './canais.mjs';
7
7
  import { frasesDePendencia } from './pendencias.mjs';
8
- import { passosDe } from './passos.mjs';
8
+ import { NAO_EDITE_O_WORD, passosDe } from './passos.mjs';
9
9
 
10
10
  export const TEXTO = {
11
11
  vaiPublicar: 'Esta crew publica este canal sozinha. Antes de postar à mão, confira se já saiu.',
@@ -64,7 +64,8 @@ function secaoDoCanal(d, pasta) {
64
64
  // Na cópia, o canal que não foi copiado traz só a situação e o que falta.
65
65
  if (d.ehCopia && pendencias.length) return secao(CANAIS[pasta], [situacao, ...bloco('Pendências:', pendencias)]);
66
66
  const arquivos = d.arquivos.filter((a) => a.pasta === pasta);
67
- const passos = passosDe(pasta, arquivos, pendencias.length > 0, d.ressalvas.has(pasta)).map((p, i) => `${i + 1}. ${p}`);
67
+ const daPasta = passosDe(pasta, arquivos, pendencias.length > 0, d.ressalvas.has(pasta));
68
+ const passos = daPasta.filter((p) => !d.ehCopia || p !== NAO_EDITE_O_WORD).map((p, i) => `${i + 1}. ${p}`);
68
69
  return secao(CANAIS[pasta], [
69
70
  ...(d.vaiPublicar.includes(pasta) ? [TEXTO.vaiPublicar] : []),
70
71
  situacao,
@@ -7,7 +7,9 @@
7
7
  export const ANTES_DE_POSTAR = 'Antes de postar, resolva o que está em Pendências. Corrija no arquivo de origem e peça para montar a entrega de novo: o que você mudar nesta pasta se perde.';
8
8
  /** O primeiro passo do canal entregue com ressalva. */
9
9
  export const CONFIRA_AS_RESSALVAS = 'Antes de postar, confira as ressalvas em "Antes de usar".';
10
- const MARKDOWN = 'Este texto está em markdown: se o seu editor não aceitar, ajuste títulos, negrito e links depois de colar.';
10
+ /** O passo de `documentos` que só vale na pasta da entrega (a cópia não é refeita). Spec: fase-u3b-documento-word.md, §6. */
11
+ export const NAO_EDITE_O_WORD = 'Não edite o Word dentro desta pasta: ela é refeita a cada entrega. Para mexer, copie o arquivo para outra pasta do projeto.';
12
+ const MARKDOWN ='Este texto está em markdown: se o seu editor não aceitar, ajuste títulos, negrito e links depois de colar.';
11
13
 
12
14
  // Cada passo: [o que o faz aparecer, o texto]. null = sempre que o canal tem arquivo; um tipo de
13
15
  // arquivo = só quando a entrega tem arquivo desse tipo no canal; `tipo:um` e `tipo:varios` = só
@@ -58,6 +60,12 @@ const PASSOS = {
58
60
  [null, 'Confira e publique.'],
59
61
  ],
60
62
  youtube: [['roteiro', '`youtube/{arquivo}` é o roteiro para gravar: não é texto para colar.']],
63
+ documentos: [
64
+ ['documento', 'Abra `documentos/{arquivo}` no Word e confira: cabeçalho, páginas, tabelas e assinaturas.'],
65
+ ['documento', NAO_EDITE_O_WORD],
66
+ ['documento', 'Para ter um PDF: abra o documento no Word e use Arquivo → Salvar como → PDF.'],
67
+ ['documento', 'O Word é uma cópia do texto. O que você mudar nele não volta sozinho: altere o texto e gere de novo.'],
68
+ ],
61
69
  };
62
70
  // "texto": o roteiro e o arquivo que foi inteiro (carrossel em texto, peça não encontrada).
63
71
  const TIPOS = { texto: ['roteiro', 'inteiro'] };
@@ -100,5 +108,7 @@ function doQuadro(pasta, arquivos) {
100
108
  export function passosDe(pasta, arquivos, comPendencia = false, comRessalva = false) {
101
109
  const passos = doQuadro(pasta, arquivos);
102
110
  const antes = comPendencia ? ANTES_DE_POSTAR : comRessalva ? CONFIRA_AS_RESSALVAS : null;
103
- return antes && passos.length ? [antes, ...passos] : passos;
111
+ // Documento não se posta: imprime-se, assina-se, protocola-se.
112
+ const frase = pasta === 'documentos' ? antes?.replace('Antes de postar', 'Antes de usar') : antes;
113
+ return frase && passos.length ? [frase, ...passos] : passos;
104
114
  }
@@ -6,6 +6,7 @@ import path from 'node:path';
6
6
  import { lerItem } from '../verificar/arquivos.mjs';
7
7
  import { semFrontmatter } from '../verificar/leitura.mjs';
8
8
  import { EDITAVEIS, OUTROS } from './canais.mjs';
9
+ import { DOCUMENTOS, tipoDoDocumento } from './documentos.mjs';
9
10
  import { avisoDeFora, blocosFora } from './fora.mjs';
10
11
  import { PRINCIPAL, hashtagsSoltas, pecasDoLeitor } from './leitor.mjs';
11
12
  import { pecasDoBlog, pecasDoEmail, pecasDoWhatsapp, tweetsDaThread } from './longas.mjs';
@@ -82,6 +83,8 @@ async function separarItem(item, itens, p) {
82
83
  p.avisos.push({ pasta: OUTROS, tela: MSG.semCanal(item.rel) });
83
84
  return p.copias.push(copia(item, OUTROS, 'copia'));
84
85
  }
86
+ // Documento oficial: vai inteiro, sem procura de peças; `documentos.mjs` converte o texto em Word.
87
+ if (item.canal === DOCUMENTOS) return p.copias.push(copia(item, DOCUMENTOS, tipoDoDocumento(item.abs)));
85
88
  const lido = ['.md', '.txt'].includes(extensao(item)) ? await lerItem(item.abs) : null;
86
89
  if (lido?.tipo !== 'texto') return p.copias.push(copia(item, item.canal, ehImagem(item) ? 'imagem' : 'copia'));
87
90
  const texto = emLf(lido.texto);
@@ -11,6 +11,8 @@
11
11
  // --aceitar-pendencias: as pendências deste momento viram ressalvas ("entregar assim mesmo"); a
12
12
  // ressalva vale enquanto a pendência dela existir: a que some sai do `ressalvas.json`.
13
13
  // --vai-publicar: canal que a crew publica sozinha (pode repetir); o LEIA-ME avisa.
14
+ // Item de formato com a plataforma `documento` (`.md` ou `.txt`) sai em `entrega/documentos/`, em
15
+ // Word, com o perfil de documento oficial do projeto (fase-u3b-documento-word.md, regra 12).
14
16
  // Grava em crews/<crew>/output/<run>/: a pasta `entrega/` (refeita do zero a cada chamada, montada
15
17
  // em `entrega.tmp/`), `verificacao-entrega.md`, `ressalvas.json` (as pendências aceitas que ainda
16
18
  // existem) e `copia.json` (o retrato do que foi copiado, para a comparação seguinte). Fora
@@ -29,6 +31,7 @@ import { formatarRelatorio, verificar } from './verificar.mjs';
29
31
  import { USO, lerArgs, lerLista, limpar, runValido } from './entrega/argumentos.mjs';
30
32
  import { CANAIS, canalDoFormato, ehCanal, ehDeServico } from './entrega/canais.mjs';
31
33
  import { MSG as DESTINO, escolherDestino, validarDestino } from './entrega/destino.mjs';
34
+ import { DOCUMENTOS, converterDocumentos, naoConferidoDoWord } from './entrega/documentos.mjs';
32
35
  import { MSG as COPIA, guardar } from './entrega/guardar.mjs';
33
36
  import { gravarEntrega } from './entrega/gravar.mjs';
34
37
  import { montarLeiame } from './entrega/leiame.mjs';
@@ -84,15 +87,18 @@ async function classificar(raiz, pedidos) {
84
87
  async function montar(raiz, args, itens, aceitas) {
85
88
  const arquivosDaLista = itens.map((i) => (i.formato ? { arquivo: i.arquivo, formato: i.formato } : i.arquivo));
86
89
  const verificacao = await verificar({ raiz, crew: args.crew, arquivos: arquivosDaLista, semPadraoDeBlog: true });
87
- const { arquivos, avisos } = nomear(await separar(itens));
90
+ const separados = await converterDocumentos(raiz, await separar(itens), args.gerarDocx);
91
+ const { arquivos, avisos } = nomear(separados.produtos);
88
92
  const todas = pendenciasPorPasta(raiz, itens, verificacao);
93
+ // O texto que não virou Word é pendência de `documentos` (fase-u3b-documento-word.md, regra 12).
94
+ if (separados.pendencias.length) todas.set(DOCUMENTOS, [...(todas.get(DOCUMENTOS) ?? []), ...separados.pendencias]);
89
95
  const presentes = new Set([...arquivos.map((a) => a.pasta), ...todas.keys()]);
90
96
  const dados = {
91
97
  crew: path.basename(path.resolve(raiz, args.crew)), run: args.run, arquivos, avisos,
92
98
  ...separarPendencias(todas, aceitas, args.aceitar),
93
99
  pastas: Object.keys(CANAIS).filter((c) => presentes.has(c)),
94
100
  alertas: await alertasDeTamanho(raiz, itens, arquivos, verificacao),
95
- naoConferido: naoConferido(raiz, itens, verificacao),
101
+ naoConferido: [...naoConferido(raiz, itens, verificacao), ...naoConferidoDoWord(arquivos)],
96
102
  vaiPublicar: args.vaiPublicar,
97
103
  };
98
104
  return { dados, relatorio: `${formatarRelatorio(verificacao)}\n` };
@@ -137,12 +143,13 @@ async function entregar(raiz, args, itens, escrever) {
137
143
 
138
144
  /**
139
145
  * @param {string[]} argv
140
- * @param {object} [deps] `cwd` (a pasta do projeto) e `escrever`
146
+ * @param {object} [deps] `cwd` (a pasta do projeto), `escrever` e `gerarDocx` (quem monta o Word
147
+ * de um documento oficial; sem ele, o do `documento.mjs`)
141
148
  * @returns {Promise<number>} 0 = a linha `ENTREGA:` saiu (ou `--ajuda`) · 1 = erro de uso, ou erro
142
149
  * que impediu a entrega inteira (uma linha em PT-BR, sem linha `ENTREGA:`, nada escrito)
143
150
  */
144
- export async function main(argv, { cwd = process.cwd(), escrever = (s) => process.stdout.write(`${s}\n`) } = {}) {
145
- const args = lerArgs(argv);
151
+ export async function main(argv, { cwd = process.cwd(), escrever = (s) => process.stdout.write(`${s}\n`), gerarDocx } = {}) {
152
+ const args = { ...lerArgs(argv), gerarDocx };
146
153
  const sair = (...linhas) => linhas.forEach((l) => escrever(l)) ?? 1;
147
154
  if (args.ajuda) return sair(USO) && 0;
148
155
  const pedidos = lerLista(args.arquivos);